📄 wtl for mfc programmers, part ii - wtl gui base classes - wtl.htm
字号:
{
<SPAN class=cpp-keyword>if</SPAN>(wParam != SIZE_MINIMIZED)
{
T* pT = <SPAN class=cpp-keyword>static_cast</SPAN><T*>(<SPAN class=cpp-keyword>this</SPAN>);
pT->UpdateLayout();
}
bHandled = FALSE;
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>1</SPAN>;
}</font></PRE>
<P>它首先检查窗口是否最小化,如果不是就调用UpdateLayout(),下面是UpdateLayout():</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">void</font></SPAN><font color="#0000FF"> UpdateLayout(BOOL bResizeBars = TRUE)
{
RECT rect;
GetClientRect(&rect);
<SPAN class=cpp-comment>// position bars and offset their dimensions</SPAN>
UpdateBarsPosition(rect, bResizeBars);
<SPAN class=cpp-comment>// resize client window</SPAN>
<SPAN class=cpp-keyword>if</SPAN>(m_hWndClient != NULL)
::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER | SWP_NOACTIVATE);
}</font></PRE>
<P>注意这些代码是如何使用m_hWndClient得,既然m_hWndClient是一般窗口的句柄,它就可能是任何窗口,对这个窗口的类型没有限制。这一点不像MFC,MFC在很多情况下需要CView的派生类(例如分隔窗口类)。如果你回过头看看CMainFrame::OnCreate()就会看到它创建了一个视图窗口并赋值给m_hWndClient,由m_hWndClient确保视图窗口被设置为正确的大小。</P>
<H2><A name=backtotheclock></A><font color="#FFFF66">回到前面的时钟程序</font></H2>
<P>现在我们已经看到了主窗口类的一些细节,现在回到我们的时钟程序。视图窗口用来响应定时器消息并负责显示时钟,就像前面的CMyWindow类。下面是这个类的部分定义:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CWTLClockView : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CWTLClockView>
{
<SPAN class=cpp-keyword>public</SPAN>:
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()
};</font></PRE>
<P>使用BEGIN_MSG_MAP_EX代替BEGIN_MSG_MAP后,ATL的消息映射宏可以和WTL的宏混合使用,前面的例子在OnEraseBkgnd()中显示(画)时钟,现在被被搬到了OnPaint()中。新窗口看起来是这个样子的:<br>
</P>
<P><IMG height=215 alt=" [Clock app w/view window - 3K] "
src="images/clockapp.png"
width=277 align=bottom border=0></P>
<P>最后为我们的程序添加UI updating功能,为了演示这些用法,我们为窗口添加Start菜单和Stop菜单用于开始和停止时钟,Start菜单和Stop菜单将被适当的设置为可用和不可用。</P>
<H2><A name=uiupdating></A><font color="#FFFF66">界面元素的自动更新(UI Updating)</font></H2>
<P>空闲时间的界面更新是几件事情协同工作的结果: CMessageLoop对象,嵌入类CIdleHandler 和 CUpdateUI,CMainFrame类继承了这两个嵌入类,当然还有CMainFrame类中的UPDATE_UI_MAP宏。CUpdateUI能够操作5种不同的界面元素:顶级菜单项(就是菜单条本身),弹出式菜单的菜单项,工具条按钮,状态条的格子和子窗口(如对话框中的控件)。每一种界面元素都对应CUpdateUIBase类的一个常量:</P>
<UL>
<LI>菜单条项: UPDUI_MENUBAR
<LI>弹出式菜单项: UPDUI_MENUPOPUP
<LI>工具条按钮: UPDUI_TOOLBAR
<LI>状态条格子: UPDUI_STATUSBAR
<LI>子窗口: UPDUI_CHILDWINDOW</LI>
</UL>
<P>CUpdateUI可以设置enabled状态,checked状态和文本(当然不是所有的界面元素都支持所有状态,如果一个子窗口是编辑框它就不能被check)。菜单项可以被设置为默认状态,这样它的文字会被加重显示。</P>
<p>要使用UI updating需要做四件事:</p>
<p></p>
<OL>
<LI>主窗口需要继承CUpdateUI 和 CIdleHandler
<LI>将 CMainFrame 的消息链入 CUpdateUI
<LI>将主窗口添加到模块的空闲处理队列
<LI>在主窗口中添加 UPDATE_UI_MAP 宏 </LI>
</OL>
<P>向导生成的代码已经为我们做了三件事,现在我们只需要决定那个菜单项需要更新和他们是么时候可用什么时候不可用。</P>
<H3><A name=newmenuitems></A><font color="#FFFF66">添加控制时钟的新菜单项</font></H3>
<P>在菜单条添加一个Clock菜单,它有两个菜单项:IDC_START and IDC_STOP:</P>
<P><IMG height=103 alt=" [Clock menu - 2K] "
src="images/clockmenu.png"
width=169 align=bottom border=0></P>
<P>然后在UPDATE_UI_MAP宏中为每个菜单项添加一个入口:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMainFrame : <SPAN class=cpp-keyword>public</SPAN> ...
{
<SPAN class=cpp-keyword>public</SPAN>:
<SPAN class=cpp-comment>// ...</SPAN>
BEGIN_UPDATE_UI_MAP(CMainFrame)
</font> <FONT color=red>UPDATE_ELEMENT(IDC_START, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(IDC_STOP, UPDUI_MENUPOPUP)</FONT>
<font color="#0000FF"> END_UPDATE_UI_MAP()
<SPAN class=cpp-comment>// ...</SPAN>
};</font></PRE>
<P>我们只需要调用CUpdateUI::UIEnable()就可以改变这两个菜单项的任意一个的使能状态时。UIEnable()有两个参数,一个是界面元素的ID,另一个是标志界面元素是否可用的bool型变量(true表示可用,false表示不可用)。</P>
<p><br>
这套体系比MFC的ON_UPDATE_COMMAND_UI体系笨拙一些,在MFC中我们只需编写处理函数,由MFC选择界面元素的显示状态,在WTL中我们需要告诉WTL界面元素的状态在何时改变。当然,这两个库都是在菜单将要显示的时候才应用菜单状态的改变。<br>
</p>
<H3><A name=callinguienable></A><font color="#FFFF66">调用 UIEnable()</font></H3>
<P>现在返回到OnCreate()函数看看是如何设置Clock菜单的初始状态。</P>
<PRE><font color="#0000FF">LRESULT CMainFrame::OnCreate(UINT <SPAN class=cpp-comment>/*uMsg*/</SPAN>, WPARAM <SPAN class=cpp-comment>/*wParam*/</SPAN>,
LPARAM <SPAN class=cpp-comment>/*lParam*/</SPAN>, BOOL& <SPAN class=cpp-comment>/*bHandled*/</SPAN>)
{
m_hWndClient = m_view.Create(...);
<SPAN class=cpp-comment>// register object for message filtering and idle updates</SPAN>
<SPAN class=cpp-comment>// [omitted for clarity]</SPAN>
</font>
<FONT color=red><SPAN class=cpp-comment>// Set the initial state of the Clock menu items:</SPAN>
UIEnable ( IDC_START, <SPAN class=cpp-keyword>false</SPAN> );
UIEnable ( IDC_STOP, <SPAN class=cpp-keyword>true</SPAN> );</FONT>
<font color="#0000FF"><SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}</font></PRE>
<P>我们的程序开始时Clock菜单是这样的:</P>
<P><img height=216 alt=" [Start item disabled - 4K] "
src="images/startdisabled.png"
width=294 align=bottom border=0></P>
<P>CMainFrame现在需要处理两个新菜单项,在视图类调用它们开始和停止时钟时处理函数需要翻转这两个菜单项的状态。这是MFC的内建消息处理无法想象的地方之一。在MFC的程序中,所有的界面更新和命令消息处理必须完整的放在视图类中,但是在WTL中,主窗口类和视图类通过某种方式沟通;菜单由主窗口拥有,主窗口获得这些菜单消息并做相应的处理,要么响应这些消息,要么发送给视图类。</P>
<p>这种沟通是通过PreTranslateMessage()完成的,当然CMainFrame仍然要调用UIEnable()。CMainFrame可以将this指针传递给视图类,这样视图类也可以通过这个指针调用UIEnable()。在这个例子中我选择的这种解决方案导致主窗口和视图成为紧密耦合体,但是我发现这很容易理解(和解释!)。</p>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMainFrame : <SPAN class=cpp-keyword>public</SPAN> ...
{
<SPAN class=cpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMainFrame)
<SPAN class=cpp-comment>// ...</SPAN>
COMMAND_ID_HANDLER_EX(IDC_START, OnStart)
COMMAND_ID_HANDLER_EX(IDC_STOP, OnStop)
END_MSG_MAP()
<SPAN class=cpp-comment>// ...</SPAN>
<SPAN class=cpp-keyword>void</SPAN> OnStart(UINT uCode, <SPAN class=cpp-keyword>int</SPAN> nID, HWND hwndCtrl);
<SPAN class=cpp-keyword>void</SPAN> OnStop(UINT uCode, <SPAN class=cpp-keyword>int</SPAN> nID, HWND hwndCtrl);
};
<SPAN class=cpp-keyword>void</SPAN> CMainFrame::OnStart(UINT uCode, <SPAN class=cpp-keyword>int</SPAN> nID, HWND hwndCtrl)
{
<SPAN class=cpp-comment>// Enable Stop and disable Start</SPAN>
UIEnable ( IDC_START, <SPAN class=cpp-keyword>false</SPAN> );
UIEnable ( IDC_STOP, <SPAN class=cpp-keyword>true</SPAN> );
<SPAN class=cpp-comment>// Tell the view to start its clock.</SPAN>
m_view.StartClock();
}
<SPAN class=cpp-keyword>void</SPAN> CMainFrame::OnStop(UINT uCode, <SPAN class=cpp-keyword>int</SPAN> nID, HWND hwndCtrl)
{
<SPAN class=cpp-comment>// Enable Start and disable Stop</SPAN>
UIEnable ( IDC_START, <SPAN class=cpp-keyword>true</SPAN> );
UIEnable ( IDC_STOP, <SPAN class=cpp-keyword>false</SPAN> );
<SPAN class=cpp-comment>// Tell the view to stop its clock.</SPAN>
m_view.StopClock();
}</font></PRE>
<P>每个处理函数都更新Clock菜单,然后在视图类中调用一个方法,选择在视图类中使用是因为时钟是由视图类控制得。StartClock() 和 StopClock()得代码没有列出,但可以在这个工程得例子代码中找到它们。</P>
<H2><A name=fixclassview></A><font color="#FFFF66">消息映射链中最后需要注意的地方</font></H2>
<P>如果你使用VC 6,你会注意到将BEGIN_MSG_MAP改为BEGIN_MSG_MAP_EX后ClassView显得有些杂乱无章:</P>
<P><IMG height=248 alt=" [Messed-up ClassView - 6K] "
src="images/classview.png"
width=350 align=bottom border=0></P>
<P>出现这种情况是因为ClassView不能解释BEGIN_MSG_MAP_EX宏,它以为所有得WTL消息映射宏是函数定义。你可以将宏改回为BEGIN_MSG_MAP并在stdafx.h文件得结尾处添加这两行代码来解决这个问题:</P>
<P><font color="#0000FF">#undef BEGIN_MSG_MAP<br>
#define BEGIN_MSG_MAP(x) BEGIN_MSG_MAP_EX(x)</font></P>
<H2><A name=nextup></A><font color="#FFFF66">下一站, 1995</font></H2>
<P>我们现在只是掀起了WTL的一角,在下一篇文章我会为我们的时钟程序添加一些Windows 95的界面标准,比如工具条和状态条,同时体验一下CUpdateUI的新东西。例如试着用UISetCheck()代替UIEnable(),看看菜单项会有什么变化。</P>
<H2><A name=revisionhistory></A><font color="#FFFF66">修改记录</font></H2>
<P>2003年3月26日,本文第一次发表。</P>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -