MFC入门指导—图像滚动刷新并克服屏幕闪烁

http://wenku.baidu.com/view/14407a10a216147917112885.html/

MFC入门指导

2011-3

说明

       本文从零讲起,内容简单,旨在帮助MFC零基础的同学快速上手,可以短时间内做出简单的可视化界面。工程环境:Visual C++ 6.0;语言基础:C++。

 

1.        怎样创建MFC工程

打开VC6,新建工程,选择MFC AppWizard(exe),点击OK。

                           

图 1 新建MFC工程

       下面选择工程类型,分为三种:单文档、多文档、对话框。我们使用单文档及对话框较多。一般情况下,单文档类处理文字、绘图较多,对话框类更多是通过控件与用户交互,两者亦互通,没有绝对的选择规则。

图 2 选择工程类型

       一路点击Next,直到如图3所示界面,最后一项选择MFC库的连接方式。默认为动态连接,这样做出的可执行文件较小,但发布时需要附带MFC库,或者需要在装有MFC库的机器上运行。也可以选择静态连接,这样的可执行文件较大,但对运行环境没有要求。对于较小的工程,推荐使用静态连接。选择完毕后,直接点击Finish,完成工程创建。

图 3 选择MFC库连接方式

 

2.        主函数在哪里

MFC工程不像控制台工程那样,有一个明确的主函数,作为程序的入口。MFC的运行是基于消息响应机制的。形象的说,一个MFC程序运行起来,完成了一系列初始化工作后,就静静的等在那里,等待用户的动作(譬如按下按钮,选择菜单项)。我们不需要追究代码运行的源头,只需要为相应的操作写对应的代码即可。

 

3.        在哪里添加自定义变量

首先要了解MFC自动生成了什么。如图4所示,为对话框和单文档自动生成的几个类。对话框中的C***Dlg类以及单文档的C***View类是我们添加代码较多的类,我们要重载的响应函数大多数都在这两个类中,所以可以将自定义变量写成他们的成员变量,既不影响使用的方便性,又保证了良好的封装性。

如果需要建立较复杂的数据结构,如队列、二叉树、链表等,建议单独自定义类,再将头文件添加到Dlg或View类中,声明其对象为成员变量。

图 4 MFC自动生成的类

4.        变量的初始化在哪里完成

对于单文档类,变量的初始化工作可以放在构造函数C***View::C***View()中;对于对话框类,初始化应在初始化函数BOOL C***Dlg::OnInitDialog()中完成。

 

5.        如何添加菜单

以单文档为例,找到Resouce中的Menu文件夹,双击里面的菜单对象,进入菜单编辑界面。双击空白菜单,可以新建母菜单栏,之后可以新建菜单项,注意菜单的ID要起好名字,以免写响应函数时混淆不清。

图 5 编辑菜单栏

图 6 添加菜单项

       点击View->ClassWizard,或者Ctrl+W(强烈建议大家使用这个快捷键),打开ClassWizard。ClassWizard是VC6集各种强大功能于一身的特色工具,一定要熟练应用。在Message Maps中的Object IDs框内,选中刚刚建立的菜单项,再在Messages框内选中COMMAND,即鼠标点击后的消息响应,点击Add Function,MFC将自动添加这个响应函数,再点击Edit Code,即可跳到函数中编写代码了。

       这里需要注意的是,要选好响应函数所在的类。函数生成到哪个类中都可以顺利运行,但一般我们集中放在View类中,方便共享View类中自定义的成员变量。

图 7 用ClassWizard添加消息响应函数

 

6.        如何用简单的对话框做数据输入

首先要创建新的对话框,并设计界面。在Resouce中,右键单击Dialog文件夹,选择InsertDialog,完成新对话框的创建。默认的对话框界面只包含两个按钮,OK和Cancel,保留他们不动。添加一个Edit Box控件,用来接受键盘输入的数据,一个Static Text控件做提示。分别修改两个控件的属性,Static控件只需修改Caption属性,Edit控件建议修改控件ID,方便程序阅读和管理。

图 8 添加控件并修改ID

       Ctrl+W打开Class Wizard,会弹出提示是否创建新的类,点击OK,输入类名后再OK,完成对话框类的创建。在Class Wizard中,选择Member Variables选项卡,找到刚刚建立的Edit控件ID,点Add Variable,为控件添加变量。变量添加完毕后,退出ClassWizard。

图 9 为控件添加变量

       建立变量后,Edit控件中输入的数值就与变量建立了连接,但并不是实时更新的,需要手动更新变量。更新通过UpdateData()来完成,默认参数为true,表示将Edit控件中的数值传递给控件的变量,参数为false则相反,用变量修改控件显示的数值。数据的更新可以放在OK按钮响应函数中,双击OK按钮重载OnOK函数,添加代码即可。

void CInputDlg::OnOK()
{
       // TODO: Add extravalidation here
       UpdateData();        //更新数据
 
       CDialog::OnOK();
}


对话框功能设计完成后,剩下的工作就是如何调用并在主调函数中获取键盘输入的数值。首先声明一个对话框类的对象,并通过成员函数DoModal()显示对话框。示意代码如下:

CInputDlg dlg;
if(dlg.DoModal()==IDOK){
       CString str;
       str.Format("输入的数据是%d", dlg.m_edit_data);
       MessageBox(str);
}


       这里只举例讲解了Edit控件的使用方法,还有多种控件,使用起来大同小异,可以查阅其他资料自行学习。

 

 

7.        如何画图

建议自定义的绘图代码加入到系统重绘响应函数中,这样保证了系统自动重绘的时候,我们画的图不会被漏掉。单文档类,可以写在OnDraw函数中,对话框类,可以写在OnPaint函数中。下面以对话框为例,举几个简单的例子。

对话框中OnPaint函数,自定义的代码写在原代码的else部分。常用的设备类有CPen、CBrush、CFont等。设备的创建函数往往有多种重载方式供大家选择,根据需要选择一种使用即可。下面给一段示例,绘制一个矩形一条直线以及一段文字。

CPaintDC dc(this); //获取绘图DC
CPen pen;
pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));      //创建线宽为1,黑色的实线画笔
CBrush brush;
brush.CreateSolidBrush(RGB(100, 100, 100));    //创建灰色的画刷
CFont font;
font.CreatePointFont(400, "Arial");                   //创建Arial点数为400的字体
 
dc.SelectObject(&pen);         //将画笔选入设备
dc.SelectObject(&brush);      //将画刷选入设备
dc.SelectObject(&font);        //将字体选入设备
dc.SetBkMode(TRANSPARENT);       //设置字体背景透明
 
//绘制矩形
dc.Rectangle(50, 50, 400, 200);
//绘制直线
dc.MoveTo(30, 20);
dc.LineTo(420, 230);
//写字
dc.TextOut(110, 20, "hello MFC!");
 
pen.DeleteObject();
brush.DeleteObject();
font.DeleteObject();


图 10 一个简单的示例

       绘图代码写在OnPaint之后,系统只会在必要的时候调用刷新(比如移动窗口、窗口最小化后再打开等),如果需要手动刷新,可以使用函数Invalidate()进行强制重绘。默认参数为true,系统自动擦除背景后重绘,参数为false时,不会自动擦除背景。如果对刷新速度有较高要求,且不需求擦除背景的时候可以选择false。

 

8.        怎样解决高速刷新时屏幕闪烁的问题

上一段中使用Invalidate(false)强制重绘是一种简单的解决办法,省掉了系统擦除并重绘背景的时间。下面再从绘图时间入手,介绍一种非常常用的双缓冲技术。

所谓双缓冲,就是首先在内存中建立一个DC,然后将要绘制的图形先绘制到内存DC中,最后一次性从内存中拷贝出来到当前DC,可以有效的解决闪屏问题。以对话框为例,给出一段参考代码。

CPaintDC dc(this); //获取绘图DC
 
// 创建内存缓存DC
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CBitmap bufferMemBitmap;
CRect rect;
GetClientRect(&rect);
bufferMemBitmap.CreateCompatibleBitmap(&dc, rect.Width(),rect.Height());
memDC.SelectObject(&bufferMemBitmap);
CBrush whiteBrush;
whiteBrush.CreateSolidBrush(RGB(255, 255, 255));
memDC.FillRect(CRect(0, 0, rect.Width(), rect.Height()),&whiteBrush);
 
// 使用memDC做绘图操作
 
// 将内存缓冲DC拷贝至当前DC
dc.BitBlt(rect.left, rect.top, rect.Width(), rect.Height(),&memDC, 0, 0, SRCCOPY);
bufferMemBitmap.DeleteObject();
memDC.DeleteDC();


 

9.        怎样添加鼠标键盘响应

打开ClassWizard,在Messages框中可以找到所有的鼠标键盘消息响应。譬如鼠标移动WM_MOUSEMOVE,左键按下WM_LBUTTONDOWN,键盘按键按下WM_KEYDOWN,键盘字符消息WM_CHAR等,选中要添加的响应,点击Add Function,进入编写代码即可。    举个简单的例子,做一个跟随鼠标运动的空心圆。首先在dlg类头文件中添加成员变量m_mousePos。

CPoint m_mousePos;


       添加鼠标移动响应,记录当前鼠标的位置。

void CDlgTestDlg::OnMouseMove(UINT nFlags, CPoint point)
{
       // TODO: Add yourmessage handler code here and/or call default
       m_mousePos = point;
       Invalidate(false);
       CDialog::OnMouseMove(nFlags,point);
}


       由于鼠标移动是高速刷新,为避免闪屏,强制重绘不擦除背景,且在OnPaint中使用双缓冲技术,并在绘图部分添加下述代码:

memDC.Ellipse(m_mousePos.x-50,m_mousePos.y-50,m_mousePos.x+50,m_mousePos.y+50);


       这样就做出一个随鼠标移动,半径为50的圆了。

 

10.    怎样固定频率刷新图像

设定一个定时器,就可以每隔固定时间给出中断,进行对图像数据的更新,达到刷新图像的目的。开启定时器的函数为SetTimer,关闭定时器的函数为KillTimer。定时器消息WM_TIMER需要在ClassWizard中添加响应函数OnTimer。

举个简单的例子,从程序开始运行起,每1s刷新一次图像。

首先在OnInitDialog()函数中开启定时器。

SetTimer(1001, 1000, NULL);      //开启定时器,ID为1001,中断间隔1000ms

       打开ClassWizard,找到消息WM_TIMER,点Add Function,重载响应函数OnTimer。

void CDlgTestDlg::OnTimer(UINT nIDEvent)
{
       // TODO: Add yourmessage handler code here and/or call default
       switch(nIDEvent){
       case 1001:      //ID为1001的定时器代码部分
              {
                     //添加图像数据更新代码
 
                     //强制重绘
                     Invalidate(false);
                     break;
              }
       default:
              break;
       }
       CDialog::OnTimer(nIDEvent);
}


 

小结

       MFC入门简单,精通难,本文针对零基础的同学,列举了一些非常简单的操作。能够学习并合理运用上述知识点,已经可以做出简单的可视化人机交互界面。作者能力有限,所写不多,更深一步的学习,还需要多借助MSDN、参考书籍、网上资料的帮助。



你可能感兴趣的:(MFC入门指导—图像滚动刷新并克服屏幕闪烁)