一个月学会VC++2012 3.我们动手吧!

    中国的文人,有个根深蒂固的传统:皓首穷经。

    从三百千千,到四书五经,到诸子百家,诗词歌赋,琴棋书画,每个学子往往要经历漫长的十几年甚至三十年。所谓十年寒窗无人问,一朝成名天下知,是所有文人的潜藏情节。所以,有老实人说出了老实话:百无一用是书生。我一直觉得他们仿佛在学东方不败大侠,先对自己狠心点,然后闭门苦练,最后终于明白了万物滋生的大道,乃至所向无敌。

    作为普通人,我们还是尽早动手为好。

 

    第一项任务很简单,精确点描述是这样的:创建一个应用程序,使用两个Ribbon按钮,在两个窗体中切换。

    是啊,用户做的第一件事情,就是找到功能,然后进入相应的界面,菜单、工具栏还有Vc++2010提供的Ribbon功能区方式,无论怎样,这都是首先要掌握的内容。

    站在程序员的角度,你最经常遇见的可能是哪些场景呢?界面、读写文件、读写数据库、设计业务类,就这些,做得专业点,号称程序员就顺理成章了。我觉得各种知识的讲解很多,但如何在实际工作中,做到像模象样,好象没有多少这样的资料。因此,这个系列讲解的虽然只是一项不太复杂的功能,但会尽量的讲透,涉及到界面、性能、异常、用户体验诸方面。

 

    因为是首次接触VC,那么我给自己定下的规矩很明确:学习阶段,绝不考虑重用性问题、绝不考虑扩展性问题、绝不考虑代码风格问题,尽量在第一时间将功能稳定的实现。在不熟悉语言的时候,你七七八八太多,那简直是在虐待自己的大脑。语言熟悉后这些东西你有精力再去玩,现在,还是老实点好。

     那么,我们首先面临的问题是使用传统的菜单、工具栏形式,还是使用Ribbon界面。这种选择对于我来说,不是什么问题,喜新厌旧据说是男人的本能,嗯,照照镜子,我发现里面是一张男人胡子拉碴的脸。然后呢?我遇到的第一个问题,是文档视图结构。

     这是一种延续了几十年的模式,换句话说,是一种古老的模式。大家看看记事本就知道了,处理一种后缀名为TXT的文件,打开、关闭、保存文件,将文件后缀在操作系统注册到记事本,简单的说,就是你创建的这种应用,将针对某种专用的格式,相关的一些处理工作,由文档视图结构预先的帮你做好,很贴心,是不是?

    所谓文档视图结构,马上动手,用项目向导创建一个简单的单文档、单视图结构的应用,可以看到VS帮我们创建的一些代码,嗯,四个类:一个应用程序类,是整个系统的入口,然后创建MainFrame实例、后者将管理文档类和视图类。文档类负责处理对这种格式的文档的各类操作,视图类负责呈现、编辑这种格式的文档。

    但是,两个问题:首先,我们现在已经很少敢于制定自己专有的文件格式,比如各类视频格式,都是几家恐怖的巨头在发布标准然后互相撕咬。一个可怜的程序员竟然做这种不合身份的事情,实在令人齿冷。其次,相对于专有格式而言,我们做桌面应用普遍还是以数据库应用居多。

    所以我的想法很简单,文档类基本不用,放个空壳在哪里。视图类呢?首先我想找寻类似Wpf的窗体设计器,视图能不能直接的“画界面”?很沮丧,不能,我只找到了对话框设计器。接下来,我们知道,若视图继承于CFormView,则可以使用对话框设计器来设计界面。 但这样我们需要做多视图项目,

    为了利用对话框的界面设计能力、同时保持整个程序的简单,浮现在我脑子里的,是一种古怪的想法:对话框有一种“子对话框”风格,我们在视图中直接打开这个对话框,则看起来是无缝集成的,我们就能自如的在两个对话框之间切换了。

    嗯,有熟悉的程序员马上要问了:不是有基于对话框的应用程序向导吗?两个问题:首先我们希望的是处理多个窗体,但不要弹出,这个是木有办法的;其次,更重要的是,这时候你不能在应用程序向导中选择Ribbon方式,也许有“在对话框应用中使用Ribbon“之类的深入研究,但我确实很懒惰,不太愿意在这类事情上消耗时间。

 

    接下来,我按照工作的步骤,简单的描述完成这项任务的过程。需要说明的是,这个系列的帖子是休闲贴,我不会一边做一边截图一边写,时间和精力不允许,所以很难做到step by step的方式,仅仅是列出步骤和基本的知识点,当然也包括初学者肯定会闹出的笑话。

 

1、创建项目:创建一个MFC应用,在配置向导中,注意勾上“功能区方式”,提供文档视图支持,单文档模式,其他无所谓。项目创建完毕后,运行一下。

 

 

 

 

 

 

 

 

 

 

2、简单的阅读一下生成的代码,次序是App类->MainFrame类->Document类->View类。不要深究,比如View中那堆很混乱的宏,只要从字面理解就行了:定义消息循环,不需要学究到一定要弄懂这堆宏究竟会转换成怎样的代码。读代码的方法,嘿嘿,这是秘籍:看看类结构,揣摩一下方法的名字,然后不要太关注每个函数内部的代码,会很快的。需要注意的问题,是我们经常见到的项目按文件夹组织的方式,这里没有,VC项目中叫做筛选器,仅仅将同一个目录下的文件分类,并不改变文件的物理位置。

3、我们将创建两个对话框,注意:style设为child,去掉确定和取消按钮,不要边框。仅仅放上一个静态文本字段,分别是“第一个”和“第二个”。

4、我们要为两个对话框创建类,这个不要傻乎乎的自己写代码:右键,为对话框生成类,就行了。为什么要有这个步骤?对话框以“资源”形式保存,仅仅保留了一个唯一的ID和控件类型、位置、大小等信息,我们毕竟要通过代码控制其工作,那么创建类绑定这个对话框就是必须要做的事情。

我不太喜欢用记事本写程序那一套,今后自然会理解:类向导自动的帮我们创建对应的对话框类,一般是继承自一个公用的基类,添加消息循环等代码片段,重写一些虚方法。

5、那么,我们在Ribbon设计器中,创建一个“面板”,然后放上两个按钮。

6、当然,需要为两个按钮创建“添加消息处理程序”,也是在Ribbon设计器中,右键,就能看到了。针对消息类型“Command"”和“Update_Command_UI”都要创建,前者是用户点击按钮后要做的事情,后者我们用来处理按钮是否可用的问题,当然,这两个方法添加到View类中。

7、我们现在有了四个消息处理函数,以Command为例,怎样在视图中显示一个对话框?

8、看看下面的代码:

void CSouGuView::OnButtonHome()
{

    if (CurrentDialog) //如果已经有对话框打开了,干掉它。
    {
        CurrentDialog->DestroyWindow();
        delete CurrentDialog;
    }

    CHomeDialog* dlg = new CHomeDialog(this);  //创建对话框类的实例
    CurrentDialog =dlg; //我们在View中定义的CDialogEx* CurrentDialog; 我们创建的对话框类都是从CDialogEx继承的,因此,这个指针用来表示当前显示的窗体。
    CurrentDialog->Create(dlg->IDD,this); //这里创建窗体
    CurrentDialog->ShowWindow(SW_SHOW);  //显示窗体
    CurrentDialog->CenterWindow(this);//将对话框在视图中居中,注意屏幕大小改变后,就不再居中,需要在OnSize事件中处理
    CurrentDialog->SetBackgroundColor(RGB(255,255,255));//将背景色改为白色,马虎应付一下

}

9、然后看看消息循环中的代码:

BEGIN_MESSAGE_MAP(CSouGuView, CView)
    ON_WM_CONTEXTMENU()
    ON_WM_RBUTTONUP()
    ON_COMMAND(ID_BUTTON_HOME, &CSouGuView::OnButtonHome)
    ON_WM_SIZE()
    ON_UPDATE_COMMAND_UI(ID_BUTTON_HOME, &CSouGuView::OnUpdateButtonHome) //注意这句,如果对应的对话框已经打开,则按钮不可用
    ON_COMMAND(ID_BUTTON_QUOTE, &CSouGuView::OnButtonQuote) //注意这一句:如果用户点击这个按钮,执行OnButtonQuote方法。
    ON_UPDATE_COMMAND_UI(ID_BUTTON_QUOTE, &CSouGuView::OnUpdateButtonQuote)
    ON_COMMAND(ID_BUTTON_CHART, &CSouGuView::OnButtonChart)
    ON_UPDATE_COMMAND_UI(ID_BUTTON_CHART, &CSouGuView::OnUpdateButtonChart)
END_MESSAGE_MAP()

10、当然,如果你打开了这个对话框,可以让这个按钮失效:

void CSouGuView::OnUpdateButtonHome(CCmdUI *pCmdUI)
{
    if (CurrentDialog)
    {
        pCmdUI->Enable(CurrentDialog->GetRuntimeClass()!=RUNTIME_CLASS(CHomeDialog));
    }
}

很显然,消息映射中ON_UPDATE_COMMAND_UI(ID_BUTTON_HOME, &CSouGuView::OnUpdateButtonHome),是对应这个的。

大致的意思:如果当前有对话框打开,那么:如果是CHomeDialog类的实例,这个按钮就不可用。已经打开了这个对话框,你还打开它干嘛?

11、另一个对话框也是一样,算起来我们创建了四个消息处理函数。代码就复制一下,运行,能够看到来回切换的效果就行。

12、存在的问题:

     首先,VC的对话框的布局问题,很原始。原始的意思,就是你需要自己处理。当你改变主窗体大小、或者在不同分辨率的机器上,对话框的所有控件总显示在视图的左上角,界面是很丑陋的。这是以后的问题,我最后的解决方案是:多数情况下窗体比较小,保持居中显示就行了;少数情况下,比如显示K线图的时候,需要自动适应主窗体的大小,使用一套叫做EasySize的宏解决问题。

    其次,这里虽然实现了对话框居中显示,但由于OnSize事件中没有处理,窗体大小改变的时候,对话框的位置将不再居中,这个此后再处理。

 

总结一下遇到的问题:

1、首先是所谓指针的概念:

    其实这真是幼儿园级别的问题,往下看,五分钟差不多能够弄清了:我们假设内存有1000个字节(不用说2G了,只说1k吧,好描述),从0开始给每个字节编号,最后一个是999。这个编号就是指针,很显然,指针实际上是整数,表示内存的编号,也就是内存地址。我们常见的,32位操作系统的电脑,不能利用4G内存的事情,大家可以算算,32位也就是4个字节的整数,最大值是什么。

    比如我们定义一个int 指针,怎么做?

    int *p;

    意思是我们现在有了一个int指针变量p

    然后p=0,什么意思?指向内存0的位置。

    你如果没有为指针分配空间,那么对指针的操作就是一种令人崩溃的游戏。

    虽然指到了那个地方,但那里并没有你分配的一个整型的4个字节的空间,而很可能是另一个变量的位置。

    所以第二步我们可以为其分配空间:

    p=new int;

    或者p=new int();

    这个时候,你设置断点看看,p的值已经变化了。怎么回事呢?系统将从空闲的内存中画出四个字节给你,并将这小片内存的地址:也就是第一个字节的编号传给p。系统同时会给这部分内存做上标记,继续分配空间的时候,不要再拿出来献宝,

     好吧,第三步:这片内存里的值是不可知的,为其赋值。

     *p=10;

     嗯,第四步,我想将p的值传给一个局部变量 int x;

     容易:x=*p;看看,x现在也是10了。

     注意x是局部变量,不是指针,那么背后发生了什么事情呢?p所指向的四个字节的内容,拷贝到x所占用的四个自己的内容,如此而已。

     那么第五步,我们反过来,令x=100,想让p接管x所占用的四个字节,怎么办?

     p=&x;

    这就行了,&表示x的地址,再看看p,他的值显然发生变化了,指向另一片位置了么。

    不过马上你面临第6步:

    原来p的那四个字节呢?没主了,谁都遗忘了,它还在那里,不再能够使用。内存泄漏?一个函数可能短暂时间执行几千次,这样不断失去控制、不断丢失的内存就可能膨胀起来,最终的结果大家很容易想象。内存泄漏是很多大侠不断用来表达自己功力深厚的东西,其实不复杂是不是?

    第7步,怎么避免呢?

    容易,请在改变p之前,先delete p;先不说执行析构方法之类,我们简单的理解:将p指向的内存空间还给系统,那么,虽然p指向其他地方的话,这块地方也能继续分配给其他变量。

    第8步,那么int类型的局部变量,是不是也要delete呢?不需要,堆的概念、栈的概念也不用多讲,一个函数结束的时候,所有局部变量都会自动的析构,不用我们操心。只有用手工方式分配内存的,比如new出来的,需要我们处理。我最初闹过这个笑话,自己在函数里创建一个对象,退出前自作聪明的运行其析构函数,程序运行时崩溃,因为析构方法执行了两次,第二次执行的时候那个对象已经不存在了。嗯,这种错误犯过两次,弄半天才找到原因,相当于在同一条沟沟里跌倒两次,所以我对自己的智商也委实不太自信。

    第九步:int毕竟是简单的,你创建一个类的实例,用指针指向它,一切行为没什么不同,两个区别:1、你创建的对象可能需要更多的空间;2、p->访问对象的成员,对象x则用“.”这种符号。

    需要一个月才弄清指针的概念吗?不需要,没有更多的知识了,指针的指针是什么?嗯,指向指针变量本身的指针而已;Void *是什么:指向一块不知类型的内存空间罢了。p++什么意思?指针越过其类型所占的字节,这里是加4。如果你刻意将这种概念弄得太精巧和复杂,要整出很多变态的用法,我要问问你为什么?你其实只需要这些知识就能轻松的做事,何必折磨自己的大脑。

    第十步,我是怎样弄清这些的?知道内存,知道内存地址,知道指针是存放内存地址的变量,这么三句话之后,上面的这些不是很自然的理解了吗?

    第十一步,有大牛说过,不解决内存泄漏的问题,C++无法做超大型的项目,怎么办?嗯,搜索一下“智能指针”,不过好象我们很少做超大型的项目吧?虽然这里有高手说起500万行以上代码的项目,但做那个的,我觉得和我们几乎是两个世界的人物,或者就是一帮精神病院的家伙。暂时就不要杞人忧天了,发现问题解决问题就是,仿佛一句流行的官话:“发现一起,查处一起”,当然,聪明人马上会想到:怎样才能发现?谁来监督你是否去想办法发现了?怎样查处?谁能知道是否真被查处了?查处了以后会不会马上就复出?中国语言的模糊性,注定了中国程序员的质地。

2、然后是头文件的问题:

    对于习惯C#命名空间的人来说,头文件真是很无聊的东西,太古老、太原始,Too simple,some times naive。

    我很快弄清了原因,为什么这个是需要的?.net 最终编译之后,保存了元数据,所以你使用一个dll,有反射机制可用。C++没这种东西。

    在头文件中声明,在源文件中实现。

    声明的意思是告诉编译器,我这里定义了一个类,编译器只需要知道,却不会将这些东西编译到obj或者dll中去。事实上,主要的作用,是编译器知道某个对象分配多少空间,相当于编译器使用的参数。

    源文件中include “xx.h”

    意思是将头文件全文复制到这里,然后一起交给编译器。

    在这里我闹的笑话很简单,就是void CSouGuView::OnButtonHome()

    看看这个符号“::”

    表示这个类的方法,头文件里要声明,源文件里要实现。

    如果没有这个呢?

    最初我本能的在源文件中写int xxxx;

    当然,不在任何函数内,用起来似乎也比较正常,但总是很奇怪,为什么这里改变了,在那里它也同样改变了。

   直到后来出现问题。

   甚至你这样自由的在源文件中定义不带“::”符号的函数

   似乎也可以用,但却很奇怪为什么里面的智能感知失效了,用类的成员会出现编译错误。

    噢,这种简单的问题是一周后才弄清楚的,不要笑话我,所有人都会碰到的,高手们不会讲解这种问题。   

3、代码的坏味道:

    极限编程、Scrum这类敏捷流派,耳熟能详的一句话是:“当你复制剪切的时候,就要嗅到代码的坏味道,啊,重构的时机来了。”

    看看,两个Command消息函数、两个update的消息函数,是不是都是复制的?为了重用,这里显然要重构一番是吧?对不起,我现在没工夫,就这么对付了。要知道自己的目的,时间花在主要目的上,对于重构也好、对于怎样将代码设计的更柔韧也好,我相信自己比多数人都会强一些----可是我现在在干什么?尽快实现功能,熟练掌握Vc++。重构不需要时间吗?需要的,但这个阶段不能花这种时间。这个小的应用程序,总数不超过15个窗体,几秒钟就能复制粘贴一番,估计到项目结束之后才有空去考虑这个问题。

    第二个坏味道,是View类必须知道每个对话框类的名字,当然,这也意味着有多少对话框就要include多少头文件。哇,设计模式发挥作用的地方到了,为每个类提供一个创建实例的方法,然后整个接口?创建一个对话框的基类?我说是吃饱了撑的,有闲的时候再做好不好?每个小时都很重要,你追求的是可扩展性,可是至少要等到你的产品做出来再说吧?或者今后就完全没有扩展的需要呢?或者今后接手的是个菜鸟呢?最优先考虑的肯定是如期完成项目,可扩展性服务的是程序员而不是用户,而大量使用各类设计模式,因为更抽象,所消耗的时间往往是简单实现的数倍以上,是一个工时和5个工时的区别。是否能够分清重点,是智商是否达到及格线的问题,不是技术能力问题。并不是某位高手说“我主要找写库之类的岗位”,那就更高人一等,虽然写库这种词听起来就有些别扭的味道。

    做类设计的时候,多一个类可能意味着10%以上的理解难度、开发成本的增长,所以我的目标往往首先是用最少的类实现,只在会影响到实现、或者通用性考虑不足会加大开发成本的情形下,才考虑那些稍稍抽象的东西。我当然也经历过设计模式狂热的短暂阶段,以这种实用主义、直触本质的学习方法,武器显然是不缺乏的。最明显的例子,大家可以看看通达信的股票行情软件,这样以产品吃饭的公司来说,居然存在针对不同证券公司的多种版本,对于开发团队的维护、升级来说,这绝对是一个灾难:不同的公司显然会提出一些各自特色的要求,有些可以拒绝,不能拒绝的往往是公司很关注的部分,这要做好业务分析、精心抽象,并通过通用化的设计,进行单一版本的升级来向所有客户提供同样的功能,是否使用由客户自己决定。这种多样版本并存的情形,源代码管理、开发人员是否永远在公司、自动升级服务器的部署,复杂度和工作量完全是几何级数的增长。至于应用项目,短周期的情形下,则要尽可能回避复杂化的苗头。软件开发首先是生意中的一个环节,在保证交付、稳定运行的前提下,开发和维护成本该怎样控制、如何保证如期交付、如何保证符合合同规定的质量要求,其实是团队要重点平衡拿捏的,这种观念必须贯穿于软件过程的始终。简单的说:你是在工作,不是在研究。

    所以按照前面说的,我理直气壮的忽略这些“代码的坏味道”,留待项目结束之后。

 

    再总结一下这短短几行代码,我们逐渐嗅到了些什么东西,当然,谈不上熟悉:

    1、消息循环:宏语句,不要探究细节,从字面上理解足够了。

    2、类向导:能够用工具就不要手工写了,相当于代码生成器,多用。

    3、头文件的问题:理解多数情况下,头文件仅仅是嵌入源代码的“声明部分”。

    4、调试、断点设置之类,这个没什么变化。

    5、指针:整数,记录某块内存的开始地址。

    6、文档视图机制:当作阑尾吧,留着。

    7、Ribbon界面的设计:操作问题罢了。

    8、对话框的设计、创建类、在视图中显示子对话框。

    9、this指针:和C#中一样,this指向是指向类的当前实例的指针,访问类的成员,不是我们习惯的“this.xxx”而是this->xxx,因为是指针么。创建对话框的方法中,this作为参数,意思是将目前的这个view对象的指针,作为参数传递进去。

    可能都不是特别的清晰,但不停编码的过程中,不知不觉的你的理解会一步步增强。慢慢来,最终也不会超过一个月的,我们今后会遇到std、stl、各类字符串、unicode问题、多线程问题、Directx Api,发现一起查处一起,这项功能做完,由任务的驱动会逼着我们逐一弄清所有这些语法细节,这样比你主动的去看书强吧?到时候象我这样写几篇博客总结一下,思路能够整理得更为清晰、众多有如牛毛或者过江之鲫的高手们如果肯指出理解上的问题的话,又能够更上层楼,何乐而不为?

 

略微解释几句:

    这个系列,由于是面向初学者的东西,主要是希望提供一个学习的路径图,因此内容相当的初级,也不会太过详细。这里还是希望高手们要么当作游戏之作,要么带点容忍异己的心态。当然,我说起高手,一般是当贬义词用的,这是习惯,很难改变。同时因为时间的关系,一篇帖子往往用不了半个小时,两分钟写个提纲,然后顺着往下胡侃,因此错漏之处必定也有很多,就内容挑毛病我是很欢迎的,也会尽量修改。至于对我的人品挑毛病的话,就看兴趣了:兴致高的时候我会争锋相对,练练嘴皮子,体验斗嘴的乐趣,对心态保持年青、养颜防老还是很有好处的。兴致不高的时候直接忽略,所以还请各位骂手体谅,不予还击并不是不尊重你的劳动,当然也不是看不到您鼻孔朝天一脸不屑的模样,而是此人正在沮丧中……非洲还有许多孩子没有校车呢。

    关于侯捷,嗯,我还是强调一下:这个人真的很无聊。他的粉丝众多,也掩盖不了这种无聊。你可以喜欢他“滴人”,我也可以不喜欢他的方法,我不能勉强你,也希望你不要逼着我和你一起做粉丝,既然都那么博大精深,我也期待你们成为星星本身,呃,通过追星这种方式?偷偷说一句:我唯一喜欢的是他的表达方式,这是个人好恶的问题,无关其他。

    继续预告:下一篇将将讲述怎样设计“导入日线对话框”的界面,如何获取对话框控件的值、处理相应的消息。

转载于:https://www.cnblogs.com/by1990/archive/2011/12/06/2277463.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值