友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
深入浅出MFC第2版(PDF格式)-第9部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
#0020 MENUITEM SEPARATOR
#0021 MENUITEM 〃E&xit〃; IDM_EXIT
#0022 END
#0023 POPUP 〃&Edit〃
#0024 BEGIN
#0025 MENUITEM 〃&UndotCtrl+Z〃; IDM_UNDO; GRAYED
#0026 MENUITEM SEPARATOR
#0027 MENUITEM 〃Cu&ttCtrl+X〃; IDM_CUT; GRAYED
#0028 MENUITEM 〃&CopytCtrl+C〃; IDM_COPY; GRAYED
#0029 MENUITEM 〃&PastetCtrl+V〃; IDM_PASTE; GRAYED
#0030 MENUITEM 〃Paste &Link〃; IDM_LINK; GRAYED
#0031 MENUITEM SEPARATOR
#0032 MENUITEM 〃Lin&ks。。。〃; IDM_LINKS; GRAYED
#0033 END
#0034 POPUP 〃&Help〃
#0035 BEGIN
#0036 MENUITEM 〃&Contents〃; IDM_HELPCONTENTS; GRAYED
#0037 MENUITEM 〃&Search for Help On。。。〃; IDM_HELPSEARCH; GRAYED
#0038 MENUITEM 〃&How to Use Help〃; IDM_HELPHELP; GRAYED
#0039 MENUITEM SEPARATOR
#0040 MENUITEM 〃&About Generic。。。〃; IDM_ABOUT
#0041 END
14
…………………………………………………………Page 77……………………………………………………………
#0042 END
#0043
#0044 AboutBox DIALOG DISCARDABLE 22; 17; 144; 75
#0045 STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
#0046 CAPTION 〃About Generic〃
#0047 BEGIN
#0048 CTEXT 〃Windows 95〃; …1;0; 5;144;8
#0049 CTEXT 〃Generic Application〃;…1;0;14;144;8
#0050 CTEXT 〃Version 1。0〃; …1;0;34;144;8
#0051 DEFPUSHBUTTON 〃OK〃; IDOK;53;59;32;14;WS_GROUP
#0052 END
程序进入点 WinMain
main C
是一般 程序的进入点:
int main (int argc; char *argv ' '; char *envp ' ');
{
。。。
}
WinMain 则是Windows 程序的进入点:
int CALLBACK WinMain (HINSTANCE hInstance; HINSTANCE hPrevInstance;
LPSTR lpCmdLine; int nCmdShow)
{
。。。
}
// Win32 CALLBACK __stdcall
在 中 被定义为 ,是一种函数调用习惯,关系到
// 参数挤压到堆栈的次序,以及处理堆栈的责任归属。其它的函数调用习惯还有
// _pascal _cdecl
和
当Windows 的「外壳」(shell,例如Windows 3。1 的程序管理员或Windows 95 的文件
总管)侦测到使用者意欲执行一个Windows 程序,于是调用加载器把该程序加载,然后
调用C startup code,后者再调用WinMain ,开始执进程序。WinMain 的四个参数由操作
系统传递进来。
15
…………………………………………………………Page 78……………………………………………………………
窗口类别之注册与窗口之诞生
一开始,Windows 程序必须做些初始化工作,为的是产生应用程序的工作舞台:窗口。
这没有什么困难,因为API 函数Create Window 完全包办了整个巨大的工程。但是窗口
产生之前,其属性必须先设定好。所谓属性包括窗口的「外貌」和「行为」,一个窗口
的边框、颜色、标题、位置等等就是其外貌,而窗口接收消息后的反应就是其行为(具
API RegisterClass
体地说就是指窗口函数本身)。程序必须在产生窗口之前先利用 函数
设定属性(我们称此动作为注册窗口类别)。RegisterClass 需要一个大型数据结构
WNDCLASS 做为参数,Create Window 则另需要11 个参数。
WNDCLASS wc; GENERIC。C
GENERIC。C LRESULT CALLBACK WndProc(HWND hWnd;
wc。style = CS_HREDRAW | CS_VREDRAW; 窗口函数 UINT message;
wc。lpfnWndProc = (WNDPROC)WndProc; WPARAM wParam;
wc。cbClsExtra = 0; LPARAM lParam)
wc。cbWndExtra = 0; {
wc。hInstance = hInstance; 。。。 GENERIC。C
wc。hIcon = LoadIcon(hInstance; 〃jjhouricon 〃); } GENERIC。C
wc。hCursor = LoadCursor(NULL; IDC_ARROW);
wc。hbrBackground = GetStockObject(WHITE_BRUSH); 窗口图标(icon)
wc。lpszMenuName = 〃GenericMenu〃;
jjhouricon ICON DISCARDABLE 〃jjhour。ico〃
wc。lpszClassName = 〃Generic〃; 菜单(menu) GenericMenu MENU DISCARDABLE
BEGIN
RegisterClass(&wc); POPUP 〃&File〃 GENERIC。RC
GENERIC。RC
窗口类別名称 。。。
HWND hWnd;
POPUP 〃&Edit〃
菜单(menu)
hWnd = CreateWindow( 。。。
〃Generic〃; POPUP 〃&Help〃}
。。。
〃 〃;
Generic Sample Application END
WS_OVERLAPPEDWINDOW; 窗口标睿╟aption)
CW_USEDEFAULT; // left
CW_USEDEFAULT; // top
CW_USEDEFAULT; // width
CW_USEDEFAULT; // height
NULL;
NULL;
height
hInstance;
NULL
);
width
图 1…3 RegisterClass 与 CreateWindow
16
…………………………………………………………Page 79……………………………………………………………
图 wc lpf nWndProc
从 1…3 可以清楚看出一个窗口类别牵扯的范围多么广泛,其中 。 所指
定的函数就是窗口的行为中枢,也就是所谓的窗口函数。注意,Create Window 只产生窗
口,并不显示窗口,所以稍后我们必须再利用ShowWindow 将之显示在屏幕上。又,我
们希望先传送个WM_PAINT 给窗口, 以驱动窗口的绘图动作, 所以调用
Updat e Window 。消息传递的观念暂且不表,稍后再提。
请注意,在Generic 程序中,RegisterClass 被我包装在InitAppl ication 函数之中,
Create Window 则被我包装在InitInstance 函数之中。这种安排虽非强制,却很普遍:
int CALLBACK WinMain (HINSTANCE hInstance; HINSTANCE hPrevInstance;
LPSTR lpCmdLine; int nCmdShow)
{
if (!hPrevInstance)
if (!InitApplication (hInstance))
return (FALSE);
if (!InitInstance (hInstance; nCmdShow))
return (FALSE);
。。。
}
//…………………………………………………………………………………………………………………………………
BOOL InitApplication (HINSTANCE hInstance)
{
WNDCLASS wc;
。。。
return (RegisterClass (&wc));
}
//…………………………………………………………………………………………………………………………………
BOOL InitInstance (HINSTANCE hInstance; int nCmdShow)
{
_hWnd = CreateWindow (。。。);
。。。
}
两个函数(InitAppl ication 和InitInstance )的名称别具意义:
Windows 3。x
■ 在 时代,窗口类别只需注册一次,即可供同一程序的后续每一个
instance
执行实例 ( )使用(之所以能够如此,是因为所有进程共在一个地址空
RegisterClass
间中),所以我们把 这个动作安排在「只有第一个执行个体才会
17
…………………………………………………………Page 80……………………………………………………………
进入」的InitAppl ication 函数中。至于此一进程是否是某个程序的第一个执行
实例,可由WinMain 的参数hPrevInstance 判断之;其值由系统传入。
■ 产生窗口, 是每一个执行实例( instance ) 都得做的动作, 所以我们把
Create Window 这个动作安排在「任何执行实例都会进入」的InitInstance 函数中。
Windows NT Windows 95 Win32
以上情况在 和 中略有变化。由于 程序的每一个执行实
instance Win32
例 ( )有自己的地址空间,共享同一窗口类别已不可能。但是由于 系统令
hPrevInstance 0 RegisterClass Create Window
永远为 ,所以我们仍然得以把 和 按旧习惯
安排。既符合了新环境的要求,又兼顾到了旧源代码的兼容。
InitAppl ication 和InitInstance 只不过是两个自定函数,为什么我要对此振振有词呢?原
因是MFC 把这两个函数包装成CWinApp 的两个虚拟成员函数。第6章「MFC 程序的
生与死」对此有详细解释。
消息循环
初始化工作完成后,WinMain 进入所谓的消息循环:
while (GetMessage (&msg;。。。)) {
TranslateMessage (&msg); // 转换键盘消息
DispatchMessage (&msg); // 分派消息
}
其中的TranslateMessage 是为了将键盘消息转化,Dispat chMessage 会将消息传给窗口函
数去处理。没有指定函数名称,却可以将消息传送过去,岂不是很玄?这是因为消息发
生之时,操作系统已根据当时状态,为它标明了所属窗口,而窗口所属之窗口类别又已
wc lpf nWndProc Dispat chMessage
。 )
经明白标示了窗口函数(也就是 所指定的函数 ,所以
图 所示 Dispa tchMessage USER
自有脉络可寻。请注意 1…2 , 经过 模块的协助,才把消
息交到窗口函数手中。
消息循环中的GetMessage 是Windows 3。x 非强制性(non…preemptive )多任务的关键。应
用程序藉由此动作,提供了释放控制权的机会:如果消息队列上没有属于我的消息,我
就把机会让给别人。透过程序之间彼此协调让步的方式,达到多任务能力。Windows 95 和
…………………………………………………………Page 81……………………………………………………………
Windows NT 具备强制性(preemptive )多任务能力,不再非靠GetMessage 释放CPU 控
制权不可,但程序写法依然不变,因为应用程序仍然需要靠消息推动。它还是需要抓消
息!
窗口的生命中枢 :窗口函数
消息循环中的Dispat chMessage 把消息分配到哪里呢?它透过USER 模块的协助,送到
switch case
该窗口的窗口函数去了。窗口函数通常利用 / 方式判断消息种类,以决定处置
方式。由于它是被Windows 系统所调用的(我们并没有在应用程序任何地方调用此函
数),所以这是一种call back 函数,意思是指「在你的程序中,被Windows 系统调用」
的函数。这些函数虽然由你设计,但是永远不会也不该被你调用,它们是为Windows 系
统准备的。
程序进行过程中,消息由输入装置,经由消息循环的抓取,源源传送给窗口并进而送到
窗口函数去。窗口函数的体积可能很庞大,也可能很精简,依该窗口感兴趣的消息数量
多寡而定。至于窗口函数的形式,相当一致,必然是:
LRESULT CALLBACK WndProc (HWND hWnd;
UINT message;
WPARAM wParam;
LPARAM lParam)
switch case def ault
注意,不论什么消息,都必须被处理,所以 / 指令中的 : 处必须调用
Def WindowProc,这是Windows 内部预设的消息处理函数。
窗口函数的wParam 和lParam 的意义,因消息之不同而异。wParam 在16 位环境中是
16 位,在32 位环境中是32 位。因此,参数内容(格式)在不同操作环境中就有
了变化。
我想很多人都会问这个问题:为什么Windows Programming Modal 要把窗口函数设计为
一个call back 函数?为什么不让程序在抓到消息(GetMessage )之后直接调用它就好
了?原因是,除了你需要调用它,有很多时候操作系统也要调用你的窗口函数(例如当
19
…………………………………………………………Page 82……………………………………………………………
某个消息产生或某个事件发生)。窗口函数设计为callback 形式,才能开放出一个接口
给操作系统叫用。
消息映射 ( )的雏形
Message Map
有没有可能把窗口函数的内容设计得更模块化、更一般化些?下面是一种作法。请注意,
MFC
以下作法是 「消息映射表格」(第9章)的雏形,我所采用的结构名称和变量名称,
都与MFC 相同,藉此让你先有个暖身。
MSGMAP ENTR Y dim
_
首先,定义一个 结构和一个 宏:
struct MSGMAP_ENTRY {
UINT nMessage;
LONG (*pfn)(HWND; UINT; WPARAM; LPARAM);
};
#define dim (x) (sizeof(x) / sizeof(x '0'))
MSGMAP_ENTR Y pf n
请注意 的第二元素 是一个函数指针,我准备以此指针所指之函
数处理nMessage 消息。这正是对象导向观念中把「资料」和「处理资料的方法」封装
起来的一种具体实现,只不过我们用的不是C++ 语言。
接下来,组织两个数组_messageEntries ' ' 和_mandEntries ' ',把程序中欲处理的消
息以及消息处理例程的关联性建立起来:
// 消息与处理例程之对照表格
struct MSGMAP_ENTRY _
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!