- 浏览: 11881604 次
-
文章分类
最新评论
-
wahahachuang8:
我觉得这种东西自己开发太麻烦了,就别自己捣鼓了,找个第三方,方 ...
WebSocket和node.js -
xhpscdx:
写的这么详细,全面,对架构师的工作职责,个人能力都进行了梳理。 ...
架构师之路---王泽宾谈架构师的职责 -
xgbzsc:
是http://www.haoservice.com 吗?
android WIFI定位 -
lehehe:
http://www.haoservice.com/docs/ ...
android WIFI定位 -
lehehe:
http://www.haoservice.com/docs/ ...
android WIFI定位
WTL for MFC Programmers, Part II - WTL GUI Base Classes
WTL for MFC Programmers, Part II - WTL GUI Base Classes
原作 :Michael Dunn [英文原文]
翻译 :Orbit(星轨 oRbIt) [http://www.winmsg.com/cn/orbit.htm]
![]() |
本章内容
- 对第二部分的介绍
- WTL 的总体印象
- 开始写WTL程序
- WTL 对消息映射链的增强
- 从WTL的应用程序生成向导能得到什么
- CMessageLoop 的内部实现
- CFrameWindowImpl 的内部实现
- 回到前面的时钟程序
- UI状态的自动更新
- 消息映射链(Message Maps)中最后需要注意的地方
- 下一站,1995
- 修改记录
对第二部分的介绍
好了,现在正式开始介绍WTL!在这一部分我讲的内容包括生成一个基本的主窗口和WTL提供的一些友好的改进,比如UI界面的更新(如菜单上的选择标记)和更好的消息映射机制。为了更好地掌握本章的内容,你应该安装WTL并将WTL库的头文件目录添加到VC的搜索目录中,还要将WTL的应用程序生成向导复制到正确的位置。WTL的发布版本中有文档具体介绍如何做这些设置,如果遇到困难可以查看这些文档。
WTL 总体印象
WTL的类大致可以分为几种类型:
- 主框架窗口的实现
-
CFrameWindowImpl, CMDIFrameWindowImpl - 控件的封装- CButton, CListViewCtrl
- GDI 对象的封装- CDC, CMenu
- 一些特殊的界面特性 - CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw
- 实用的工具类和宏- CString, CRect, BEGIN_MSG_MAP_EX
本篇文章将深入地介绍框架窗口类,还将简要地讲一下有关的界面特性类和工具类,这些界面特性类和工具类中绝大多数都是独立的类,尽管有一些是嵌入类,例如:CDialogResize。
开始写WTL程序
如果你没有用WTL的应用程序生成向导也没关系(我将在后面介绍这个向导的用法), WTL的程序的代码结构很像ATL的程序,本章使用的例子代码有别于第一章的例子,主要是为了显示WTL的特性,没有什么实用价值。
这一节我们将在WTL生成的代码基础上添加代码,生成一个新的程序,程序主窗口的客户区显示当前的时间。stdafx.h的代码如下:
#define STRICT #define WIN32_LEAN_AND_MEAN #define _WTL_USE_CSTRING #include <atlbase.h> // 基本的ATL类 #include <atlapp.h> // 基本的WTL类
extern CAppModule _Module; // WTL 派生的CComModule版本 #include <atlwin.h> // ATL 窗口类 #include <atlframe.h> // WTL 主框架窗口类 #include <atlmisc.h> // WTL 实用工具类,例如:CString #include <atlcrack.h> // WTL 增强的消息宏
atlapp.h 是你的工程中第一个包含的头文件,这个文件内定义了有关消息处理的类和CAppModule,CAppModule是从CComModule派生的类。如果你打算使用CString类,你需要手工定义_WTL_USE_CSTRING标号,因为CString类是在atlmisc.h中定义的,而许多包含在atlmisc.h之前的头文件都会用到CString,定义_WTL_USE_CSTRING之后,atlapp.h就会向前声明CString类,其他的头文件就知道CString类的存在,从而避免编译器为此大惊小怪。
接下来定义框架窗口。我们的SDI窗口是从CFrameWindowImpl派生的,在定义窗口类时使用DECLARE_FRAME_WND_CLASS代替前面使用的DECLARE_WND_CLASS。下面时MyWindow.h中窗口定义的开始部分:
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
DECLARE_FRAME_WND_CLASS(_T("First WTL window"), IDR_MAINFRAME);
BEGIN_MSG_MAP(CMyWindow)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
};
DECLARE_FRAME_WND_CLASS有两个参数,窗口类名(类名可以是NULL,ATL会替你生成一个类名)和资源ID,创建窗口时WTL用这个ID装载图标,菜单和加速键表。我们还要象CFrameWindowImpl中的消息处理(例如WM_SIZE和WM_DESTROY消息)那样将消息链入窗口的消息中。
现在来看看WinMain()函数,它和第一部分中的例子代码中的WinMain()函数几乎一样,只是创建窗口部分的代码略微不同。
// main.cpp:
#include "stdafx.h"
#include "MyWindow.h"
CAppModule _Module;
int APIENTRY WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
{
_Module.Init ( NULL, hInstance );
CMyWindow wndMain;
MSG msg;
// Create the main window
if ( NULL == wndMain.CreateEx() )
return 1; // Window creation failed
// Show the window
wndMain.ShowWindow ( nCmdShow );
wndMain.UpdateWindow();
// Standard Win32 message loop
while ( GetMessage ( &msg, NULL, 0, 0 ) > 0 )
{
TranslateMessage ( &msg );
DispatchMessage ( &msg );
}
_Module.Term();
return msg.wParam;
}
CFrameWindowImpl中的CreateEx()函数的参数使用了常用的默认值,所以我们不需要特别指定任何参数。正如前面介绍的,CFrameWindowImpl会处理资源的装载,你只需要使用IDR_MAINFRAME作为ID定义你的资源就行了(译者注:主要是图标,菜单和加速键表),你也可以直接使用本章的例子代码。
如果你现在就运行程序,你会看到主框架窗口,事实上它没有做任何事情。我们需要手工添加一些消息处理,所以现在是介绍WTL的消息映射宏的最佳时间。
WTL 对消息映射的增强
将Win32 API通过消息传递过来的WPARAM和LPARAM数据还原出来是一件麻烦的事情并且很容易出错,不幸得是ATL并没有为我们提供更多的帮助,我们仍然需要从消息中还原这些数据,当然WM_COMMAND和WM_NOTIFY消息除外。但是WTL的出现拯救了这一切!
WTL的增强消息映射宏定义在atlcrack.h中。(这个名字来源于“消息解密者”,是一个与windowsx.h的宏所使用的相同术语)首先将BEGIN_MSG_MAP改为BEGIN_MSG_MAP_EX,带_EX的版本产生“解密”消息的代码。
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public: BEGIN_MSG_MAP_EX(CMyWindow) CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
};
对于我们的时钟程序,我们需要处理WM_CREATE消息来设置定时器,WTL的消息处理使用MSG_作为前缀,后面是消息名称,例如MSG_WM_CREATE。这些宏只是代表消息响应处理的名称,现在我们来添加对WM_CREATE消息的响应:
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow) MSG_WM_CREATE(OnCreate) CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
// OnCreate(...) ? };
WTL的消息响应处理看起来有点象MFC,每一个处理函数根据消息传递的参数不同也有不同的原型。由于我们没有向导自动添加消息响应,所以我们需要自己查找正确的消息处理函数。幸运的是VC可以帮我们的忙,将鼠标光标移到“MSG_WM_CREATE”宏的文字上按F12键就可以来到这个宏的定义代码处。如果是第一次使用这个功能,VC会要求从新编译全部文件以建立浏览信息数据库(browse info database),建立了这个数据库之后,VC会打开atlcrack.h并将代码定位到MSG_WM_CREATE的定义位置:
#define MSG_WM_CREATE(func) / if (uMsg == WM_CREATE) /
{ /
SetMsgHandled(TRUE); / lResult = (LRESULT)func((LPCREATESTRUCT)lParam); / if(IsMsgHandled()) /
return TRUE; /
}
标记为红色的那一行非常重要,就是在这里调用实际的消息响应函数,他告诉我们消息响应函数有一个LPCREATESTRUCT
类型的参数,返回值的类型是LRESULT。请注意这里没有ATL的宏所用的 bHandled 参数,SetMsgHandled()
函数代替了这个参数,我会对此作些简要的介绍。
现在为我们的窗口类添加OnCreate()响应函数:
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP() LRESULT OnCreate(LPCREATESTRUCT lpcs)
{
SetTimer ( 1, 1000 );
SetMsgHandled(false);
return 0;
} };
CFrameWindowImpl 是直接从CWindow类派生的, 所以它继承了所有CWindow类的方法,如SetTimer()。这使得对窗口API的调用有点象MFC的代码,只是MFC使用CWnd类包装这些API。
我们使用SetTimer()函数创建一个定时器,它每隔一秒钟(1000毫秒)触发一次。由于我们需要让CFrameWindowImpl也处理WM_CREATE消息,所以我们调用SetMsgHandled(false),让消息通过CHAIN_MSG_MAP宏链入基类,这个调用代替了ATL宏使用的bHandled参数。(即使CFrameWindowImpl类不需要处理WM_CREATE消息,调用SetMsgHandled(false)让消息流入基类是个好的习惯,因为这样我们就不必总是记着哪个消息需要基类处理那些消息不需要基类处理,这和VC的类向导产生的代码相似,多数的派生类的消息处理函数的开始或结尾都会调用基类的消息处理函数)
为了能够停止定时器我们还需要响应WM_DESTROY消息,添加消息响应的过程和前面一样,MSG_WM_DESTROY宏的定义是这样的:
#define MSG_WM_DESTROY(func) / if (uMsg == WM_DESTROY) /
{ /
SetMsgHandled(TRUE); / func(); / lResult = 0; /
if(IsMsgHandled()) /
return TRUE; /
}
OnDestroy()函数没有参数也没有返回值,CFrameWindowImpl也要处理WM_DESTROY消息,所以还要调用SetMsgHandled(false):
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate) MSG_WM_DESTROY(OnDestroy) CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
void OnDestroy()
{
KillTimer(1);
SetMsgHandled(false);
}
};
接下来是响应WM_TIMER消息的处理函数,它每秒钟被调用一次。你应该知道怎样使用F12键的窍门了,所以我直接给出响应函数的代码:
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy) MSG_WM_TIMER(OnTimer) CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP() void OnTimer ( UINT uTimerID, TIMERPROC pTimerProc )
{
if ( 1 != uTimerID )
SetMsgHandled(false);
else RedrawWindow(); } };
这个响应函数只是在每次定时器触发时重画窗口的客户区。最后我们要响应WM_ERASEBKGND消息,在窗口客户区的左上角显示当前的时间。
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_TIMER(OnTimer) MSG_WM_ERASEBKGND(OnEraseBkgnd) CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP() LRESULT OnEraseBkgnd ( HDC hdc )
{
CDCHandle dc(hdc);
CRect rc;
SYSTEMTIME st;
CString sTime;
// Get our window's client area. GetClientRect ( rc ); // Build the string to show in the window. GetLocalTime ( &st ); sTime.Format ( _T("The time is %d:%02d:%02d"),
st.wHour, st.wMinute, st.wSecond );
// Set up the DC and draw the text. dc.SaveDC(); dc.SetBkColor ( RGB(255,153,0);
dc.SetTextColor ( RGB(0,0,0) );
dc.ExtTextOut ( 0, 0, ETO_OPAQUE, rc, sTime,
sTime.GetLength(), NULL );
// Restore the DC. dc.RestoreDC(-1);
return 1; // We erased the background (ExtTextOut did it) } };
这个消息处理函数不仅使用了CRect和CString类,还使用了一个GDI包装类CDCHandle。对于CString类我想说的是它等同与MFC的CString类,我在后面的文章中还会介绍这些包装类,现在你只需要知道CDCHandle是对HDC的简单封装就行了,使用方法与MFC的CDC类相似,只是CDCHandle的实例在超出作用域后不会销毁它所操作的设备上下文。
所有的工作完成了,现在看看我们的窗口是什么样子:
例子代码中还使用了WM_COMMAND响应菜单消息,在这里我不作介绍,但是你可以查看例子代码,看看WTL的COMMAND_ID_HANDLER_EX宏是如何工作的。
从WTL的应用程序生成向导能得到什么
WTL的发布版本附带一个很棒的应用程序生成向导,让我们以一个SDI 应用为例看看它有什么特性。
使用向导的整个过程
在VC的IDE环境下单击File|New菜单,从列表中选择ATL/WTL AppWizard,我们要重写时钟程序,所以用WTLClock作为项目的名字:
在下一页你可以选择项目的类型,SDI,MDI或者是基于对话框的应用,当然还有其它选项,如下图所示设置这些选项,然后点击“下一步”:
在最后一页你可以选择是否使用toolbar,rebar和status bar,为了简单起见,取消这些选项并单击“结束”。
查看生成的代码
向导完成后,在生成的代码中有三个类:CMainFrame, CAboutDlg, 和CWTLClockView,从名字上就可以猜出这些类的作用。虽然也有一个是视图类,但它仅仅是从CWindowImpl派生出来的一个简单的窗口类,没有象MFC那样的文档/视图结构。
还有一个_tWinMain()函数,它先初始化COM环境,公用控件和_Module,然后调用全局函数Run()。Run()函数创建主窗口并开始消息循环,Run()调用CMessageLoop::Run(),消息泵实际上是位于CMessageLoop::Run()内,我将在下一个章节介绍CMessageLoop的更多细节。
CAboutDlg是CDialogImpl的派生类,它对应于ID IDD_ABOUTBOX资源,我在第一部分已经介绍过对话框,所以你应该能看懂CAboutDlg的代码。
CWTLClockView是我们的程序的视图类,它的作用和MFC的视图类一样,没有标题栏,覆盖整个主窗口的客户区。CWTLClockView类有一个PreTranslateMessage()函数,也和MFC中的同名函数作用相同,还有一个WM_PAINT的消息响应函数。这两个函数都没有什么特别之处,只是我们会填写OnPaint()函数来显示时间。
最后是我们的CMainFrame类,它有许多有趣的新东西,这是这个类的定义缩略版本:
class CMainFrame : public CFrameWindowImpl<CMainFrame>,
public CUpdateUI<CMainFrame>,
public CMessageFilter,
public CIdleHandler
{
public:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
CWTLClockView m_view;
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL OnIdle();
BEGIN_UPDATE_UI_MAP(CMainFrame)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CMainFrame)
// ... CHAIN_MSG_MAP(CUpdateUI<CMainFrame>) CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) END_MSG_MAP() };
CMessageFilter是一个嵌入类,它提供PreTranslateMessage()函数,CIdleHandler也是一个嵌入类,它提供了OnIdle()函数。CMessageLoop, CIdleHandler 和 CUpdateUI三个类互相协同完成界面元素的状态更新(UI update),就像MFC中的ON_UPDATE_COMMAND_UI宏一样。
CMainFrame::OnCreate()中创建了视图窗口并保存这个窗口的句柄,当主窗口改变大小时视图窗口的大小也会随之改变。OnCreate()函数还将CMainFrame对象添加到由CAppModule维持的消息过滤器队列和空闲处理队列,我将在稍后介绍这些。
LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, |
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
// register object for message filtering and idle updates CMessageLoop* pLoop = _Module.GetMessageLoop(); pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
return 0;
}
m_hWndClient是CFrameWindowImpl对象的一个成员变量,当主窗口大小改变时此窗口的大小也将改变。
在生成的CMainFrame中还添加了对File|New, File|Exit, 和 Help|About菜单消息的处理。我们的时钟程序不需要这些默认的菜单项,但是现在将它们留在代码中也没有害处。现在可以编译并运行向导生成的代码,不过这个程序确实没有什么用处。如果你感兴趣的话可以深入CMainFrame::CreateEx()函数的内部看看主窗口和它的资源是如何被加载和创建得。
我们的下一步WTL之旅是CMessageLoop,它掌管消息泵和空闲处理。
CMessageLoop 的内部实现
CMessageLoop为我们的应用程序提供一个消息泵,除了一个标准的DispatchMessage/TranslateMessage循环外,它还通过调用PreTranslateMessage()函数实现了消息过滤机制,通过调用OnIdle()实现了空闲处理功能。下面是Run()函数的伪代码:
int Run()
{
MSG msg;
for(;;)
{
while ( !PeekMessage(&msg) )
DoIdleProcessing();
if ( 0 == GetMessage(&msg) )
break; // WM_QUIT retrieved from the queue if ( !PreTranslateMessage(&msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
那些需要过滤消息的类只需要象CMainFrame::OnCreate()函数那样调用CMessageLoop::AddMessageFilter()函数就行了,CMessageLoop就会知道该调用那个PreTranslateMessage()函数,同样,如果需要空闲处理就调用CMessageLoop::AddIdleHandler()函数。
需要注意得是在这个消息循环中没有调用TranslateAccelerator() 或 IsDialogMessage() 函数,因为CFrameWindowImpl在这之前已经做了处理,但是如果你在程序中使用了非模式对话框,那你就需要在CMainFrame::PreTranslateMessage()函数中添加对IsDialogMessage()函数的调用。
CFrameWindowImpl 的内部实现
CFrameWindowImpl 和它的基类 CFrameWindowImplBase提供了对toolbars,rebars, status bars,工具条按钮的工具提示和菜单项的掠过式帮助,这些也是MFC的CFrameWnd类的基本特征。我会逐步介绍这些特征,完整的讨论CFrameWindowImpl类需要再写两篇文章,但是现在看看CFrameWindowImpl是如何处理WM_SIZE和它的客户区就足够了。需要记住一点前面提到的东西,m_hWndClient是CFrameWindowImplBase类的成员变量,它存储主窗口内的“视图”窗口的句柄。
CFrameWindowImpl类处理了WM_SIZE消息:
LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if(wParam != SIZE_MINIMIZED)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
}
bHandled = FALSE;
return 1;
}
它首先检查窗口是否最小化,如果不是就调用UpdateLayout(),下面是UpdateLayout():
void UpdateLayout(BOOL bResizeBars = TRUE)
{
RECT rect;
GetClientRect(&rect);
// position bars and offset their dimensions UpdateBarsPosition(rect, bResizeBars); // resize client window if(m_hWndClient != NULL)
::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER | SWP_NOACTIVATE);
}
注意这些代码是如何使用m_hWndClient得,既然m_hWndClient是一般窗口的句柄,它就可能是任何窗口,对这个窗口的类型没有限制。这一点不像MFC,MFC在很多情况下需要CView的派生类(例如分隔窗口类)。如果你回过头看看CMainFrame::OnCreate()就会看到它创建了一个视图窗口并赋值给m_hWndClient,由m_hWndClient确保视图窗口被设置为正确的大小。
回到前面的时钟程序
现在我们已经看到了主窗口类的一些细节,现在回到我们的时钟程序。视图窗口用来响应定时器消息并负责显示时钟,就像前面的CMyWindow类。下面是这个类的部分定义:
class CWTLClockView : public CWindowImpl<CWTLClockView>
{
public:
DECLARE_WND_CLASS(NULL)
BOOL PreTranslateMessage(MSG* pMsg);
BEGIN_MSG_MAP_EX(CWTLClockView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_TIMER(OnTimer)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
END_MSG_MAP()
};
使用BEGIN_MSG_MAP_EX代替BEGIN_MSG_MAP后,ATL的消息映射宏可以和WTL的宏混合使用,前面的例子在OnEraseBkgnd()中显示(画)时钟,现在被被搬到了OnPaint()中。新窗口看起来是这个样子的:
最后为我们的程序添加UI updating功能,为了演示这些用法,我们为窗口添加Start菜单和Stop菜单用于开始和停止时钟,Start菜单和Stop菜单将被适当的设置为可用和不可用。
界面元素的自动更新(UI Updating)
空闲时间的界面更新是几件事情协同工作的结果: CMessageLoop对象,嵌入类CIdleHandler 和 CUpdateUI,CMainFrame类继承了这两个嵌入类,当然还有CMainFrame类中的UPDATE_UI_MAP宏。CUpdateUI能够操作5种不同的界面元素:顶级菜单项(就是菜单条本身),弹出式菜单的菜单项,工具条按钮,状态条的格子和子窗口(如对话框中的控件)。每一种界面元素都对应CUpdateUIBase类的一个常量:
- 菜单条项: UPDUI_MENUBAR
- 弹出式菜单项: UPDUI_MENUPOPUP
- 工具条按钮: UPDUI_TOOLBAR
- 状态条格子: UPDUI_STATUSBAR
- 子窗口: UPDUI_CHILDWINDOW
CUpdateUI可以设置enabled状态,checked状态和文本(当然不是所有的界面元素都支持所有状态,如果一个子窗口是编辑框它就不能被check)。菜单项可以被设置为默认状态,这样它的文字会被加重显示。
要使用UI updating需要做四件事:
- 主窗口需要继承CUpdateUI 和 CIdleHandler
- 将 CMainFrame 的消息链入 CUpdateUI
- 将主窗口添加到模块的空闲处理队列
- 在主窗口中添加 UPDATE_UI_MAP 宏
向导生成的代码已经为我们做了三件事,现在我们只需要决定那个菜单项需要更新和他们是么时候可用什么时候不可用。
添加控制时钟的新菜单项
在菜单条添加一个Clock菜单,它有两个菜单项:IDC_START and IDC_STOP:
然后在UPDATE_UI_MAP宏中为每个菜单项添加一个入口:
class CMainFrame : public ...
{
public:
// ... BEGIN_UPDATE_UI_MAP(CMainFrame) UPDATE_ELEMENT(IDC_START, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(IDC_STOP, UPDUI_MENUPOPUP) END_UPDATE_UI_MAP()
// ... };
我们只需要调用CUpdateUI::UIEnable()就可以改变这两个菜单项的任意一个的使能状态时。UIEnable()有两个参数,一个是界面元素的ID,另一个是标志界面元素是否可用的bool型变量(true表示可用,false表示不可用)。
这套体系比MFC的ON_UPDATE_COMMAND_UI体系笨拙一些,在MFC中我们只需编写处理函数,由MFC选择界面元素的显示状态,在WTL中我们需要告诉WTL界面元素的状态在何时改变。当然,这两个库都是在菜单将要显示的时候才应用菜单状态的改变。
调用 UIEnable()
现在返回到OnCreate()函数看看是如何设置Clock菜单的初始状态。
LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
m_hWndClient = m_view.Create(...);
// register object for message filtering and idle updates // [omitted for clarity]
// Set the initial state of the Clock menu items: UIEnable ( IDC_START, false );
UIEnable ( IDC_STOP, true ); return 0;
}
我们的程序开始时Clock菜单是这样的:
CMainFrame现在需要处理两个新菜单项,在视图类调用它们开始和停止时钟时处理函数需要翻转这两个菜单项的状态。这是MFC的内建消息处理无法想象的地方之一。在MFC的程序中,所有的界面更新和命令消息处理必须完整的放在视图类中,但是在WTL中,主窗口类和视图类通过某种方式沟通;菜单由主窗口拥有,主窗口获得这些菜单消息并做相应的处理,要么响应这些消息,要么发送给视图类。
这种沟通是通过PreTranslateMessage()完成的,当然CMainFrame仍然要调用UIEnable()。CMainFrame可以将this指针传递给视图类,这样视图类也可以通过这个指针调用UIEnable()。在这个例子中我选择的这种解决方案导致主窗口和视图成为紧密耦合体,但是我发现这很容易理解(和解释!)。
class CMainFrame : public ...
{
public:
BEGIN_MSG_MAP_EX(CMainFrame)
// ... COMMAND_ID_HANDLER_EX(IDC_START, OnStart) COMMAND_ID_HANDLER_EX(IDC_STOP, OnStop) END_MSG_MAP() // ... void OnStart(UINT uCode, int nID, HWND hwndCtrl);
void OnStop(UINT uCode, int nID, HWND hwndCtrl);
};
void CMainFrame::OnStart(UINT uCode, int nID, HWND hwndCtrl)
{
// Enable Stop and disable Start UIEnable ( IDC_START, false );
UIEnable ( IDC_STOP, true );
// Tell the view to start its clock. m_view.StartClock(); } void CMainFrame::OnStop(UINT uCode, int nID, HWND hwndCtrl)
{
// Enable Start and disable Stop UIEnable ( IDC_START, true );
UIEnable ( IDC_STOP, false );
// Tell the view to stop its clock. m_view.StopClock(); }
每个处理函数都更新Clock菜单,然后在视图类中调用一个方法,选择在视图类中使用是因为时钟是由视图类控制得。StartClock() 和 StopClock()得代码没有列出,但可以在这个工程得例子代码中找到它们。
消息映射链中最后需要注意的地方
如果你使用VC 6,你会注意到将BEGIN_MSG_MAP改为BEGIN_MSG_MAP_EX后ClassView显得有些杂乱无章:
出现这种情况是因为ClassView不能解释BEGIN_MSG_MAP_EX宏,它以为所有得WTL消息映射宏是函数定义。你可以将宏改回为BEGIN_MSG_MAP并在stdafx.h文件得结尾处添加这两行代码来解决这个问题:
#undef BEGIN_MSG_MAP
#define BEGIN_MSG_MAP(x) BEGIN_MSG_MAP_EX(x)
下一站, 1995
我们现在只是掀起了WTL的一角,在下一篇文章我会为我们的时钟程序添加一些Windows 95的界面标准,比如工具条和状态条,同时体验一下CUpdateUI的新东西。例如试着用UISetCheck()代替UIEnable(),看看菜单项会有什么变化。
修改记录
2003年3月26日,本文第一次发表。
相关推荐
WTL for MFC Programmers, Part II - WTL GUI Base Classes - WTL WTL for MFC Programmers, Part III - Toolbars and Status Bars - WTL WTL for MFC Programmers, Part IV - Dialogs and Controls - WTL WTL for ...
Part II - WTL GUI Base Classes Part III - Toolbars and Status Bars Part IV - Dialogs and Controls Part V - Advanced Dialog UI Part VI - Hosting ActiveX Controls Part VII - Splitter Windows Part VIII -...
**WTL GUI基类详解** 在Windows编程领域,MFC(Microsoft ...通过实践和学习,如《WTL for MFC Programmers Part II - WTL GUI Base Class》这样的资源,开发者可以更好地过渡到WTL世界,提升他们的Windows开发技能。
《永磁无刷直流电机控制系统与软件综合研究——集成电机计算软件、电机控制器及电磁设计软件的创新设计与实践》,永磁无刷直流电机计算与控制软件:高效电机控制器与电磁设计工具,永磁无刷直流电机计算软件,电机控制器,无刷电机设计软件,电机电磁设计软件 ,永磁无刷直流电机计算软件; 电机控制器; 无刷电机设计软件; 电机电磁设计软件,无刷电机设计专家:永磁无刷直流电机计算与控制器设计软件
新能源汽车VCU开发模型及策略详解:从控制策略到软件设计全面解析,新能源汽车VCU开发模型及策略详解:从控制策略到软件设计全面解析,新能源汽车VCU开发模型及控制策略,MBD电控开发 新能源汽车大势所向,紧缺VCU电控开发工程师,特别是涉及新能源三电系统,工资仅仅低于无人驾驶、智能驾驶岗位。 ——含控制策略模型 整车控制策略详细文档 通讯协议文档 接口定义 软件设计说明文档 等(超详细,看懂VCU电控策略开发就通了) 内容如下: 新能源汽车整车控制器VCU学习模型,适用于初学者。 1、模型包含高压上下电,行驶模式管理,能量回馈,充电模式管理,附件管理,远程控制,诊断辅助功能。 2、软件说明书(控制策略说明书) 3、模型有部分中文注释 对想着手或刚开始学习整车控制器自动代码生成或刚接触整车控制器有很大帮助。 ,新能源汽车VCU开发模型; 控制策略; MBD电控开发; 模型学习; 代码生成; 整车控制器; 能量回馈; 诊断辅助功能,新能源汽车电控开发详解:VCU控制策略模型及学习手册
内容概要:本文详细介绍了两种利用 Python 读取 Excel 文件的不同方法,分别是基于 pandas 和 openpyxl。对于想要利用Python 处理 Excel 数据的读者来说,文中不仅提供了简洁明了的具体代码片段以及执行效果展示,还针对每个库的应用特性进行了深度解析。此外,文档提到了一些进阶应用技巧如只读特定的工作薄、过滤某些列等,同时强调了需要注意的地方(像是路径设置、engine 参数调整之类),让读者可以在面对实际项目需求时做出更加明智的选择和技术选型。 适合人群:对 Python 有基本掌握并希望提升数据读取能力的开发人员。 使用场景及目标:适用于任何涉及到批量数据导入或是与 Excel 进行交互的业务流程。无论是做初步的数据探索还是深入挖掘隐藏于电子表格背后的故事,亦或是仅为了简化日常办公自动化任务都可以从中受益。最终目标帮助使用者熟悉两大主流 Excel 解决方案的技术特性和最佳实践。 阅读建议:本文既是一份详尽的学习指南也是一份方便随时查阅的手册。因此初学者应当认真研究所提供的示例,而有一定经验者也可以快速定位到感兴趣的部分查看关键要点。
# 医护人员排班系统 ## 1. 项目介绍 本系统是一个基于SpringBoot框架开发的医护人员排班管理系统,用于医院管理医护人员的排班、调班等工作。系统提供了完整的排班管理功能,包括科室管理、人员管理、排班规则配置、自动排班等功能。 ## 2. 系统功能模块 ### 2.1 基础信息管理 - 科室信息管理:维护医院各科室基本信息 - 医护人员管理:管理医生、护士等医护人员信息 - 排班类型管理:配置不同的排班类型(如:早班、中班、晚班等) ### 2.2 排班管理 - 排班规则配置:设置各科室排班规则 - 自动排班:根据规则自动生成排班计划 - 排班调整:手动调整排班计划 - 排班查询:查看各科室排班情况 ### 2.3 系统管理 - 用户管理:管理系统用户 - 角色权限:配置不同角色的操作权限 - 系统设置:管理系统基础配置 ## 3. 技术架构 ### 3.1 开发环境 - JDK 1.8 - Maven 3.6 - MySQL 5.7 - SpringBoot 2.2.2 ### 3.2 技术栈 - 后端框架:SpringBoot - 持久层:MyBatis-Plus - 数据库:MySQL - 前端框架:Vue.js - 权限管理:Spring Security ## 4. 数据库设计 主要数据表: - 科室信息表(keshixinxi) - 医护人员表(yihurengyuan) - 排班类型表(paibanleixing) - 排班信息表(paibanxinxi) - 用户表(user) ## 5. 部署说明 ### 5.1 环境要求 - JDK 1.8+ - MySQL 5.7+ - Maven 3.6+ ### 5.2 部署步骤 1. 创建数据库并导入SQL脚本 2. 修改application.yml中的数据库配置 3. 执行maven打包命令:mvn clean package 4. 运行jar包:java -jar xxx.jar ## 6. 使用说明 ### 6.1 系统登录 - 管理员账号:admin - 初始密码:admin ### 6.2 基本操作流程 1. 维护基础信息(科室、人员等) 2. 配置排班规则 3. 生成排班计划 4. 查看和调整排班 ## 7. 注意事项 1. 首次使用请及时修改管理员密码 2. 定期备份数据库 3. 建议定期检查和优化排班规则
MATLAB仿真的夫琅禾费衍射强度图:圆孔、圆环、矩形孔定制研究,MATLAB仿真:夫琅禾费衍射强度图的可定制性——以圆孔、圆环及矩形孔为例的研究分析,MATLAB夫琅禾费衍射强度图仿真 圆孔,圆环,矩形孔可定制。 ,MATLAB; 夫琅禾费衍射; 强度图仿真; 圆孔; 圆环; 矩形孔; 可定制。,MATLAB仿真夫琅禾费衍射强度图:定制孔型(圆孔/圆环/矩形)
详细介绍及样例数据:https://blog.csdn.net/samLi0620/article/details/145652300
基于Dugoff轮胎模型与B08_01基础建模的七自由度车辆动力学模型验证:利用MATLAB 2018及以上版本与CarSim 2020.0软件的仿真对比研究,基于Dugoff轮胎模型与B08_01框架的七自由度车辆动力学模型验证——使用MATLAB 2018及以上版本与CarSim 2020.0软件进行仿真对比研究,七自由度车辆动力学模型验证(Dugoff轮胎模型,B08_01基础上建模) 1.软件: MATLAB 2018以上;CarSim 2020.0 2.介绍: 基于Dugoff轮胎模型和车身动力学公式,搭建7DOF车辆动力学Simulink模型,对相关变量(质心侧偏角,横摆角速度,纵、横向速度及加速度)进行CarSim对比验证。 ,核心关键词:七自由度车辆动力学模型验证; Dugoff轮胎模型; B08_01建模基础; MATLAB 2018以上; CarSim 2020.0; Simulink模型; 变量对比验证。,基于Dugoff轮胎模型的七自由度车辆动力学模型验证与CarSim对比
【毕业设计】基于Java+servlet+jsp+css+js+mysql实现“转赚”二手交易平台_pgj
微猫恋爱聊妹术小程序源码介绍: 微猫恋爱聊妹术小程序源码是一款全新升级的聊天工具,它采用全新主题和UI,完美支持分享朋友圈功能。同时,它的独立后台也进行了大规模更新,让操作更加简单。其中,课堂页面、搜索页面和子话术列表页面等,均增加了流量主展示,具有超多的功能。 安装教程: 您可以先加入微猫恋爱聊妹术小程序源码的赞助群,然后在群内找到魔方安装说明。根据源码编号找到相应的安装说明,非常详细,让您轻松完成安装。
电气安装工程安全技术规程_蒋凯,杨华甫,马仲范,王清禄译;孙照森校;鞍钢工程技术编委会编
基于Copula函数的风光空间相关性联合场景生成与K-means聚类削减MATLAB研究,基于Copula函数的风光空间相关性联合场景生成与K-means聚类削减算法研究,基于copula的风光联合场景生成?K-means聚类并削减 MATLAB 由于目前大多数研究的是不计风光出力之间的相关性影响,但是地理位置相近的风电机组和光伏机组具有极大的相关性。 因此,采用 Copula 函数作为风电、光伏联合概率分布,生成风、光考虑空间相关性联合出力场景,在此基础上,基于Kmeans算法,分别对风光场景进行聚类,从而实现大规模场景的削减,削减到5个场景,最后得出每个场景的概率与每个对应场景相乘求和得到不确定性出力 ,基于Copula的风光联合场景生成; K-means聚类削减; 空间相关性; 概率分布; 场景削减,基于Copula与K-means的风光联合场景生成与削减研究
模块化多电平变流器MMC的VSG控制技术研究:基于MATLAB-Simulink的仿真分析与定制实现——支持三相与任意电平数,构网型模块化多电平变流器MMC的VSG控制策略与仿真模型:三相负荷变动下的虚拟同步发电机控制研究,构网型 模块化多电平变流器 MMC 的VSG控制 同步发电机控制 MATLAB–Simulink仿真模型,可按需求定制 10电平.14电平,任意电平可做。 三相MMC,采用VSG控制。 设置负荷变动,调整有功无功,保持电网电压和频率 ,构网型模块化多电平变流器; MMC的VSG控制; 虚拟同步发电机控制; MATLAB–Simulink仿真模型; 任意电平可做; 三相MMC; 负荷变动; 有功无功调整; 电网电压和频率保持。,基于VSG控制的模块化多电平变流器(MMC)的构网型仿真模型
暗通道算法DCP-Python实现
南师大实验室安全准入知识供学习
纯openMV寻迹小车.zip
【毕业设计】基于Java mvc架构开发的完整购物网站
以下是针对初学者的 **51单片机入门教程**,内容涵盖基础概念、开发环境搭建、编程实践及常见应用示例,帮助你快速上手。