本人对Windows系统、MFC谈不上有深入的了解,但对MFC本身包装API的机制很有兴趣,特别是读了候老师的《深入浅出 MFC》后,感觉到VISUAL C++的Application FrameWork十分精制[不敢用“完美”一词]。在以前,我对SDI结构处理消息有一定的认识,但对于模式对话框的消息机制不了解,读了《深入》一书 也没能得到解决,近日,通过在CSDN上网友的帮助,和查阅MSDN,自认为已经了解。一时兴起,写下这些文字,没有其它目的,只是希望让后来者少走弯 路,也希望和我一样喜欢“钻牛角尖”的人共同讨论、学习。如果你是牛人,那么你现在要慎重考虑有没有充足的时间读这些幼稚文字[何出此言?请大家看一下:http://www.csdn.net/expert/topic/13/13451.xml的相关评论]。本文中有些“理论”是我自己胡乱猜测,还请大家指正.[文中内容有些不可避免的会和《深入》一书某些内容重复]。
正文:
Windows程序和DOS程序的主要不同点之一是:Windows程序是以事件为驱动、消息机制为基础。如何理解?
举了例子,当你CLICK Windows “开始”BUTTON时,为什么就会弹出一个菜单呢?
当你单击鼠标左键时,操作系统中与MOUSE相关的驱动程序在第一时间内得到这个信号[LBUTTONDOWN],然后它通知操作系统―――“嗨,鼠标左键被单击了!”,操作系统得到这一信号后,马上要判断――用户单击鼠标左键,这是针对哪个窗口呢?如何判断?这很简单!当前状态中,具有焦点的窗口[或控件]就是了[这里当然是“开始”BUTTON了]。然后操作系统马上向这个窗口发送一条消息到这个窗口所在进程的消息队列,消息内容应是消息本身的代号、附加参数、窗口句柄…等等了。那么,只有操作系统才有资格发送消息至某一窗口的消息队列吗?不然,其它程序也有资格。你可以在你的程序中调用:SendMessage 、PostMessage。这样,被单击的窗口得到了一条由操作系统发送的包含CLICK的消息,操作系统已经暂时不再管窗口的任何事,因为它还要忙于处理其它事务。你的程序得到一条消息后如何做呢?Windows对于你在“开始”BUTTON上的单击事件做出如下反映:弹出一菜单。可是,得到消息到做出反映这一过程是如何实现的呢?这就是本文讨论的主要内容[当然只是针对MFC了]。
我首先简要谈一下SDI,然后会花更多文字描述模式对话框。
对于SDI窗口,你的应用程序类的InitInstance()大约如下:
BOOL CEx06aApp::InitInstance()
{ ……………
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CEx06aDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CEx06aView));
AddDocTemplate(pDocTemplate);
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (!ProcessShellCommand(cmdInfo))
return FALSE;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
完成一些如动态生成相关文档、视,显示主框架窗口、处理参数行信息等工作。这些都是显示在你工程中的“明码”。我们现在把断点设置到return TRUE;一句,跟入MFC源码中,看看到底MFC内部做了什么。
程序进入SRC\WinMain.cpp,下一个大动作应是:
nReturnCode = pThread->Run();
各位看官注意了,重点来了。F11进入
int CWinApp::Run()
{
if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
{
// Not launched /Embedding or /Automation, but has no main window!
TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.\n");
AfxPostQuitMessage(0);
}
return CWinThread::Run();
}
再次F11进入:
int CWinThread::Run()
{
ASSERT_VALID(this);
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
// acquire and dispatch messages until a WM_QUIT message is received.
for (;;)
{
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{
// call OnIdle while in bIdle state
if (!OnIdle(lIdleCount++))
bIdle = FALSE; // assume "no idle" state
}
// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE); // not reachable
}
BOOL CWinThread::IsIdleMessage(MSG* pMsg)
{
// Return FALSE if the message just dispatched should _not_
// cause OnIdle to be run. Messages which do not usually
// affect the state of the user interface and happen very
// often are checked for.
// redundant WM_MOUSEMOVE and WM_NCMOUSEMOVE
if (pMsg->message == WM_MOUSEMOVE || pMsg->message == WM_NCMOUSEMOVE)
{
// mouse move at same position as last mouse move?
if (m_ptCursorLast == pMsg->pt && pMsg->message == m_nMsgLast)
return FALSE;
m_ptCursorLast = pMsg->pt; // remember for next time
m_nMsgLast = pMsg->message;
return TRUE;
}
// WM_PAINT and WM_SYSTIMER (caret blink)
return pMsg->message != WM_PAINT && pMsg->message != 0x0118;
}
这是SDI处理消息的中心机构,但请注意,它觉对不是核心!
分析一下,在无限循环FOR内部又出现一个WHILE循环
while (bIdle &&
!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{
// call OnIdle while in bIdle state
if (!OnIdle(lIdleCount++))
bIdle = FALSE; // assume "no idle" state
}
这段代码是当你程序进程的消息队列中没有消息时,会调用OnIdle做一些后备工作,
临 时对象在这里被删除。当然它是虚函数。其中的PeekMessage,是查看消息队列,如果有消息返回TRUE,如果没有消息返回FALSE,这里指定 PM_NOREMOVE,是指查看过后不移走消息队列中刚刚被查看到的消息,也就是说这里的PeekMessage只起到一个检测作用,显然返回 FALSE时[即没有消息],才会进入循环内部,执行OnIdle,当然了,你的OnIdle返回FLASE,会让程序不再执行OnIdle。你可能要问:
当bidle=0或消息队例中有消息时,程序又执行到哪了呢?
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
看啊,又进入一个循环!
其中有个重要的函数,PumpMessage,内容如下:
BOOL CWinThread::PumpMessage()
{
ASSERT_VALID(this);
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
{
#ifdef _DEBUG
if (afxTraceFlags & traceAppMsg)
TRACE0("CWinThread::PumpMessage - Received WM_QUIT.\n");
m_nDisablePumpCount++; // application must die
// Note: prevents calling message loop things in 'ExitInstance'
// will never be decremented
#endif
return FALSE;
}
#ifdef _DEBUG
if (m_nDisablePumpCount != 0)
{
TRACE0("Error: CWinThread::PumpMessage called when not permitted.\n");
ASSERT(FALSE);
}
#endif
#ifdef _DEBUG
if (afxTraceFlags & traceAppMsg)
_AfxTraceMsg(_T("PumpMessage"), &m_msgCur);
#endif
// process this message
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
如你所想,这才是MFC消息处理的核心基地[也是我个人认为的]。
GetMessage 不同于PeekMessae,它是不得到消息不罢体,PeekMessage如果发现消息队列中没有消息会返回0,而GetMessage如果发现没有消 息,等,直到有了消息,而且,GetMessage不同于PeekMessage,它会将消息移走[当然,PeekMessage也可以做到这点]。我想 当你读了这个函数后,你应明白PreTranslateMessage函数的用法了吧[我比较喜欢在程序中充分利用这个函数]。
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
将消息发送到窗口的处理函数[它是由窗口类指定的],之后的动作一直到你的程序做出反映的过程,你可以在《深入》一书中得到完美的解释。我们还是通过reurn TRUE;回到CWinThread::Run()中的Do{}while;循环。然后还是对IDLE的处理,即便刚才你的ONIDLE返回了FALSE,在这里你看到,你的程序还是有机会执行它的。然后又是利用PeekMessage检测消息队列:
如果有消息[这个消息不被移动的原因是因为它要为PumpMessage内的GetMessage所利用。]再次进入PumpMessage[叫它“消息泵”吧]。
如果没有消息,退出DO循环,但它还在FOR内部,所以又执行第一个While循环。
这是CwinThread::Run的一个执行过程。
不用担心退不出for(;;)如果你的消息队列中有一条WM_QUIT,会使GetMessage返回0,然后PumpMessage返回0而RUN()内部:
if (!PumpMessage())
return ExitInstance();。
SDI就说到这,下面我来谈一下模式对话框。我分2种情况讨论:
一当你的工程以模式对话框为基础时[没父窗口,或为桌面]。
与SDI不同处在于,在应用程序类的InItInstance内部:
BOOL CComboBoxApp::InitInstance()
{
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
this->m_nCmdShow = SW_HIDE;
CComboBoxDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
int nResponse = dlg.DoModal();一句使你的整个程序都在DoModal()内部进行。而且,你退出DoMal()时[你一定结束了你的对话框],InitInstance返回的是False,我们知道,这样,CwinThread::Run是不会执行的。
但对话框程序是在哪里进行消息处理的呢。
原来,dlg.DoModal()内部会调用CwinThread::RunModalLoop,它起到的作用和RUN()是一样的[当然内部有细小差别,请参考MSDN]!!!
第二种情况,你是在SDI[或其它]程序中调用Dlg.DoModal() 产生了一模式对话框[有父窗口].
这又是如何运作的呢?
建了这样一个工程做为例子。
SDI,在View中处理LBUTTONDOWN:
MyDLg.DoModal();
MyDLg内有按钮,以惫后用.
没有显示模式对话框前,消息处理一直在Cthread::Run()中进行.
你单击后,程序执行点进入DoModal()内部的RunModalLoop,这又是一个消息处理机制.
不过DoModal()中调用RunModalLoop,前会Disable掉它的父窗口.从RunModalLoop中出来后,再 Enable它.
模式对话框和非模式对话框都是通过调用CreateDialogIndirect()产生创建对话框.那它和非模式对话框区别是什么造成的呢?
1 模式对话框将父窗口DISABLE掉.
我 原以为被Disable的窗口是不接收消息的.但后来我马上发现我是错的.但,为什么你对被Disable的窗口进行KeyBorad,Mouse动作 时,窗口没反映呢,我想,这可能是操作系统从中搞的鬼.我在本文一开始,就写出操作系统向窗口发送消息的过程,我想当它发现目标窗口处理Disabled 状态时,不会将消息发送给它,但这不能说窗口不接收消息,其它程序[或它本身]发送给它的消息还是可以接收并处理的.
2 模式对话框本身有消息处理机制 RunModalLoop.
对以上两点加以实验.
我在我的刚才建的项目中的模式对话框中加上一个BUTTON,其中加入如下代码:
OnButton1()
{
GetParaent()->EnableWindow(1);
}
单击,后我们发现,此时它已经不再表现为”模态”,我试着点击菜单,还是会作出正常反映.
我想这此消息[对于父窗口的,如:菜单动作]的处理也应是在模式对话框中的RunModalLoop中进行处理的吧[这点我不能确定].
先写到这里吧,一点浊见.请大家批评.
分享到:
相关推荐
要想熟练掌握 Windows 应用程序的开发,首先需要理解Windows 平台下程序运行的 内部机制。市面上很多介绍Visual C++开发的书籍...部运行机制,为读者扫清VC++学习路途中的第一个障碍,为进一步学习MFC 程序打下基 础。
要想熟练掌握 Windows 应用程序的开发,首先需要理解Windows 平台下程序运行的 内部机制。市面上很多介绍Visual C++开发的书籍...部运行机制,为读者扫清VC++学习路途中的第一个障碍,为进一步学习MFC 程序打下基 础。
感谢网络前辈的无私分享,再整理一下 WTL 的学习文档。 所有源码都能在 VC6,WTL7.0 下编译通过。 具体内容如下: 1、WTL个性设置demo 文档和源码。...10、深入剖析MFC中对于Windows消息处理、运行机制.doc
第3章 MFC中的消息处理 CCmdTarget和消息映射表 窗口消息 MFC消息映射内幕 MFC如何使用消息映射表 进入消息循环:PreTranslateMessage() 结语 第4章 MFC实用类 简单值类型 MFC的集合类 CFile家族:MFC对文件的访问 ...
消息循环 窗口的生命中枢:窗口函数 消息映射(Message Map)的雏形 对话框的运行 模块定义文件(.DEF) 资源描述档(.RC) Widnows程序的生与死 空闲时间的处理:OnIdle Console程序 Console程序与DOS程序的差别 ...
深入浅出MFC(第二版) 目录 第0章 你一定要知道(导读) 这本书适合谁 你需要什么技术基础 你需要什么软硬件环境 让我们使用同一种语言 本书符号习惯 本书例程的取得 范例程序说明 与前版本之差异 如何联络作者 第...
深入浅出MFC(第二版) 目录 第0章 你一定要知道(导读) 这本书适合谁 你需要什么技术基础 你需要什么软硬件环境 让我们使用同一种语言 本书符号习惯 本书例程的取得 范例程序说明 与前版本之差异 如何联络作者 第...
介绍了Windows程序运行的内部机制,剖析了MFC框架的运行机制
这意味着Windows不使用系统定时器将处理时间分配给系统中运行的多个应用程序,程序必须自愿放弃控制以便其它程序运行。在Windows NT和Windows 98中,多任务是优先权式的,而且程序自身可分割成近乎同时执行的多个...
C/VC++教程,深入详解,VC教程 VC++深入详解 pdf,要想熟练掌握 Windows 应用程序的开发,首先需要...本章将深入剖析Windows 程序的内部运行机制,为读者扫清VC++学习路途中的第一个障碍,为进一步学习MFC 程序打下基矗
第四课:MFC消息映射机制的剖析,讲述如何运用ClassWizard 第五课:文本编程 第六课:菜单的工作原理及编写应用 第七课:对话框用户界面程序的编写 第八课:对话框第二部分 第九课:如何修改MFC AppWizard向导生成的框架...