⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 wtl for mfc programmers, part iv - dialogs and controls - wtl.htm

📁 MFC程序员的WTL指南,非常具体,WTL入门最好的书
💻 HTM
📖 第 1 页 / 共 3 页
字号:
      
<P>如果编辑控件输入的不是数字,DDX_INT将会失败并触发OnDataExchangeError()的调用,CMainDlg重载了OnDataExchangeError()函数显示一个消息框:</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">void</font></SPAN><font color="#0033FF"> CMainDlg::OnDataExchangeError ( UINT nCtrlID, BOOL bSave )
{
CString str;
 
    str.Format ( _T(<SPAN class=cpp-string>"DDX error during exchange with control: %u"</SPAN>), nCtrlID );
    MessageBox ( str, _T(<SPAN class=cpp-string>"ControlMania1"</SPAN>), MB_ICONWARNING );
     
    ::SetFocus ( GetDlgItem(nCtrlID) );
}</font></PRE>
      
<P><IMG height=338 alt=" [DDX error msg - 7K] " 
      src="images/ddxerror4.png" 
      width=372 align=bottom border=0></P>
      
<P>作为最后一个使用DDX的例子,我们添加一个check box演示DDX_CHECK的使用:</P>
      
<P><IMG height=265 alt=" [Msg checkbox - 6K] " 
      src="images/msgchkbox4.png" 
      width=333 align=bottom border=0></P>
      
<P>DDX_CHECK使用的变量类型是int型,它的可能值是0,1,2,分别对应check box的未选择状态,选择状态和不确定状态。你也可以使用常量BST_UNCHECKED,BST_CHECKED,和 
  BST_INDETERMINATE代替,对于check box来说只有选择和未选择两种状态,你可以将其视为布尔型变量。</P>
<p>以下是为使用check box的DDX而做的改动:</p>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">class</font></SPAN><font color="#0033FF"> CMainDlg : <SPAN class=cpp-keyword>public</SPAN> ...
{
<SPAN class=cpp-comment>//...</SPAN>
    BEGIN_DDX_MAP(CMainDlg)
        DDX_CONTROL(IDC_EDIT, m_wndEdit)
        DDX_TEXT(IDC_EDIT, m_sEditContents)
        DDX_INT(IDC_EDIT, m_nEditNumber)
        <B>DDX_CHECK(IDC_SHOW_MSG, m_nShowMsg)</B>
    END_DDX_MAP()
 
<SPAN class=cpp-keyword>protected</SPAN>:
    <SPAN class=cpp-comment>// DDX variables</SPAN>
    CString m_sEditContents;
    <SPAN class=cpp-keyword>int</SPAN>     m_nEditNumber;
    <B><SPAN class=cpp-keyword>int</SPAN>     m_nShowMsg;</B>
};</font></PRE>
      
<P>在OnOK()的最后,检查m_nShowMsg的值看看check box是否被选中。</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">void</font></SPAN><font color="#0033FF"> CMainDlg::OnOK ( UINT uCode, <SPAN class=cpp-keyword>int</SPAN> nID, HWND hWndCtl )
{
    <SPAN class=cpp-comment>// Transfer data from the controls to member variables.</SPAN>
    <SPAN class=cpp-keyword>if</SPAN> ( !DoDataExchange(<SPAN class=cpp-keyword>true</SPAN>) )
        <SPAN class=cpp-keyword>return</SPAN>;
<SPAN class=cpp-comment>//...</SPAN>
    <SPAN class=cpp-keyword>if</SPAN> ( m_nShowMsg )
        MessageBox ( _T(<SPAN class=cpp-string>"DDX complete!"</SPAN>), _T(<SPAN class=cpp-string>"ControlMania1"</SPAN>), 
                     MB_ICONINFORMATION );
}</font></PRE>
      
<P>使用其它DDX_*宏的例子代码包含在例子工程中。</P>
<H2><A name=handlenotify></A><font color="#FFFF66">处理控件发送的通知消息</font></H2>
      
<P>在WTL中处理通知消息与使用API方式编程相似,控件以WM_COMMAND 或 WM_NOTIFY 消息的方式向父窗口发送通知事件,父窗口相应并做相应处理。少数其它的消息也可以看作是通知消息,例如:WM_DRAWITEM,当一个自画控件需要画自己时就会发送这个消息,父窗口可以自己处理这个消息,也可以再将它反射给控件,MFC采用得就是消息反射方式,使得控件能够自己处理通知消息,提高了代码的封装性和可重用性。</P>
      
<H3><A name=handleinparent></A><font color="#FFFF66">在父窗口中响应控件的通知消息</font></H3>
      
<P>以WM_NOTIFY和WM_COMMAND消息形式发送的通知消息包含各种信息。WM_COMMAND消息的参数包含发送通知消息的控件ID,控件的窗口句柄和通知代码,WM_NOTIFY消息的参数还包含一个NMHDR数据结构的指针。ATL和WTL有各种消息映射宏用来处理这些通知消息,我在这里只介绍WTL宏,因为本文就是讲WTL得。使用这些宏需要在消息映射链中使用BEGIN_MSG_MAP_EX并包含atlcrack.h文件。</P>
      
<H4>消息映射宏</H4>
      
<P>要处理WM_COMMAND通知消息需要使用COMMAND_HANDLER_EX宏:</P>
      
<DL> 
  <DT>COMMAND_HANDLER_EX(id, code, func)
  <DD>处理从某个控件发送得某个通知代码。
  <DT>COMMAND_ID_HANDLER_EX(id, func) 
  <DD>处理从某个控件发送得所有通知代码。
  <DT>COMMAND_CODE_HANDLER_EX(code, func)
  <DD>处理某个通知代码得所有消息,不管是从那个控件发出的。
  <DT>COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func) 
  <DD>处理ID在idFirst和idLast之间得控件发送的所有通知代码。
  <DT>COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func)
  <DD>处理ID在idFirst和idLast之间得控件发送的某个通知代码。</DD>
</DL>
<P>例子:</P>
      
<UL>
  <LI>COMMAND_HANDLER_EX(IDC_USERNAME, EN_CHANGE, OnUsernameChange): 
    处理从ID是IDC_USERNAME的edit box控件发出的EN_CHANGE通知消息。
  <LI>COMMAND_ID_HANDLER_EX(IDOK, OnOK): 处理ID是IDOK的控件发送的所有通知消息。
  <LI>COMMAND_RANGE_CODE_HANDLER_EX(IDC_MONDAY, IDC_FRIDAY, BN_CLICKED, 
    OnDayClicked): 处理ID在IDC_MONDAY和IDC_FRIDAY之间控件发送的BN_CLICKED通知消息。</LI>
</UL>
      
<P>还有一些宏专门处理WM_NOTIFY消息,和上面的宏功能类似,只是它们的名字开头以“NOTIFY_”代替“COMMAND_”。</P>
      
<P>WM_COMMAND 消息处理函数的原型是:</P>
<PRE>  <font color="#0033FF"><SPAN class=cpp-keyword>void</SPAN> func ( UINT uCode, <SPAN class=cpp-keyword>int</SPAN> nCtrlID, HWND hwndCtrl );</font></PRE>
<P>WM_COMMAND通知消息不需要返回值,所以处理函数也不需要返回值,WM_NOTIFY消息处理函数的原型是:</P>
<PRE>  <font color="#0033FF">LRESULT func ( NMHDR* phdr );</font></PRE>
<P>消息处理函数的返回值用作消息相应的返回值,这不同于MFC,MFC的消息响应通过消息处理函数的LRESULT*参数得到返回值。发送通知消息的控件的窗口句柄和通知代码包含在NMHDR结构中,分别是code和hendFrom成员。和MFC一样的是如果通知消息发送的不是普通的NMHDR结构,你的消息处理函数应该将phdr参数转换成正确的类型。</P>
<p>我们将为CMainDlg添加LVN_ITEMCHANGED通知的处理函数,处理从list控件发出的这个通知,在对话框中显示当前选择的项目,先从添加消息映射宏和消息处理函数开始:</p>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">class</font></SPAN><font color="#0033FF"> CMainDlg : <SPAN class=cpp-keyword>public</SPAN> ...
{
    BEGIN_MSG_MAP_EX(CMainDlg)
        NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)
    END_MSG_MAP()
 
    LRESULT OnListItemchanged(NMHDR* phdr);
<SPAN class=cpp-comment>//...</SPAN>
};</font></PRE>
      
<P>下面是消息处理函数:</P>
<PRE><font color="#0033FF">LRESULT CMainDlg::OnListItemchanged ( NMHDR* phdr )
{
NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr;
<SPAN class=cpp-keyword>int</SPAN> nSelItem = m_wndList.GetSelectedIndex();
CString sMsg;
 
    <SPAN class=cpp-comment>// If no item is selected, show "none". Otherwise, show its index.</SPAN>
    <SPAN class=cpp-keyword>if</SPAN> ( -<SPAN class=cpp-literal>1</SPAN> == nSelItem )
        sMsg = _T(<SPAN class=cpp-string>"(none)"</SPAN>);
    <SPAN class=cpp-keyword>else</SPAN>
        sMsg.Format ( _T(<SPAN class=cpp-string>"%d"</SPAN>), nSelItem );
 
    SetDlgItemText ( IDC_SEL_ITEM, sMsg );
    <SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;   <SPAN class=cpp-comment>// retval ignored</SPAN>
}</font></PRE>
      
<P>该处理函数并未用到phdr参数,我将他强制转换成NMLISTVIEW*只是为了演示用法。</P>
      
<H3><A name=reflnotify></A><font color="#FFFF66">反射通知消息</font></H3>
      
<P>如果你是用CWindowImpl的派生类封装控件,比如前面使用的CEditImpl,你可以在类的内部处理通知消息而不是在对话框中,这就是通知消息的反射,它和MFC的消息反射相似。不同的是在WTL中父窗口和控件都可以处理通知消息,而在MFC中只有控件能处理通知消息(译者加:除非你重载 
  WindowProc函数,在MFC反射这些消息之前截获它们)。</P>
<p>如果需要将通知消息反射给控件封装类,只需在对话框的消息映射链中添加REFLECT_NOTIFICATIONS()宏:</p>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">class</font></SPAN><font color="#0033FF"> CMainDlg : <SPAN class=cpp-keyword>public</SPAN> ...
{
<SPAN class=cpp-keyword>public</SPAN>:
    BEGIN_MSG_MAP_EX(CMainDlg)
        <SPAN class=cpp-comment>//...</SPAN>
        NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)
        <B>REFLECT_NOTIFICATIONS()</B>
    END_MSG_MAP()
};</font></PRE>
<P>这个宏向消息映射链添加了一些代码处理那些未被前面的宏处理的通知消息,它检查消息传递的HWND窗口句柄是否有效并将消息转发给这个窗口,当然,消息代码的数值被改变成OLE控件所使用的值,OLE控件有与之相似的消息反射系统。新的消息代码值用OCM_xxx代替了WM_xxx,但是消息的处理方式和未反射前一样。</P>
<p>有18中被反射的消息:</p>
<UL>
  <LI>控件通知消息: WM_COMMAND, WM_NOTIFY, WM_PARENTNOTIFY
  <LI>自画消息: WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM, WM_DELETEITEM 
  <LI>List box 键盘消息: WM_VKEYTOITEM, WM_CHARTOITEM
  <LI>其它: WM_HSCROLL, WM_VSCROLL, WM_CTLCOLOR*</LI>
</UL>
      
<P>在你想添加反射消息处理的控件类内不要忘了使用DEFAULT_REFLECTION_HANDLER()宏,DEFAULT_REFLECTION_HANDLER()宏确保将未被处理的消息交给DefWindowProc()正确处理。 
  下面的例子是一个自画按钮类,它相应了从父窗口反射的WM_DRAWITEM消息。</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">class</font></SPAN><font color="#0033FF"> CODButtonImpl : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl&lt;CODButtonImpl, CButton&gt;
{
<SPAN class=cpp-keyword>public</SPAN>:
    BEGIN_MSG_MAP_EX(CODButtonImpl)
        MSG_OCM_DRAWITEM(OnDrawItem)
        DEFAULT_REFLECTION_HANDLER()
    END_MSG_MAP()
 
    <SPAN class=cpp-keyword>void</SPAN> OnDrawItem ( UINT idCtrl, LPDRAWITEMSTRUCT lpdis )
    {
        <SPAN class=cpp-comment>// do drawing here...</SPAN>
    }
};</font></PRE>
<H4><A name=wtlreflmacros></A><font color="#FFFF66">用来处理反射消息的WTL宏</font> </H4>
      
<P>我们现在只看到了WTL的消息反射宏中的一个:MSG_OCM_DRAWITEM,还有17个这样的反射宏。由于WM_NOTIFY和WM_COMMAND消息带的参数需要展开,WTL提供了特殊的宏MSG_OCM_COMMAND和MSG_OCM_NOTIFY做这些事情。这些宏所作的工作与COMMAND_HANDLER_EX和NOTIFY_HANDLER_EX宏相同,只是前面加了“REFLECTED_”,例如,一个树控件类可能存在这样的消息映射链:<br>
</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">class</font></SPAN><font color="#0033FF"> CMyTreeCtrl : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl&lt;CMyTreeCtrl, CTreeViewCtrl&gt;
{
<SPAN class=cpp-keyword>public</SPAN>:
  BEGIN_MSG_MAP_EX(CMyTreeCtrl)
    REFLECTED_NOTIFY_CODE_HANDLER_EX(TVN_ITEMEXPANDING, OnItemExpanding)
    DEFAULT_REFLECTION_HANDLER()
  END_MSG_MAP()
 
  LRESULT OnItemExpanding ( NMHDR* phdr );
};</font></PRE>
      
<P>在ControlMania1对话框中用了一个树控件,和上面的代码一样处理TVN_ITEMEXPANDING消息,CMainDlg类的成员m_wndTree使用DDX连接到控件上,CMainDlg反射通知消息,树控件的处理函数OnItemExpanding()是这样的:</P>
<PRE><font color="#0033FF">LRESULT CBuffyTreeCtrl::OnItemExpanding ( NMHDR* phdr )
{
NMTREEVIEW* pnmtv = (NMTREEVIEW*) phdr;
 
    <SPAN class=cpp-keyword>if</SPAN> ( pnmtv-&gt;action &amp; TVE_COLLAPSE )
        <SPAN class=cpp-keyword>return</SPAN> TRUE;    <SPAN class=cpp-comment>// don't allow it</SPAN>
    <SPAN class=cpp-keyword>else</SPAN>
        <SPAN class=cpp-keyword>return</SPAN> FALSE;   <SPAN class=cpp-comment>// allow it</SPAN>
}</font></PRE>
      
<P>运行ControlMania1,用鼠标点击树控件上的+/-按钮,你就会看到消息处理函数的作用-节点展开后就不能再折叠起来。</P>
<H2><A name=oddsends></A><font color="#FFFF66">容易出错和混淆的地方</font></H2>
      
<H3><A name=dlgfonts></A><font color="#FFFF66">对话框的字体</font></H3>
      
<P>如果你像我一样对界面非常讲究并且正在只用windows 2000或XP,你就会奇怪为什么对话框使用MS Sans Serif字体而不是Tahoma字体,因为VC6太老了,它生成的资源文件在NT 
  4上工作的很好,但是对于新的版本就会有问题。你可以自己修改,需要手工编辑资源文件,据我所知VC 7不存在这个问题。</P>
<p>在资源文件中对话框的入口处需要修改3个地方:</p>
<OL>
  <LI>对话框类型: 将DIALOG改为DIALOGEX
  <LI>窗口类型: 添加DS_SHELLFONT 
  <LI>对话框字体: 将MS Sans Serif改为MS Shell Dlg</LI>
</OL>
      
<P>不幸的是前两个修改会在每次保存资源文件时丢失(被VC又改回原样),所以需要重复这些修改,下面是改动之前的代码:</P>
<PRE><font color="#0033FF">IDD_ABOUTBOX DIALOG DISCARDABLE  <SPAN class=cpp-literal>0</SPAN>, <SPAN class=cpp-literal>0</SPAN>, <SPAN class=cpp-literal>187</SPAN>, <SPAN class=cpp-literal>102</SPAN>
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION <SPAN class=cpp-string>"About"</SPAN>
FONT <SPAN class=cpp-literal>8</SPAN>, <SPAN class=cpp-string>"MS Sans Serif"</SPAN>
BEGIN
  ...
END</font></PRE>
      
<P>这是改动之后的代码:</P>
<PRE><font color="#0033FF">IDD_ABOUTBOX <B>DIALOGEX</B> DISCARDABLE  <SPAN class=cpp-literal>0</SPAN>, <SPAN class=cpp-literal>0</SPAN>, <SPAN class=cpp-literal>187</SPAN>, <SPAN class=cpp-literal>102</SPAN>
STYLE <B>DS_SHELLFONT | </B>DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION <SPAN class=cpp-string>"About"</SPAN>
FONT <SPAN class=cpp-literal>8</SPAN>, <SPAN class=cpp-string>"<B>MS Shell Dlg</B>"</SPAN>
BEGIN
  ...
END</font></PRE>
      
<P>这样改了之后,对话框将在新的操作系统上使用Tahoma字体,而在老的操作系统上仍旧使用MS Sans Serif字体。</P>
      <H3><A name=atlmincrt></A><font color="#FFFF66">_ATL_MIN_CRT</font></H3>
      
<P>本文的<A 
      href="http://www.codeproject.com/cpp/cppforumfaq.asp#cl_errormain">论坛 FAQ</A>已经做过解释, 
  ATL包含的优化设置让你创建一个不使用C运行库(CRT)的程序,使用这个优化需要在预处理设置中添加_ATL_MIN_CRT标号,向导生成的代码在Release配置中默认使用了这个优化。由于我写程序总是会用到CRT函数,所以我总是去掉这个标号,如果你在CString类或DDX中用到了浮点运算特性,你也要去掉这个标号。</P>
<H2><font color="#FFFF66">继续</font></H2>
      
<P>在第五章,我将介绍对话框数据验证(DDV),WTL对新控件的封装和自画控件、自定外观控件等一些高级界面特性。</P>
<H2><A name=revisionhistory></A><font color="#FFFF66">修改记录</font></H2>
            
      
<P>2003年4月27日,本文第一次发表。</P>
</body>
</html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -