找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2938|回复: 0
打印 上一主题 下一主题
收起左侧

MFC相关理解

[复制链接]
跳转到指定楼层
楼主
ID:60266 发表于 2014-8-18 01:10 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
                        为了进行绘图操作,必须获得一个设备描述表(或设备上下文)DC,当构造了一个GDI对象后,该对象并不会立即生效,必须选入设备描述表DC,它才会在以后的绘制操作中生效;利用SelectObject函数可以实现把GDI对象选入设备描述表DC中,并且该函数会返回指向先前被选对象的指针,这个指针主要是为了在完成当前的绘制操作后,还原设备描述表用的。一般情况下,在完成绘图操作后,要利SelectObject函数把先前的GDI对象选入设备描述表,以便使其恢复到先前的状态;
GDI  图形设备接口(GDI:Graphics DeviceInterface)是Windows的子系统,它负责在视讯显示器和打印机上显示图形。正如您所认为的那样,GDI是Windows非常重要的部分。不只您为Windows编写的应用系统在显示视觉信息时使用GDI,就连Windows本身也使用GDI来显示使用者接口对象,诸如菜单、滚动条、图标和鼠标光标。
GDI是C++中常用的一种图形图像工具,VB、Delphi等语言也移植了GDI ,.net的诸多语言甚至完全采用GDI作为绘制其界面。在Windows系统下,几乎所有的API都是直接针对C语言的,除C/C++可直接使用这些API外,其它各种语言都必须移植这些API方法,而唯独GDI,微软提供的Gdiplus头文件,从类型定义到方法接口,都是针对C++写的,这就使得C语言没法使用这些面向对象的方法,即使是GDI提供的原始API,如果不重新定义其参数中众多的数据类型,C语言也是没法使用的。
也许是因为C语言不适合编写Windows界面应用程序,所以至今没有发现GDI的C版本(可能有,但没发现)。GDI没C版本的一个可能的原因是:C版本中的众多的重载函数,缺省参数,用C来写却是头都大了,不知道用什么函数名才好(考虑GDI使用了这些年,新的函数名不应与原函数名偏离太远)。

void CDrawView::OnLButtonUp(UINT nFlags,  CPoint point){    CPen pen(PS_SOLID, 1,  RGB(255,0,0));    CClientDC  dc(this);    CPen  *pOldPen  = dc.SelectObject(&pen);    dc.MoveTo(m_ptOrigin);   dc.LineTo(point);   dc.SelectObject(pOldPen);   CView::OnLButtonUp(nFlags, point);}
void CDrawView::OnLButtonUp( UINT nFlags, CPoint point){    HDC hdc;   hdc = ::GetDC(m_hWnd);//获取该窗口的设备描述表(上下文)   MoveToEx(hdc, m_ptOrigin.x,  m_ptOrigin.y, NULL);//移动到线条的起点  LineTo(hdc, point.x, point.y); //通过hdc画线 ::ReleaseDC(m_hWnd, hdc);//用后一定要释放,因为GetDC里面是申请了资源的}
在MFC中,所有的控件类都是由CWnd类派生的,因此控件实际上也是窗口;实际上,控件通常是作为对话框的子窗口而创建的。对话框的种类:对话框可分为:1.模态(Modal)对话框 2.非模态对话框(Modeless)模态(Modal)对话框:模态对话框是指当其显示时,程序会暂停执行,直到关闭这个模态对话框后,才能继续执行程序中其他人;模态对话框垄断了用户的输入,当一个模态对话框打开时,用户只能与该对话框进行交互,而其他用户界面对象接收不到输入信息;我们平时所遇到的大部分对话框都是模态对话框;非模态对话框(Modeless):当非模态对话框(Modeless)显示时,允许转而执行程序中其他的任务,而不用关闭这个对话框。典型的例子是Windows提供的记事本程序中的“查找”对话框,该对话框不会垄断用户的输入,打开“查找”对话框后,仍可以与其他用户界面对象进行交互,用户可以一边查找,一边修改文章,这样就大大方便了使用。
MFC为我们提供一个设备描述表的封装类CDC,该类封装了所有与绘图相关的操作;该类提供了一个数据成员m_hDC,用来保存与CDC类相关的DC句柄。CDC是MFC的DC【设备上下文/设备描述表】的一个类;CDC中所有MFC的DC的基类.常用的CClientDC dc(this);就是CDC的子类(或称派生类).CDC等设备上下分类,都含有一个类的成员变量:m_nHdc;即HDC类型的句柄.记住下面的一句话,会有助于你的理解:MFC的类,是在用window API语句开发出来的有一定功能的小程序.(也可称为类).使用它的默认方法,就是,记住它的名字与参数(可以用笔记,代替脑记).DC(设备上下文):设备上下文是一种包含有关某个设备(如显示器或打印机)的绘制属性信息的 Windows数据结构。所有绘制调用都通过设备上下文对象进行,这些对象封装了用于绘制线条、形状和文本的 Windows API。设备上下文允许在Windows 中进行与设备无关的绘制。设备上下文可用于绘制到屏幕、打印机或者图元文件。

设备上下文是一种包含有关某个设备(如显示器或打印机)的绘制属性信息的Windows 数据结构。所有绘制调用都通过设备上下文对象进行,这些对象封装了用于绘制线条、形状和文本的Windows API。设备上下文允许在 Windows 中进行与设备无关的绘制。设备上下文可用于绘制到屏幕、打印机或者图元文件。
CPaintDC 对象将 Windows 的常见固定用语进行封装,调用 BeginPaint 函数,然后在设备上下文中绘制,最后调用EndPaint 函数。CPaintDC 构造函数为您调用BeginPaint,析构函数则调用EndPaint。该简化过程将创建 CDC 对象、绘制和销毁 CDC 对象。在框架中,甚至连这个过程的大部分也是自动的。具体说来,框架给OnDraw 函数传递(通过 OnPrepareDC)准备好的CPaintDC,您只需绘制到 CPaintDC 中。根据调用 OnDraw 函数的返回,CPaintDC 被框架销毁并且将基础设备上下文释放给Windows。
CClientDC对象封装对一个只表示窗口工作区的设备上下文的处理。CClientDC 构造函数调用GetDC 函数,析构函数调用 ReleaseDC函数。CWindowDC 对象封装表示整个窗口(包括其框架)的设备上下文。
CMetaFileDC 对象将绘制封装到 Windows 图元文件中。与传递给OnDraw 的 CPaintDC 相反,在这种情况下您必须自己调用 OnPrepareDC。
设备上下文(Device Context)DC
DC实际上是GDI内部保存的数据结构。
DC与特定的显示设备(如显示器或打印机)相关。
对于显示器,DC总是与显示器上的特定视窗相关。
DC中的有些值是图形「属性」,这些属性定义了GDI绘图函数工作的细节。
例如,对於TextOut,DC的属性确定了文字的颜色、文字的背景色、x坐标和y坐标映射到视窗的显示区域的方式,以及显示文字时Windows使用的字体。
MSDN的解释:一个DC是一个结构,它定义了一系列图形对象的集合以及它们相关的属性,以及影响输出效果的一些图形模式。这些图形对象包括一个画线的笔,一个填充和painting的画刷,一个用来向屏幕拷贝的位图,一个定义了一系列颜色集合的调色板,一个用来剪裁等操作的区域,一个做painting和drawing操作的路径。
一个应用程序从不直接地访问(access)dc,常见的取得dc的方式有以下几种:
SDK's way:
1. BeginPaint
case WM_PAINT: HDC hdc = BeginPaint(hwnd, &ps);EndPaint(hwnd, &ps);
MSDN的解释: BeginPaint函数自动地设置dc的剪裁区域,这个剪裁区域,剪裁的是由InvalidateRect或 InvalidateRgn 函数触发的窗口无效区域,或者是系统给出的无效区域,当窗口被sizing,moving, creating, scrolling, or any other operation that affectsthe client area.
一个应用程序从不调用BeginPaint,除了在收到一个WM_PAINT消息的时候;每一BeginPaint调用之后,需要调用EndPaint函数。
2.GetXXXDC
GetDC取得与窗口客户区相关的dc,GetWindowDC取得与整个窗口(包括客户区和非客户区)相关的dc。
还有一类重要的dc,内存DC,是一个虚拟的内存设备上下文,我们对它进行绘图等操作,不会显示在屏幕或打印机上,而我们可以在它完成之后,拷贝到屏幕上或打印机上来输出,这样我们可以避免因为操作而给屏幕带来的闪烁,对于打印机而言,打印只能是从上往下打,而我们在MEMDC中,可以随意进行操作,这样可以输出直接在打印机上输出所达不到的效果.
在窗口上贴图一般总是要用到内存DC,将所有的绘制工作先绘制在内存DC上,然活一次性拷贝到屏幕DC上,就是这样了。
这里是使用mfc进行的说明,对hdc进行了封装,但是道理是一样的。
1.创建内存DC
CDC m_MenDC; //声明内存DC
CDC m_MenDC2; //声明内存DC
CBitmap m_Bitmap1; //声明一个位图
m_MenDC.CreateCompatibleDC(GetDC());//创建内存DC
m_MenDCMap.CreateCompatibleDC(GetDC());//创建内存DC
m_Bitmap1.CreateCompatibleBitmap(GetDC(),1024,768);//创建一个兼容位图,这是一个空的位图,我们可以把它想象成一个屏幕,可以在上面画线,输出文字等,自己制作一个简单的位图。
m_hbmpBK =(HBITMAP)::LoadImage(AfxGetInstanceHandle(),path+"Bk4.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
//我们也可以从硬盘导入一张位图。
2,为内存DC选入一张位图,或兼容位图。
m_MenDC.SelectObject(m_hbmpBK);
m_MenDC2.SelectObject(m_Bitmap1);
注意,想要在内存dc上作图,必须先为它选入一张位图,或兼容位图。想想前面,不管BeginPaint还是getDC都有一个相关的区域。这里的位图就是相关区域。
3.接着我们就可以像在窗口上作图一样,使用gdi函数(drawline,textout...)在内存dc上作图了。同时也可以从一个内存dc拷贝位图到另一个内存dc。
m_MenDC2.BitBlt(0,0,1024,768,&m_MenDC,0,0,SRCCOPY);
4,绘制结果的显示,将这些东西拷到屏幕DC(getdc取得的dc)上
// 所谓的双缓冲就是把所有的绘制工作都做在一个内存DC上。
// 最后一次拷到屏幕DC上,只能有一次
dc.BitBlt(0,0,1024,768,&m_MenDC2,0,0,SRCCOPY);//这里的dc是通过getdc取得的屏幕或者某个窗口的dc。
这里所强调的“一次”;是不要连续将几个内存DC的内容都拷到屏幕DC上,这样没有起到双缓冲的效果。如果你搞了很多个内存DC,想把这些东西都显示出来,那你应该先把这多个内存DC的内容同时拷到另外一个内存DC上,再把这个内存DC的内容拷到屏幕DC上。

CPaintDC dc(this);MFC的CPaintDC类是从MFC的CDC类派生的,CDC类封装了Windows设备环境,以及包含了绘制到屏幕、打印机和其他设备的几十个成员函数
在MFC程序中,我们并不经常直接调用Windows API,而是从MFC类创建对象并调用属于这些对象的成员函数.也就是说MFC封装了Windows API 你说你喜欢C++而MFC换一种说法就是一个用C++写的一个函数库 然后你来调用 只不过这个类不是你写的
           MFC提供数百个类,最重要的、也是编写任何VC++应用程序都必不可少的两个类CWinApp和CFrameWnd,这两个类是编写复杂庞大应用程序的基石。
1>封装特性:构成MFC框架的是MFC类库而MFC类库又是C++的一个类库。这些类封装WIN32应用程序编程接口,OLE(Object Link Embed 对象链接嵌入)特性,ODBC和DAO数据访问的功能。
2>继承特性:MFC抽象出了众多类的共同特性,并设计出一些基类作为实现其他类的基础,这些类中最重要的类是CObject类和CCmdTarget类,程序员可以从适当的MFC类中派生出自己的类,实现特定的功能达到编程的目的。
3>虚拟和消息映射:MFC是以C++为基础,当然支持虚函数,但作为一个编程框架必须要解决的是效率问题:如果MFC仅仅通过虚函数来支持动态约束必然会产生大量的虚函数表这样编程框架过于臃肿而且消耗更多的内存。但是MFC建立了消息映射机制这样降低了内存的使用却大大提高了效率
消息映射是一个将消息和成员函数相互关联的表,当应用程序的框架窗口接收到一个消息时,MFC将搜索该窗口的消息映射,如果存在一个处理消息的处理程序,那么就调用该处理程序.
它通过宏来实现消息到成员函数的映射,而且这些函数不必是虚拟的成员函数,这样不需要为消息映射函数生成一个很大的虚拟函数表(V表),节省内存。
MFC消息映射机制:
将消息与消息处理函数联系起来,形成一一对应的机制。
消息映射宏
声明: DECLARE_MESSAGE_MAP
定义:
        BEGIN_MESSAGE_MAP
   ON_COMMAND
   ON_CONTROL
   ON_MESSAGE
       END_MESSAGE_MAP
MFC主要组成部分:类、宏和全局函数。
类是MFC中最主要的内容。MFC类是以层次结构方式组织起来的。MFC中的类分成两部分,除了一些辅助类,大多数的MFC类是直接或间接从根类CObject派生而来。
MFC宏主要功能:消息映射、运行时对象类型服务、诊断服务、异常处理。
MFC约定:全局函数以“Afx”为前缀,全局变量以“afx”为前缀
MFC类的层次关系
CObject项目类)->CCmdTarget(消息响应类)->
{
CWinThread(线程类)->CWinApp(Window应用程序类)
CDocument(文档类)
CWnd(窗体类)->[
              CFrameWnd(框架类)
              CView(视图类)
              ]
}

CObject类由于MFC中大部分类是从CObject类继承而来的,CObject类描述了几乎所有的MFC类的一些公共特性,CObject类为程序员提供了对象诊断、运行时类型识别和序列化等功能。
CCmdTarget类由CObject类直接派生而来,它负责将消息发送到能够响应这些消息的对象。它是所有能进行消息映射的MFC类的基类。
CWinApp类在任何MFC应用程序中有且仅有一个CWinApp派生类的对象,它代表了程序中运行的主线程,也代表了应用程序本身。 CWinApp类取代了WinMain()主函数在SDK应用程序中的地位。传统SDK应用程序WinMain()函数完成的工作。现在由类CWinApp的InitApplication(),InitInstance()和Run()三个成员函数承担。
CWnd类由CCmdTarget类直接派生而来,该类及其派生类的实例是一个窗口。CWnd类代表了MFC中最基本的GUI对象,它是一个功能最完善、成员函数最多的MFC类。
CFrameWnd类是CWnd类的派生类,主要用来掌管一个窗口,它取代了SDK应用程序中窗口函数WndProc()的地位。CFrameWnd类的对象是一个框架窗口,包括边框、标题栏、菜单、最大化按钮、最小化按钮和一个激活的视图。
CDocument类在应用程序中作为用户文档类的基类,它代表了用户存储或打开的一个文件。
CView类是MFC中一个很基本的类,它作为其它MFC视图类和用户视图派生类的基类。
从API编程到MFC编程的过渡:
WinMain()
{  初始化WNDCLASS
    注册窗体结构
    创建窗口           ->>>>>>>>应用程序类CWinApp
    显示窗口
    消息循环
}

WndProc()
{ switch(…)            
           ->>>>>>>>>框架窗口类CFrameWnd
}

MFC Object和Windows Object的对应关系:
描述                 Windows句柄       MFC Object
窗口                  HWND                    CWnd
设备上下文        HDC                      CDC
菜单                   HMENU                CMenu
笔                       HPEN                   CPen
刷子                   HBRUSH              CBrush
字体                   HFONT                 CFont
位图                    HBITMAP            CBitmap
套接字                SOCKET              CSocket

三、手工创建一个MFC应用程序:
注意:创建MFC程序,要创建一个Win32空项目,并要选择项目属性中的”在共享DLL文件中使用MFC,然后新建我们的文件
例子:在”hello.h”头文件中添写如下代码:
class CMyApp:public CWinApp
{
public:
virtual BOOL InitInstance();//虚函数
};
class CMainWindow:public CFrameWnd
{
public:
CMainWindow();
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP();//声明消息映射
};
在”hello.cpp”源文件中添写如下代码:
#include
#include “hello.h"
CMyApp myApp;
BOOL CMyApp::InitInstance()
{
m_pMainWnd = new CMainWindow;

      m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
BEGIN_MESSAGE_MAP(CMainWindow,CFrameWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()       //消息映射
CMainWindow::CMainWindow() //构造函数初始化
{
    Create(NULL,“我的第一个MFC应用程序”);//创建窗体
}
void CMainWindow::OnPaint()
{  CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
  dc.DrawText("Hello MFC",-1,&rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}
CWinApp是应用程序类,在MFC应用程序中必须从这个类派生出一个类,该派生类是MFC应用程序的入口
必须定义这个派生类的对象,并且只能有一个这个对象代表整个应用程序。
成员函数:InitInstance()
功能:初始化应用程序实例和窗口实例,
虚函数CWinApp::InitInstance必须在派生类中重写。在InitInstance函数中,编写初始化代码,如:
创建一个窗口
显示一个窗口
CFrameWnd类
作用:为应用程序提供一个窗口,同时实现消息处理功能。
成员函数: Create()
功能:创建窗体,将之赋于CFrameWnd对象上。
BOOL Create(窗口类型, 窗口标题,显示风格,显示区域,符窗口句柄,菜单,扩展显示风格,上下文对象)共有8个参数,前两个必须给出,后6个可以默认。
MFC应用程序的核心就是基于CWinApp类的应用程序对象,CWinApp提供了消息循环来检索消息并将消息调度给应用程序的窗口.我们在编写MFC应用程序时,要包含afxwin.h,
一个MFC应用程序可以有且仅有一个应用程序对象,对象必须声明为在全局范围内有效(也就是全局对象),以便它在程序开始时即在内存中被实例化
我们的Hello MFC的应用程序类被命名为CMyApp,它在hello.cpp中用如下语句进行了实例化:
CMyApp myApp;
CMyApp的类声明在hello.h中代码如下:
class CMyApp:public CWinApp
{
public:
virtual BOOL InitInstance();
};
CMyApp没有声明任何的数据成员,只是重写了一个从CWinApp类中继承来的函数,在应用程序的生存期内InitInstance的调用比较早,是在应用程序开始运行以后而窗口创建之前,除非InitIstance创建一个窗口,否则应用程序是不会有窗口,这正是为什么即使最小的MFC应用程序也必须从CWinApp派生出一个类并重写CWinApp::InitIstance的原因
InitInstance函数:CWinApp::InitInstance是一个虚函数,其默认操作仅包含一条语句:return TRUE;
InitInstance是用来执行程序每次开始时都需要进行的初始化工作最好的地方
在hello.cpp中,CMyApp的InitInstance通过实例化hello的CMainWindow类来创建hello窗口,语句:
m_pMainWnd = new CMainWindow;

构造了一个CMainWindow对象指针,并将其地址复制到了应用程序对象的m_pMainWnd数据成员中,窗口创建以后,InitInstance就会通过CMainWindow指针调用ShowWindow和UpdateWindow函数显示它:

m_pMainWnd->ShowWindow(m_nCmdShow);

m_pMainWnd->UpdateWindow();
ShowWindow和UpdateWindow是所有窗口对象共用的CWnd成员函数其中包括CFrameWnd类的对象,CMainWindow就是从CFrameWnd派生出来的.
要从MFC程序调用一个常规的Windows API函数,需要在函数名称前添加一个全局运算符:: 例如:::UpdateWindow();
通过生成窗口对象并调用其Create函数,MFC应用程序可以创建一个窗口,在CMyApp::InitInstance中,hello创建了一个CMainWindow对象,CMainWindow的构造函数生成在屏幕上看到的窗口:
Create(NULL,”我的第一个MFC应用程序”);
CPaintDC dc(this);
MFC的CPaintDC类是从MFC的CDC类派生的,CDC类封装了Windows设备环境,以及包含了绘制到屏幕、打印机和其他设备的几十个成员函数

在MFC中如何处理消息呢?
在SDK中我们利用的是消息循环和窗口过程函数对消息进行消息处理.
在MFC中我们用的是消息映射机制.
下面是将消息映射添加到一个类中需要做的全部工作.
1>通过将DECLARE_MESSAGE_MAP语句添加到类声明中,声明消息映射.
2>通过放置标识消息的宏来执行消息映射,相应的类将在对BEGIN_MESSAGE_MAP和END_MESSAGE_MAP的调用之间处理消息
3>添加成员函数来处理消息
1、构造CWinApp派生类的对象
2、系统调用WinMain()
3、WinMain调用InitInstance,在该函数中创建CFrameWnd派生类对象,调用Create函数创建窗口、调用ShowWindow函数显示窗口。
4、之后内部机制调用Run,接受用户的消息,并将消息导向默认的处理函数。当接收到WM_QUIT消息时,Run内部调用ExitInstance,退出程序。
MFC采用消息映射(Message Map)机制取代C/C++语言中的switch-case结构来处理消息。
消息映射:在MFC中把消息处理函数和它所要处理的特定的消息连接起来的一种机制。
它通过宏来实现消息到成员函数的映射,而且这些函数不必是虚拟的成员函数,这样不需要为消息映射函数生成一个很大的虚拟函数表(V表),节省内存。
MFC消息映射机制包括一组消息映射宏。一条消息映射宏把一个Windows消息和其消息处理函数联结起来。
MFC应用程序框架提供了消息映射功能。
在类的实现源文件中用BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()宏来定义消息映射。
在类定义的结尾用DECLARE_MESSAGE_MAP()宏来声明使用消息映射。
Hello的CmainWindow类只处理一种消息类型—WM_PAINT,因此其消息映射的实现如下所示:
BEGIN_MESSAGE_MAP(CMainWindow,CFrameWnd);
ON_WM_PAINT()
END_MESSAGE_MAP()
BEGIN_MESSAGE_MAP开始了消息映射,并标识了消息映射所属的类和该类的基类
END_MESSAGE_MAP()结束消息映射.
ON_WM_PAINT()在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP()之间,称做消息条目,在MFC为100多种Window消息提供了宏.
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
afx_msg 醒目地暗示OnPaint是一个消息处理程序,
DECLARE_MESSAGE_MAP()声明消息映射
MFC把消息主要分为三大类:
(1)、标准Windows消息(WM_XXX)
使用宏:ON_WM_XXX()          特点:有默认的消息处理函数
(2)、命令消息:(WM_COMMAND)
来自于菜单、工具条、按钮等的消息
使用宏: ON_COMMAND(命令按钮标识符ID,消息处理函数)
特点:由用户指定消息处理函数
3、”Notification消息” (通知消息) 由控件产生:
BOOL    布尔值,取值为TRUE或者FALSE
BSTR     32为字符指针
BYTE      8位整数无符号的
COLORREF     32位数值代表一个颜色值
DWORD    32位整数无符号的
LONG     32位整数带符号的
LPCTSTR   32位指针,指向一个常字符串
LPVOID    32位指针,指向一个为指定类型的数据
MFC特有的数据类型:
1>POSITION :一个数值,代表数组或者链表中元素的位置,在MFC中常用于数据处理类
2>LPRECT:32位指针,指向一个不变的矩形区域结构

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表