微软基础类库(英语Microsoft Foundation Classes,简称MFC)是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程式框架,以减少应用程式开发人员的工作量。其中包含大量Windows句柄封装类和很多Windows的内建控制项和组件的封装类。
基本介绍
- 中文名微软基础类库
- 外文名Microsoft Foundation Classes
- 简称MFC
- 类型C/C++的图形化界面语言
- 开发者微软
- 特点效率损失低
- 使用者大众
定义
MFC(Microsoft Foundation Classes)是微软基础类库的简称,是微软公司实现的一个c++类库,主要封装了大部分的windows API函式,vc++是微软公司开发的c/c++的集成开发环境,所谓集成开发环境,就是说利用它可以编辑,编译,调试,而不是使用多种工具轮换操作,灵活性较大。vc也指它的内部编译器,集成开发环境必须有一个编译器核心,例如DevC++其中一个编译器核心就是gcc。 MFC除了是一个类库以外,还是一个框架,在vc++里新建一个MFC的工程,开发环境会自动帮你产生许多档案,它使用了mfcxx.dll。xx是版本,它封装了mfc核心,所以你在你的代码看不到原本的SDK编程中的讯息循环等等东西,因为MFC框架帮你封装好了,这样你就可以专心的考虑你程式的逻辑,而不是这些每次编程都要重複的东西,由于是通用框架,没有最好的针对性,也就丧失了一些灵活性和效率。MFC的封装很浅,所以效率上损失不大。
比较
MFC Object和Windows Object的关係
MFC中最重要的封装是对Win32 API的封装,,理解Windows Object和MFC Object (C++对象,一个C++类的实例)之间的关係是理解MFC的关键之一。所谓Windows Object(Windows对象)是Win32下用句柄表示的Windows作业系统对象;所谓MFC Object (MFC对象)是C++对象,是一个C++类的实例,这里MFC Object是有特定含义的,指封装Windows Object的C++ Object,并非指任意的C++ Object。
MFC Object 和Windows Object是不一样的,但两者紧密联繫。以视窗对象为例
一个MFC视窗对象是一个C++ CWnd类(或派生类)的实例,是程式直接创建的。在程式执行中它随着视窗类构造函式的调用而生成,随着析构函式的调用而消失。而Windows视窗则是Windows系统的一个内部数据结构的实例,由一个“视窗句柄”标识,Windows系统创建它并给它分配系统资源。Windows视窗在MFC视窗对象创建之后,由CWnd类的Create成员函式创建,“视窗句柄”保存在视窗对象的m_hWnd成员变数中。Windows视窗可以被一个程式销毁,也可以被用户的动作销毁。MFC视窗对象和Windows视窗对象的关係如图2-1所示。其他的Windows Object和对应的MFC Object也有类似的关係。
下面,对MFC Object和Windows Object作一个比较。有些论断对设备描述表(MFC类是CDC,句柄是HDC)可能不适用,但具体涉及到时会指出。
从数据结构上比较
MFC Object是相应C++类的实例,这些类是MFC或者程式设计师定义的;
Windows Object是Windows系统的内部结构,通过一个句柄来引用;
MFC给这些类定义了一个成员变数来保存MFC Object对应的Windows Object的句柄。对于设备描述表CDC类,将保存两个HDC句柄。
从层次上讲比较
MFC Object是高层的,Windows Object是低层的;
MFC Object封装了Windows Object的大部分或全部功能,MFC Object的使用者不需要直接套用Windows Object的HANDLE(句柄)使用Win32 API,代替它的是引用相应的MFC Object的成员函式。
从创建上比较
MFC Object通过构造函式由程式直接创建;Windows Object由相应的SDK函式创建。
MFC中,使用这些MFC Object,一般分两步
,创建一个MFC Object,或者在STACK中创建,或者在HEAP中创建,这时,MFC Object的句柄实例变数为空,或者说不是一个有效的句柄。
然后,调用MFC Object的成员函式创建相应的Windows Object,MFC的句柄变数存储一个有效句柄。
CDC(设备描述表类)的创建有所不同,在后面的2.3节会具体说明CDC及其派生类的创建和使用。
,可以在MFC Object的构造函式中创建相应的Windows对象,MFC的GDI类就是如此实现的,但从实质上讲,MFC Object的创建和Windows Object的创建是两回事。
从转换上比较
可以从一个MFC Object得到对应的Windows Object的句柄;一般使用MFC Object的成员函式GetSafeHandle得到对应的句柄。
可以从一个已存在的Windows Object创建一个对应的MFC Object; 一般使用MFC Object的成员函式Attach或者FromHandle来创建,前者得到一个永久性对象,后者得到的可能是一个临时对象。
从使用範围上比较
MFC Object对系统的其他进程来说是不可见、不可用的;而Windows Object一旦创建,其句柄是整个Windows系统全局的。一些句柄可以被其他进程使用。典型地,一个进程可以获得另一进程的视窗句柄,并给该视窗传送讯息。
对同一个进程的执行绪来说,只可以使用本执行绪创建的MFC Object,不能使用其他执行绪的MFC Object。
从销毁上比较
MFC Object随着析构函式的调用而消失;但Windows Object必须由相应的Windows系统函式销毁。
设备描述表CDC类的对象有所不同,它对应的HDC句柄对象可能不是被销毁,而是被释放。
,可以在MFC Object的析构函式中完成Windows Object的销毁,MFC Object的GDI类等就是如此实现的,,应该看到两者的销毁是不同的。
每类Windows Object都有对应的MFC Object,下面用表格的形式列出它们之间的对应关係,如表2-1所示
表2-1 MFC Object和Windows Object的对应关係
描述 | Windows句柄 | MFC Object |
视窗 | HWND | CWnd and CWnd-derived classes |
设备上下文 | HDC | CDC and CDC-derived classes |
选单 | HMENU | CMenu |
笔 | HPEN | CGdiObject类,CPen和CPen-derived classes |
刷子 | HBRUSH | CGdiObject类,CBrush和CBrush-derived classes |
字型 | HFONT | CGdiObject类,CFont和CFont-derived classes |
点阵图 | HBITMAP | CGdiObject类,CBitmap和CBitmap-derived classes |
调色板 | HPALETTE | CGdiObject类,CPalette和CPalette-derived classes |
区域 | HRGN | CGdiObject类,CRgn和CRgn-derived classes |
图像列表 | HimageLIST | CimageList和CimageList-derived classes |
套接字 | SOCKET | CSocket,CAsynSocket及其派生类 |
表2-1中的OBJECT分以下几类
Windows对象,
设备上下文对象,
GDI对象(BITMAP,BRUSH,FONT,PALETTE,PEN,RGN),
选单,
图像列表,
网路套接字接口。
从广义上来看,文档对象和档案可以看作一对MFC Object和Windows Object,分别用CDocument类和档案句柄描述。
后续几节分别对前四类作一个简明扼要的论述。
Windows Object
用SDK的Win32 API编写各种Windows应用程式,有其共同的规律是编写WinMain函式,编写处理讯息和事件的视窗过程WndProc,在WinMain里头注册视窗(Register Window),创建视窗,然后开始应用程式的讯息循环。
MFC应用程式也不例外,因为MFC是一个建立在SDK API基础上的编程框架。对程式设计师来说所不同的是一般情况下,MFC框架自动完成了Windows登记、创建等工作。
下面,简要介绍MFC Window对Windows Window的封装。
Windows的注册
一个应用程式在创建某个类型的视窗前,必须注册该“视窗类”(Windows Class)。注意,这里不是C++类的类。Register Window把视窗过程、视窗类型以及其他类型信息和要登记的视窗类关联起来。
“视窗类”的数据结构
“视窗类”是Windows系统的数据结构,可以把它理解为Windows系统的类型定义,而Windows视窗则是相应“视窗类”的实例。Windows使用一个结构来描述“视窗类”,其定义如下
typedef struct _WNDCLASSEX {UINT cbSize; //该结构的位元组数UINT style; //视窗类的风格WNDPROC lpfnWndProc; //视窗过程int cbClsExtra;int cbWndExtra;HANDLE hInstance; //该视窗类的视窗过程所属的套用实例HICON hIcon; //该视窗类所用的像标HCURSOR hCursor; //该视窗类所用的游标HBRUSH hbrBackground; //该视窗类所用的背景刷LPCTSTR lpszMenuName; //该视窗类所用的选单资源LPCTSTR lpszClassName; //该视窗类的名称HICON hIconSm; //该视窗类所用的小像标} WNDCLASSEX;
从“视窗类”的定义可以看出,它包含了一个视窗的重要信息,如视窗风格、视窗过程、显示和绘製视窗所需要的信息,等等。关于视窗过程,将在后面讯息映射等有关章节作详细论述。
Windows系统在初始化时,会注册(Register)一些全局的“视窗类”,例如通用控制视窗类。应用程式在创建自己的视窗时,必须注册自己的视窗类。在MFC环境下,有几种方法可以用来注册“视窗类”,下面分别予以讨论。
调用AfxRegisterClass注册
AfxRegisterClass函式是MFC全局函式。AfxRegisterClass的函式原型
BOOL AFXAPI AfxRegisterClass(WNDCLASS lpWndClass);
参数lpWndClass是指向WNDCLASS结构的指针,表示一个“视窗类”。
,AfxRegisterClass检查希望注册的“视窗类”是否已经注册,如果是则表示已注册,返回TRUE,否则,继续处理。
接着,调用::RegisterClass(lpWndClass)注册视窗类;
然后,如果当前模组是DLL模组,则把注册“视窗类”的名字加入到模组状态的域m_szUnregisterList中。该域是一个固定长度的缓冲区,依次存放模组注册的“视窗类”的名字(每个名字是以“\n\0”结尾的字元串)。之所以这样做,是为了DLL退出时能自动取消(Unregister)它注册的视窗类。至于模组状态将在后面第9章详细的讨论。
,返回TRUE表示成功注册。
调用AfxRegisterWndClass注册
AfxRegisterWndClass函式也是MFC全局函式。AfxRegisterWndClass的函式原型
LPCTSTR AFXAPI AfxRegisterWndClass(UINT nClassStyle,
HCURSOR hCursor, HBRUSH hbrBackground, HICON hIcon)
参数1指定视窗类风格;
参数2、3、4分别指定该视窗类使用的游标、背景刷、像标的句柄,预设值是0。
此函式根据视窗类属性动态地产生视窗类的名字,然后,判断是否该类已经注册,是则返回视窗类名;否则用指定视窗类的属性(视窗过程指定为预设视窗过程),调用AfxRegisterCalss注册视窗类,返回类名。
动态产生的视窗类名字由以下几部分组成(包括冒号分隔设定)
如果参数2、3、4全部为NULL,则由三部分组成。
“Afx”+“:”+模组实例句柄”+“:”+“视窗类风格”
否则,由六部分组成
“Afx”+“:”+模组实例句柄+“:”+“视窗类风格”+“:”+游标句柄+“:”+背景刷句柄+“:”+像标句柄。比如“Afx:400000:b:13de:6:32cf”。
该函式在MFC注册主框线或者文档框线“视窗类”时被调用。具体怎样用在5.3.3.3节会指出。
隐含的使用MFC预定义的的视窗类
MFC4.0以前的版本提供了一些预定义的视窗类,4.0以后不再预定义这些视窗类。,MFC仍然沿用了这些视窗类,例如
用于子视窗的“AfxWnd”;
用于框线视窗(SDI主视窗或MDI子视窗)或视的“AfxFrameOrView”;
用于MDI主视窗的“AfxMDIFrame”;
用于标準控制条的“AfxControlBar”。
这些类的名字就 是“AfxWnd”、“AfxFrameOrView”、“AfxMdiFrame”、 “AfxControlBar”加上前缀和后缀(用来标识版本号或是否调试版等)。它们使用标準套用程式像标、标準文档像标、标準游标等标準资源。为了使用这些“视窗类”,MFC会在适当的时候注册这些类或者要创建该类的视窗时,或者创建应用程式的主视窗时,等等。
MFC内部使用了函式
BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)
来帮助注册上述原MFC版本的预定义“视窗类”。参数fClass区分了那些预定义视窗的类型。根据不同的类型,使用不同的视窗类风格、视窗类名字等填充WndClass的域,然后调用AfxRegisterClass注册视窗类。并且注册成功之后,通过模组状态的m_fRegisteredClasses记录该视窗类已经注册,这样该模组在需要注册这些视窗类之前可以查一下m_fRegisteredClasses,如果已经注册就不必浪费时间了。为此,MFC内部使用宏
AfxDeferRegisterClass(short fClass)
来注册“视窗类”,如果m_fRegisteredClasses记录了注册的视窗类,返回TRUE,否则,调用AfxEndDeferRegisterClass注册。
注册这些视窗类的例子
MFC在载入框线视窗时,会自动地注册“AfxFrameOrView”视窗类。在创建视时,就会使用该“视窗类”创建视视窗。,如果创建视视窗时,该“视窗类”还没有注册,MFC将先注册它然后使用它创建视视窗。
不过,MFC并不使用”AfxMDIFrame”来创建MDI主视窗,因为在载入主视窗时一般都指定了主视窗的资源,MFC使用指定的像标注册新的MDI主视窗类(通过函式AfxRegisterWndClass完成,“视窗类”的名字是动态产生的)。
MDI子视窗类似于上述MDI主视窗的处理。
在MFC创建控制视窗时,如工具列视窗,如果“AfxControlBar”类还没有注册,则注册它。注册过程很简单,就是调用::InitCommonControl载入通用控制动态连线库。
调用::RegisterWndClass。
直接调用Win32的视窗注册函式::RegisterWndClass注册“视窗类”,这样做有一个缺点如果是DLL模组,这样注册的“视窗类”在程式退出时不会自动的被取消注册(Unregister)。所以必须记得在DLL模组退出时取消它所注册的视窗类。
子类化
子类化(Subclass)一个“视窗类”,可自动地得到它的“视窗类”属性。
MFC视窗类CWnd
在Windows系统里,一个视窗的属性分两个地方存放一部分放在“视窗类”里头,如上所述的在注册视窗时指定;另一部分放在Windows Object本身,如视窗的尺寸,视窗的位置(X,Y轴),视窗的Z轴顺序,视窗的状态(ACTIVE,MINIMIZED,MAXMIZED,RESTORED…),和其他视窗的关係(父视窗,子视窗…),视窗是否可以接收键盘或滑鼠讯息,等等。
为了表达所有这些视窗的共性,MFC设计了一个视窗基类CWnd。有一点非常重要,那就是CWnd提供了一个标準而通用的MFC视窗过程,MFC下所有的视窗都使用这个视窗过程。至于通用的视窗过程却能为各个视窗实现不同的操作,那就是MFC讯息映射机制的奥秘和作用了。这些,将在后面有关章节详细论述。
CWnd提供了一系列成员函式,或者是对Win32相关函式的封装,或者是CWnd新设计的一些函式。这些函式大致如下。
(1)视窗创建函式
这里主要讨论函式Create和CreateEx。它们封装了Win32视窗创建函式::CreateWindowEx。Create的原型如下
BOOL CWnd::Create(LPCTSTR lpszClassName,LPCTSTR lpszWindowName, DWORD dwStyle,const RECT& rect,CWnd pParentWnd, UINT nID,CCreateContext pContext)
Create是一个虚拟函式,用来创建子视窗(不能创建桌面视窗和POP UP视窗)。CWnd的基类可以覆盖该函式,例如框线视窗类等覆盖了该函式以实现框线视窗的创建,视类则使用它来创建视视窗。
Create调用了成员函式CreateEx。CWnd::CreateEx的原型如下
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,LPCTSTR lpszWindowName, DWORD dwStyle,int x, int y, int nWidth, int nHeight,HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
CreateEx有11个参数,它将调用::CreateWindowEx完成视窗的创建,这11个参数对应地传递给::CreateWindowEx。参数指定了视窗扩展风格、“视窗类”、视窗名、视窗大小和位置、父视窗句柄、视窗选单和视窗创建参数。
CreateEx的处理流程将在后面4.4.1节讨论视窗过程时分析。
视窗创建时传送WM_CREATE讯息,讯息参数lParam指向一个CreateStruct结构的变数,该结构有11个域,其描述见后面4.4.1节对视窗过程的分析,Windows使用和CreateEx参数一样的内容填充该变数。
(2)视窗销毁函式
例如
DestroyWindow函式 销毁视窗
PostNcDestroy( ),销毁视窗后调用,虚拟函式
(3)用于设定、获取、改变视窗属性的函式,例如
SetWindowText(CString tiltle) 设定视窗标题GetWindowText() 得到视窗标题SetIcon(HICON hIcon, BOOL bBigIcon);设定视窗像标GetIcon( BOOL bBigIcon ) ;得到视窗像标GetDlgItem( int nID);得到视窗类指定ID的控制子视窗GetDC(); 得到视窗的设备上下文SetMenu(CMenu pMenu); 设定视窗选单GetMenu();得到视窗选单
…
(4)用于完成视窗动作的函式
用于更新视窗,滚动视窗,等等。一部分成员函式设计成或可重载(Overloaded)函式,或虚拟(Overridden)函式,或MFC讯息处理函式。这些函式或者实现了一部分功能,或者仅仅是一个空函式。如
有关讯息传送的函式
SendMessage( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );
给视窗传送传送讯息,立即调用方式
PostMessage(( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );
给视窗传送讯息,放进讯息伫列
…
有关改变视窗状态的函式
MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );
移动视窗到指定位置
ShowWindow(BOOL );显示视窗,使之可见或不可见
….
实现MFC讯息处理机制的函式
virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); 视窗过程,虚拟函式virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );处理命令讯息
…
讯息处理函式
OnCreate( LPCREATESTRUCT lpCreateStruct );MFC视窗讯息处理函式,视窗创建时由MFC框架调用OnClose();MFC视窗讯息处理函式,视窗创建时由MFC框架调用
…
其他功能的函式
CWnd的导出类是类型更具体、功能更完善的视窗类,它们继承了CWnd的属性和方法,并提供了新的成员函式(讯息处理函式、虚拟函式、等等)。
常用的视窗类及其层次关係见图1-1。
在MFC下创建一个视窗对象
MFC下创建一个视窗对象分两步,创建MFC视窗对象,然后创建对应的Windows视窗。在记忆体使用上,MFC视窗对象可以在栈或者堆(使用new创建)中创建。具体表述如下
创建MFC视窗对象。通过定义一个CWnd或其派生类的实例变数或者动态创建一个MFC视窗的实例,前者在栈空间创建一个MFC视窗对象,后者在堆空间创建一个MFC视窗对象。
调用相应的视窗创建函式,创建Windows视窗对象。
例如在前面提到的AppWizard产生的源码中,有CMainFrame(派生于CMDIFrame(SDI)或者CMDIFrameWnd(MDI))类。它有两个成员变数定义如下
CToolBar m_wndToolBar;CStatusBar m_wndStatusBar;
当创建CMainFrame类对象时,上面两个MFC Object也被构造。
CMainFrame还有一个成员函式
OnCreate(LPCREATESTRUCT lpCreateStruct),
它的实现包含如下一段代码,调用CToolBar和CStatusBar的成员函式Create来创建上述两个MFC对象对应的工具列HWND视窗和状态栏HWND视窗
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){…if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME)){TRACE0("Failed to create toolbar\n");return -1; // fail to create}if (!m_wndStatusBar.Create(this) ||!m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT))){TRACE0("Failed to create status bar\n");return -1; // fail to create}…}
关于工具列、状态栏将在后续有关章节作详细讨论。
在MFC中,还提供了一种动态创建技术。动态创建的过程实际上也如上所述分两步,只不过MFC使用这个技术是由框架自动地完成整个过程的。通常框架视窗、文档框架视窗、视使用了动态创建。介于MFC的结构,CFrameWnd和CView及其派生类的实例即使不使用动态创建,也要用new在堆中分配。理由见视窗的销毁(2.2.5节)。
至于动态创建技术,将在下一章具体讨论。
在Windows视窗的创建过程中,将传送一些讯息,如
在创建了视窗的非客户区(Nonclient area)之后,传送讯息WM_NCCREATE;
在创建了视窗的客户区(client area)之后,传送讯息WM_CREATE;
视窗的视窗过程在视窗显示之前收到这两个讯息。
如果是子视窗,在传送了上述两个讯息之后,还给父视窗传送WM_PARENATNOTIFY讯息。其他类或风格的视窗可能传送更多的讯息,具体参见SDK开发文档。
MFC视窗的使用
MFC提供了大量的视窗类,其功能和用途各异。程式设计师应该选择哪些类来使用,以及怎幺使用他们呢?
直接使用MFC提供的视窗类或者先从MFC视窗类派生一个新的C++类然后使用它,这些在通常情况下都不需要程式设计师提供视窗注册的代码。是否需要派生新的C++类,视MFC已有的视窗类是否能满足使用要求而定。派生的C++类继承了基类的特性并改变或扩展了它的功能,例如增加或者改变对讯息、事件的特殊处理等。
主要使用或继承以下一些MFC视窗类(其层次关係图见图1-1)
框架类CFrameWnd,CMdiFrameWnd;
文档框架CMdiChildWnd;
视图CView和CView派生的有特殊功能的视图如列表CListView,编辑CEditView,树形列表CTreeView,支持RTF的CRichEditView,基于对话框的视CFormView等等。
对话框CDialog。
通常,都要从这些类派生应用程式的框架视窗和视视窗或者对话框。
工具条CToolBar
状态条CStatusBar
其他各类控制视窗,如列表框CList,编辑框CEdit,组合框CComboBox,按钮Cbutton等。
通常,直接使用这些类。
在MFC下视窗的销毁
视窗对象使用完毕,应该销毁。在MFC下,一个视窗对象的销毁包括HWND视窗对象的销毁和MFC视窗对象的销毁。一般情况下,MFC编程框架自动地处理了这些。
(1)对CFrameWnd和CView的派生类
这些视窗的关闭导致销毁视窗的函式DestroyWindow被调用。销毁Windows视窗时,MFC框架调用的一个成员函式是OnNcDestroy函式,该函式负责Windows清理工作,并在调用虚拟成员函式PostNcDestroy。CFrameWnd和CView的PostNcDestroy调用delete this删除自身这个MFC视窗对象。
所以,对这些视窗,如前所述,应在堆(Heap)中分配,而且,不要对这些对象使用delete操作。
(2)对Windows Control视窗
在它们的析构函式中,将调用DestroyWidnow来销毁视窗。如果在栈中分配这样的视窗对象,则在超出作用範围的时候,随着析构函式的调用,MFC视窗对象和它的Windows window对象都被销毁。如果在堆(Heap)中分配,则显式调用delete操作符,导致析构函式的调用和视窗的销毁。
所以,这种类型的视窗应儘可能在栈中分配,避免用额外的代码来销毁视窗。如前所述的CMainFrame的成员变数m_wndStatusBar和m_wndToolBar就是这样的例子。
(3)对于程式设计师直接从CWnd派生的视窗
程式设计师可以在派生类中实现上述两种机制之一,然后,在相应的规范下使用。
后面章节将详细的讨论应用程式退出时关闭、清理视窗的过程。
设备描述表
设备描述表概述
当一个应用程式使用GDI函式时,必须先装入特定的设备驱动程式,然后为绘製视窗準备设备描述表,比如指定线的宽度和颜色、刷子的样式和颜色、字型、剪裁区域等等。不像其他Win32结构,设备描述表不能被直接访问,只能通过系列Win32函式来间接地操作。
如同Windows“视窗类”一样,设备描述表也是一种Windows数据结构,用来描述绘製视窗所需要的信息。它定义了一个坐标映射模式、一组GDI图形对象及其属性。这些GDI对象包括用于画线的笔,绘图、填图的刷子,点阵图,调色板,剪裁区域,及路径(Path)。
表2-2列出了设备描述表的结构和各项预设值,表2-3列出了设备描述表的类型,表2-4显示设备描述表的类型。
表2-2 设备描述表的结构
属性 | 预设值 | |
Background color | Background color setting from Windows Control Panel (typically, white) | |
Background mode | OPAQUE | |
Bitmap | None | |
Brush | WHITE_BRUSH | |
Brush origin | (0,0) | |
Clipping region | Entire window or client area with the update region clipped, as appropriate. Child and pop-up windows in the client area may also be clipped | |
Palette | DEFAULT_PALETTE | |
Current pen position | (0,0) | |
Device origin | Upper left corner of the window or the client area | |
Drawing mode | R2_COPYPEN | |
Font | SYSTEM_FONT (SYSTEM_FIXED_FONT for applications written to run with Windows versions 3.0 and earlier) | |
Intercharacter spacing | 0 | |
Mapping mode | MM_TEXT | |
Pen | BLACK_PEN | |
Polygon-fill mode | ALTERNATE | |
Stretch mode | BLACKONWHITE | |
Text color | Text color setting from Control Panel (typically, black) | |
Viewport extent | (1,1) | |
Viewport origin | (0,0) | |
Window extent | (1,1) | |
Window origin | (0,0) |
表2-3 设备描述表的分类
Display | 显示设备描述表,提供对视频显示设备上的绘製操作的支持 |
Printer | 列印设备描述表,提供对印表机、绘图仪设备上的绘製操作的支持 |
Memory | 记忆体设备描述表,提供对点阵图操作的支持 |
Information | 信息设备描述表,提供对操作设备信息获取的支持 |
表2-3中的显示设备描述表又分三种类型,如表2-4所示。
表2-4 显示设备描述表的分类
名称 | 特点 | 功能 |
Class DeviceContexts | 提供对Win16的向后兼容 | |
CommonDeviceContexts | 在Windows系统的高速缓冲区,数量有限 | Applicaion获取设备描述表时,Windows用预设值初始化该设备描述表,Application使用它完成绘製操作,然后释放 |
PrivateDeviceContexts | 没有数量限制,用完不需释放一次获取,多次使用 | 多次使用过程中,每次设备描述表属性的任何修改或变化都会被保存,以支持快速绘製 |
(1)使用设备描述表的步骤
要使用设备描述表,一般有如下步骤
获取或者创建设备描述表;
必要的话,改变设备描述表的属性;
使用设备描述表完成绘製操作;
释放或删除设备描述表。
Common设备描述表通过::GetDC,::GetDCEx,::BeginPaint来获得一个设备描述表,用毕,用::ReleaseDC或::EndPaint释放设备描述表;
Printer设备描述表通过::CreateDC创建设备描述表,用::DeleteDC删除设备描述表。
Memory设备描述表通过::CreateCompatibleDC创建设备描述表,用::DeleteDC删除。
Information设备描述表通过::CreateIC创建设备描述表,用::DeleteDC删除。
(2)改变设备描述表属性的途径
要改变设备描述表的属性,可通过以下途径
用::SelectObject选入新的除调色板以外的GDI Object到设备描述表中;
对于调色板,使用::SelectPalette函式选入逻辑调色板,并使用::RealizePalette把逻辑调色板的入口映射到物理调色板中。
用其他API函式改变其他属性,如::SetMapMode改变映射模式。
设备描述表在MFC中的实现
MFC提供了CDC类作为设备描述表类的基类,它封装了Windows的HDC设备描述表对象和相关函式。
CDC类
CDC类包含了各种类型的Windows设备描述表的全部功能,封装了所有的Win32 GDI 函式和设备描述表相关的SDK函式。在MFC下,使用CDC的成员函式来完成所有的视窗绘製工作。
CDC类有两个成员变数m_hDC,m_hAttribDC,它们都是Windows设备描述表句柄。CDC的成员函式作输出操作时,使用m_Hdc;要获取设备描述表的属性时,使用m_hAttribDC。
在创建一个CDC类实例时,预设的m_hDC等于m_hAttribDC。如果需要的话,程式设计师可以分别指定它们。例如,MFC框架实现CMetaFileDC类时,就是如此CMetaFileDC从物理设备上读取设备信息,输出则送到元档案(metafile)上,所以m_hDC和m_hAttribDC是不同的,各司其责。还有一个类似的例子列印预览的实现,一个代表印表机模拟输出,一个代表萤幕显示。
CDC封装::SelectObject(HDC hdc,HGDIOBJECT hgdiobject)函式时,採用了重载技术,即它针对不同的GDI对象,提供了名同而参数不同的成员函式
SelectObject(CPen pen)用于选入笔;SelectObject(CBitmap pBitmap)用于选入点阵图;SelectObject(CRgn pRgn)用于选入剪裁区域;SelectObject(CBrush pBrush)用于选入刷子;SelectObject(CFont pFont)用于选入字型;
至于调色板,使用SelectPalette(CPalette pPalette,BOOL bForceBackground )选入调色板到设备描述表,使用RealizePalletter()实现逻辑调色板到物理调色板的映射。
从CDC派生出功能更具体的设备描述表
下面,分别讨论派生出的四种设备描述表。
CClientDC
代表视窗客户区的设备描述表。其构造函式CClientDC(CWnd pWin)通过::GetDC获取指定视窗的客户区的设备描述表HDC,并且使用成员函式Attach把它和CClientDC对象捆绑在一起;其析构函式使用成员函式Detach把设备描述表句柄HDC分离出来,并调用::ReleaseDC释放设备描述表HDC。
CPaintDC
仅仅用于回响WM_PAINT讯息时绘製视窗,因为它的构造函式调用了::BeginPaint获取设备描述表HDC,并且使用成员函式Attach把它和CPaintDC对象捆绑在一起;析构函式使用成员函式Detach把设备描述表句柄HDC分离出来,并调用::EndPaint释放设备描述表HDC,而::BeginPaint和::EndPaint仅仅在回响WM_PAINT时使用。
CMetaFileDC
用于生成元档案。
CWindowDC
代表整个视窗区(包括非客户区)的设备描述表。其构造函式CWindowDC(CWnd pWin)通过::GetWindowDC获取指定视窗的客户区的设备描述表HDC,并使用Attach把它和CWindowDC对象捆绑在一起;其析构函式使用Detach把设备描述表HDC分离出来,调用::ReleaseDC释放设备描述表HDC。
MFC设备描述表类的使用
使用CPaintDC、CClientDC、CWindowDC的方法
,定义一个这些类的实例变数,通常在栈中定义。然后,使用它。
例如,MFC中CView对WM_PAINT讯息的实现方法如下
void CView::OnPaint(){// standard paint routineCPaintDC dc(this);OnPrepareDC(&dc);OnDraw(&dc);}
在栈中定义了CPaintDC类型的变数dc,随着构造函式的调用获取了设备描述表;设备描述表使用完毕,超出其有效範围就被自动地清除,随着析构函式的调用,其获取的设备描述表被释放。
如果希望在堆中创建,例如
CPaintDC pDC;pDC = new CPaintDC(this)
则在使用完毕时,用delete删除pDC:
delete pDC;
直接使用CDC
需要注意的是在生成CDC对象的时候,并不像它的派生类那样,在构造函数里获取相应的Windows设备描述表。最好不要使用::GetDC等函式来获取一个设备描述表,而是创建一个设备描述表。其构造函式如下
CDC::CDC(){m_hDC = NULL;m_hAttribDC = NULL;m_bPrinting = FALSE;}其析构函式如下CDC::~CDC(){if (m_hDC != NULL)::DeleteDC(Detach());}
在CDC析构函式中,如果设备描述表句柄不空,则调用DeleteDC删除它。这是直接使用CDC时最好创建Windows设备描述表的理由。如果设备描述表不是创建的,则应该在析构函式被调用前分离出设备描述表句柄并用::RealeaseDC释放它,释放后m_hDC为空,则在析构函式调用时不会执行::DeleteDC。,不用担心CDC的派生类的析构函式调用CDC的析构函式,因为CDC::~CDC()不是虚拟析构函式。
直接使用CDC的例子是记忆体设备上下文,例如
CDC dcMem; //声明一个CDC对象
dcMem.CreateCompatibleDC(&dc); //创建设备描述表
pbmOld = dcMem.SelectObject(&m_bmBall);//更改设备描述表属性
…//作一些绘製操作
dcMem.SelectObject(pbmOld);//恢复设备描述表的属性
dcMem.DeleteDC(); //可以不调用,而让析构函式去删除设备描述表
GDI对象
在讨论设备描述表时,已经多次涉及到GDI对象。这里,需强调一下GDI对象要选入Windows 设备描述表后才能使用;用毕,要恢复设备描述表的原GDI对象,并删除该GDI对象。
一般按如下步骤使用GDI对象
Create or get a GDI OBJECT hNewGdi;hOldGdi = ::SelectObject(hdc, hNewGdi)……::SelectObject(hdc, hOldGdi)::DeleteObject(hNewGdi)
先创建或得到一个GDI对象,然后把它选入设备描述表并保存它原来的GDI对象;用毕恢复设备描述表原来的GDI对象并删除新创建的GDI对象。
需要指出的是,如果hNewGdi是一个Stock GDI对象,可以不删除(删除也可以)。通过
HGDIOBJ GetStockObject(int fnObject // type of stock object);
来获取Stock GDI对象。
MFC GDI对象
MFC用一些类封装了Windows GDI对象和相关函式,
CGdiObject封装了Windows GDI Object共有的特性。其派生类在继承的基础上,主要封装了各类GDI的创建函式以及和具体GDI对象相关的操作。
CGdiObject的构造函式仅仅让m_hObject为空。如果m_hObject不空,其析构函式将删除对应的Windows GDI对象。MFC GDI对象和Windows GDI对象的关係如图2-5所示。
使用MFC GDI类的使用
创建GDI对象,可分一步或两步创建。一步创建就是构造MFC对象和Windows GDI对象一步完成;两步创建则先构造MFC对象,接着创建Windows GDI对象。然后,把新创建的GDI对象选进设备描述表,取代原GDI对象并保存。,恢复原GDI对象。例如
void CMyView::OnDraw(CDC pDC){CPen penBlack; //构造MFC CPen对象if (penBlack.CreatePen(PS_SOLID, RGB(0, 0, 0))){CPen pOldPen = pDC->SelectObject(&penBlack)); //选进设备表,保存原笔…pDC->SelectObject(pOldPen); //恢复原笔}else{…}}
和在SDK下有一点不同的是这里没有DeleteObject。因为执行完OnDraw后,栈中的penBlack被销毁,它的析构函式被调用,导致DeleteObject的调用。
还有一点要说明
pDC->SelectObject(&penBlack)返回了一个CPen 指针,也就是说,它根据原来PEN的句柄创建了一个MFC CPen对象。这个对象是否需要删除呢?不必要,因为它是一个临时对象,MFC框架会自动地删除它。,在本函式执行完毕把控制权返回给主讯息循环之前,该对象是有效的。
关于临时对象及MFC处理它们的内部机制,将在后续章节详细讨论。
至此,Windows编程的核心概念视窗、GDI界面(设备描述表、GDI对象等)已经陈述清楚,特别揭示了MFC对这些概念的封装机制,并简明讲述了与这些Windows Object对应的MFC类的使用方法。还有其他Windows概念,可以参见SDK开发文档。在MFC的实现上,基本上仅仅是对和这些概念相关的Win32函式的封装。如果明白了MFC的视窗、GDI界面的封装机制,其他就不难了。