1.新建工程.参考第一节的方法新建一个工程,名字为Eg02完成后如下图
图片1 (原文件名:01.jpg)细心的朋友一定会发现.新建的工程里还有一个对话框,ID名是IDD_ABOUTBOX这个是做什么用的呢?我们用到的软件都会有一个版权声明.通过第一章的学习,大家应该知道怎么观看这个IDD_ABOUTBOX对话框了吧.没错!双击IDD_ABOUTBOX就可以了.我们会看到如下的一个对话框
图片2 (原文件名:02.jpg)这就是我们这个程序的关于对话框,一般用于版权声明及版本号标识.大家看到的这个对话框里有两个静态文本框和一个图像框(Picture),静态文本框我们在前一节已经介绍过了.大家可以修改一下版权所有这一行,填什么都可以,签个大名也行.完成以后你一定想看看效果,这个对话框怎么打开呢?其实VC已经为我们做好了.先按F7编译,然后按F5运行.大家可以看到程序运行了.
图片3 (原文件名:03.jpg)单击应用程序图标,就会出现一个菜单,选最后一个[关于Eg02],关于对话框就弹出来了. 当然,这是系统为我们做好的.自己怎么在程序中调用这个对话框呢?为了演示,首先参考第一节的内容添加一个按钮,然后把按钮的ID改为IDC_BTN_ABOUTME,把标题,也就是Caption改为[关于].最终效果如下
图片4 (原文件名:04.jpg)下面我们为按钮添加代码.相信大家一定还记得怎么进入代码吧..对了,双击[关于]按钮,在弹出的对话框中点[确定]就可以了.为了让大家更好的理解下面的操作,我们先要解释一下关于对话框的类.VC向导会为关于对话框建立一个类,大家看看下面的图
图片5 (原文件名:05.jpg)单击标签ClassView(这里显示的是[Class…])就可以看到Eg02这个应用程序的类.第一个CAboutDlg就是关于对话框的类.CEg02Dlg对应IDD_EG02_DIALOG.中间的Ceg02App是应用程序的基础类.所以,如果要对关于对话框进行操作,就要用到类CAboutDlg,因为与此有关的函数及变量都封装在CAboutDlg中.看到这里大家可能又糊涂了,没关系,在以后的教程中,通过一些练习大家就会慢慢领会到的.这里还是先为[关于]按钮添加代码. void CEg02Dlg::OnBtnAboutme() { // TODO: Add your control notification handler code here } 上面是VC为[关于]按钮添加的响应函数.我们添加代码成以下所示 void CEg02Dlg::OnBtnAboutme() { // TODO: Add your control notification handler code here CAboutDlg ADlg; ADlg.DoModal(); } 一共有两句,第一句是CAboutDlg ADlg;作用是定义一个变量Adlg.第二句是ADlg.DoModal();功能是调用类CAboutDlg里的一个函数DoModal();这个函数在MSDN里的解释是Call this member function to invoke the modal dialog box and return the dialog-box result when done. This member function handles all interaction with the user while the dialog box is active. This is what makes the dialog box modal; that is, the user cannot interact with other windows until the dialog box is closed.一般我们用于显示一个对话框.其实大家看看CAboutDlg这个类下面,只有两个函数
图片6 (原文件名:06.jpg)DoModal()这个函数并不在这个里面.第一章我们提到过类的派生和继承.其实CAboutDlg这个类是派生于CDialog类,DoModal()这个函数是CDialog的成员函数,由于CAboutDlg是继承父类CDialog的,所以CDialog里的函数在CAboutDlg中也可以使用. 下面我们来说说几个常用控件的使用. 首先在IDD_EG02_DIALOG对话框中加入一个Edit(编辑框)控件.Edit一般用于输入输出数据文本.相当于VB里的TextBox.加入Edit控件后,编辑其属性为
图片7 (原文件名:07.jpg)然后,我们再加一个按钮(PushButton),并编辑其属性为
图片8 (原文件名:08.jpg)接下来,我们先说一下要实现的效果.很简单,在编辑框里输入一个文本,然后按显示,就把文本显示在静态文本框中.所以,这里要把静态文本编辑框的ID改为IDC_DISPLABEL 下面我们为[显示]按钮添加代码 void CEg02Dlg::OnBtnShow() { // TODO: Add your control notification handler code here CString a; GetDlgItemText(IDC_EDIT_INPUT,a); SetDlgItemText(IDC_DISPLABEL,a); } 其实不复杂,也只有三句,第一句定义一个CString类变量a 我们来说说GetDlgItemText这个函数吧.查查MSDN就知道函数原型了. int GetDlgItemText( int nID, LPTSTR lpStr, int nMaxCount ) const; int GetDlgItemText( int nID, CString& rString ) const; 大家看看就觉得奇怪了,怎么有两个原型啊?并且一个是传两个参数,另一个是传三个参数.在VC里面,同一个类下是可以存在多个同名函数的,具体调用哪个函数要看参数的不同.在这里我们传入了两个参数,所以VC会调用int GetDlgItemText( int nID, CString& rString ) const;这个函数.第一个函数是控件的ID号,第二个是字串.第二个传了地址,所以我们在下一句中用的a已经是获得IDC_EDIT_INPUT的文本了.运行效果如下
图片9 (原文件名:09.jpg)下面介绍一下进度条的使用以及定时器的使用. 我们要实现的效果是进度从0到满格,然后再从0到满格,依次循环.每跳一格间隔500ms,这个时间我们用定时器来实现. 首先从控件条里拖出一个进程条到对话框,修改属性如下
图片10 (原文件名:10.jpg)然后我们要介绍一下VC的定时器.VC里面使用定时器有多种方式,我们先介绍一种作为抛砖引玉 首先添加一个Windows消息处理器.消息这个词语可能很陌生,我们会在后面很多次说明.这里先照图做
图片11 (原文件名:11.jpg)在类管理器里选中Ceg02Dlg这个类,然后点右键,就会弹出一个菜单,选择[Add Windows Message Handler…],接下来会弹出另一个菜单
图片12 (原文件名:12.jpg)双击WM_TIMER然后按[确定]就可以了.大家会看到,CEg02Dlg类中多了一个函数
图13 (原文件名:13.jpg)这个就是VC中的定时器响应函数.然后双击这个函数就进入代码了
图14 (原文件名:14.jpg)然后我们为Timer事件添加代码. void CEg02Dlg::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default IDC_PROGRESS static int nPos=0; ((CProgressCtrl*)(GetDlgItem(IDC_PROGRESS)))->SetPos(nPos); if(nPos<100) nPos+=10; else nPos=0; CDialog::OnTimer(nIDEvent); } 首先定义一个整型的变量nPos用于记录进度条的进度值.默认时,进度条0为空,100为满格.从后的程序大家可以看到,这个变量自加到100就变为0.最难理解的就是 ((CProgressCtrl*)(GetDlgItem(IDC_PROGRESS)))->SetPos(nPos); 首先, GetDlgItem(IDC_PROGRESS)这个函数用来获取IDC_PROGRESS的句柄,在VC里面引入了句柄这个词语,我们将在下一章中对消息和句柄进行详细的说明, 句柄是WINDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。在这里,GetDlgItem获取了窗体句柄,大家应该还记得,在VC里面,控制就看成窗体。(CProgressCtrl*)这个地方是将返回的句柄强制转换为CProgressCtrl类型,与C语言的中的强制转换是一样的.进度条的控制类是CprogressCtrl.而前面返回的是一个窗体类型,所以先要强制转换.在第一章中,我们提到过,VC中的控件都认为是窗体,在这里就体现出来了. SetPos(nPos);这个函数是类CprogressCtrl的成员函数,用来指定当前进度条的进度.最后还有一步,就是激活这个定时器.像我们的C51或AVR一样,要初始化定时器. 而void CEg02Dlg::OnTimer(UINT nIDEvent)这个函数就像我们单片机的定时器中断服务函数一样.时间到了就会自动执行. 参考上面的图,双击OnInitDialog(),就可以进入对话框初始化函数,只要添加一句就可以了.完成后如下 BOOL CEg02Dlg::OnInitDialog() { CDialog::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here SetTimer(0,500,NULL); //这里是添加的,别的都是自动生成的 return TRUE; // return TRUE unless you set the focus to a control } 在这里我们只添加SetTimer(0,500,NULL);其中,参数0代表定时器的ID号为0,.第二个参数500是定时器的时间,单位为ms,后面的NULL是指不要回调函数. 按F7编译后运行就可以看到运行效果了.
图15 (原文件名:15.jpg)或许大家有个问题,为什么开始时是添加了一个WM_TIMER的消息处理器呢?我们使用SetTimer开始定时器后,如果没有回调函数,系统会在每次定时时间到后发送一个WM_TIMER消息到窗体.窗体收到这个信息后,调用OnTimer()函数进行处理.系统定义这个函数为afx_msg void OnTimer(UINT nIDEvent);可以看到是以afx_msg修饰的,这种函数会与其中一个信息关联,或者说是消息影射到这个函数.每当有消息发过来,都会执行这个函数.大家只要好好想想单片机的定时器中断就会明白的,原理一样,只是传输机制不同. 如果上面的内容你制作成功了,那么恭喜你,第二节就基本学完了. 下面我们也来说说多任务和消息机制吧. Windows是基于消息机制的,它是一个多任务的操作系统,也就是说,同一时间内,系统会挂起多个任务.为了说明多任务,我们先来看一段单片机程序. Void main(void) { While(1) { TaskA(); } } 这个程序很简单,单片机工作后就进入while()循环了,单片机这个时候就干一件事,那就是执行任务TaskA.这样的工作总是在一个主循环内实施,一次只执行一个任务的我们称为单任务系统.单片机程序只要不引入操作系统并且由一个主循环一直执行完毕的基本都是单任务的.有些程序也是这样写的. Void main(void) { While(1) { TaskA(); TaskB(); TaskC(); } } 这样的程序看起来似乎是有三个任务了.这三个任务是顺序执行的,也就是说,必须让A完成后,才能到B,B完成后才能到C.如果A有一个长时间的延时,系统就会在A中空等,然而B与A本来是无关的,这样空等的时间就算是浪费了.如果我们的windows也是顺序执行的就麻烦了,那时我们不仅仅会说Windows有点慢,而是说Windows像蜗牛一样在爬.如果我们让这些任务更合理的安排一下,在执行A的时候,有空就去执行B,而在B的空闲时间去执行C或者A,那么时间就节省下来了.如果时间切换够快,那么我们可以认为A,B,C三个任务在同时进行.所以,如果我们把单片机的执行时间分成若干等份,每份1ms或者更小,这种时间等份我们称为时间片,每次时间到了就换一个任务.也就是说,第一毫秒执行A,这时我们并不等A全部执行完,并记住这个断点,到了第二毫秒执行B,第三毫秒执行C,第四毫秒又执行A并从原来的断点开始执行,依次直至三个任务都完成.大家可以看到,任务A每三毫秒执行了一次.这就是多任务的模型了. Windows其实就是这么干的.只是每次任务时间不一定是我们上面说的3ms,因为系统同一时间内可能会有很多待执行的任务,这些任务就被系统按优先级排成队,一个个取出来执行.比如我们现在打开VC,系统在加载VC,同时我们还可以移动鼠标,这些好像都是在同时进行,其实它们也是按时间片分时执行的. 现在分析一下这个工作过程吧.现在我们要打开VC,系统开始加载VC了,可以看到VC的LOGO界面,但这个时候我们还可以动一下鼠标,硬件首先会响应,并给软件发个通知,而软件这个时候可能还没有轮到鼠标程序的执行,怎么办呢?系统会先把这个鼠标操作保存起来,轮到鼠标时间片时再来处理鼠标操作.大家可能觉得,这样鼠标操作不是滞后了吗?确实是这样的,但这个滞后时间是很短的,我们基本不会察觉出来. 我们来看看消息的定义. 消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。 消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。 综合前面的分析,消息就好理解了,鼠标动一下会产生一个事件,这个事件必须要告诉系统啊,怎么告诉系统呢?Windows把这个定义为Message也就是消息,所以,消息可以认为是一个操作记号,记录上一步所发生的或者说是下一步所要处理的事件.系统为消息安排了一个队列,里面可以存放很多个消息,我们称为消息队列.有键盘操作时,把键盘操作放到消息队列里面,相当于做一个记号,我们动了键盘,有鼠标操作时,把鼠标操作放到消息队列,也便记住我们在键盘后还动了鼠标,系统就把这些东西一个个按时间取出来执行.在Windows里面,鼠标消息可以有几种,例如单击落了左键,系统会产生一个消息为WM_LBUTTONDOWN如果我们移动了鼠标,就像产生一个消息为WM_MOUSEMOVE消息入队后,系统会依次处理.当然,我们这样的解释虽然容易懂,但不是非常专业,并且Windows内部处理消息也比这复杂多了,但基本原理就是这样的了,并且,我们在进行操作时,并不用关心Windows的底层是怎么处理这些事件的,除非我们的操作真的让Windows生气了,就必须去查查出什么问题了. 说了这么多,消息可能还是一个十分飘渺的概念,倒底要怎么用呢?我们来举个例子说明一下.给系统发消息一般会用到两个函数,一个是SendMessage,另一个是PostMessage,这两个是有点区别的,具体区别作为这次的作品,大家去网上查查.例如我们要退出当前的应用程序,最简单的方法当然是按一下关闭的那个红X,如果我们要自己让程序退出,可以给系统发一个消息,其实那个关闭程序的红X也是这么干的.为了演示,首先在上次的对话框中加一个按钮,修改标题也就是Caption为”关闭程序”,ID号改为IDC_QUIT_ME双击为按钮添加以下代码. PostMessage(WM_CLOSE); 编译运行试试,如果没有写错代码就可以用那个按钮退出程序了. 这一节也就写到这里了,可以大家还是有点糊涂,慢慢体会一下就好了.还是不明白可以来这里提问.