Free mind

          Be fresh and eager every morning, and tired and satisfied every night.
          posts - 39, comments - 2, trackbacks - 0, articles - 0
             :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          Cluster

          WTL

          搜索

          •  

          最新評論

          MFC程序員的WTL指南: Part V - 高級對話框用戶界面類

          原作 :Michael Dunn [英文原文]
          翻譯 :Orbit(桔皮干了) [www.winmsg.com]

          下載演示程序代碼

          本章內容 第五章介紹

          在上一篇文章我們介紹了一些與對話框和控件有關的WTL的特性,它們和MFC的相應的類作用相同。本文將介紹一些新類實現高級界面特性新類:控件自畫和自定外觀控件,新的WTL控件,UI updating和對話框數據驗證(DDV)。

          特別的自畫和外觀定制類

          由于自畫和定制外觀控件在圖形用戶界面中是很常用的手段,所以WTL提供了幾個嵌入類來完成這些令人厭煩的工作。我接著就會介紹它們,事實上我們在上一個例子工程ControlMania2的結尾部分已經這么做了。如果你正隨著我的講解用應用程序生成向導創建新工程,請不要忘了使用無模式對話框,為了使正常工作必須使用無模式對話框,我會在對話框中控件的UI Updating部分詳細解釋為什么這樣作。

          COwnerDraw

          控件的自畫需要響應四個消息:WM_MEASUREITEM, WM_DRAWITEM, WM_COMPAREITEM, 和WM_DELETEITEM,在atlframe.h頭文件中定義的COwnerDraw類可以簡化這些工作,使用這個類就不需要處理這四個消息,你只需將消息鏈入COwnerDraw,它會調用你的類中的重載函數。

          如何將消息鏈入COwnerDraw取決與你是否將消息反射給控件,兩種方法有些不同。下面是COwnerDraw類的消息映射鏈,它使得兩種方法的差別更加明顯:

          template <class T> class COwnerDraw
          {
          public:
            BEGIN_MSG_MAP(COwnerDraw<T>)
              MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)
              MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem)
              MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem)
              MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem)
            ALT_MSG_MAP(1)
              MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
              MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem)
              MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem)
              MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem)
            END_MSG_MAP()
          };

          注意,消息映射鏈的主要部分處理WM_*消息,而ATL部分處理反射的消息,OCM_*。自畫的通知消息就像WM_NOTIFY消息一樣,你可以在父窗口處理它們,也可以將它們反射會控件,如果你使用前一種方法,消息被直接鏈入COwnerDraw:

          class CSomeDlg : public COwnerDraw<CSomeDlg>, ...
          {
            BEGIN_MSG_MAP(CSomeDlg)
              //...
              CHAIN_MSG_MAP(COwnerDraw<CSomeDlg>)
            END_MSG_MAP()
           
            void DrawItem ( LPDRAWITEMSTRUCT lpdis );
          };

          當然,如果你想要控件自己處理這些消息,你需要使用CHAIN_MSG_MAP_ALT宏將消息鏈入ALT_MSG_MAP(1)部分:

          class CSomeButtonImpl : public COwnerDraw<CSomeButtonImpl>, ...
          {
            BEGIN_MSG_MAP(CSomeButtonImpl)
              //...
              CHAIN_MSG_MAP_ALT(COwnerDraw<CSomeButtonImpl>, 1)
              DEFAULT_REFLECTION_HANDLER()
            END_MSG_MAP()
           
            void DrawItem ( LPDRAWITEMSTRUCT lpdis );
          };

          COwnerDraw類將對消息傳遞的參數展開,然后調用你的類中的實現函數。上面的例子中,我們自己的類實現DrawItem()函數,當有WM_DRAWITEM或OCM_DRAWITEM消息被鏈入COwnerDraw時,這個函數就會被調用。你可以重載的方法有:

          void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
          void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
          int  CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct);
          void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct);

          如果你不想處理某個消息,你可以調用SetMsgHandled(false),消息會被傳遞給消息映射鏈中的其他響應者。SetMsgHandled()事實上是COwnerDraw類的成員函數,但是它的作用和在BEGIN_MSG_MAP_EX()中使用SetMsgHandled()一樣。

          對于ControlMania2,它從ControlMania1中的樹控件開始,添加了自畫按鈕處理反射的WM_DRAWITEM消息,下面是資源編輯器中的新按鈕:

           [Owner-drawn button 1 - 7K]

          現在我們需要一個新類實現自畫按鈕:

          class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>,
                                public COwnerDraw<CODButtonImpl>
          {
          public:
              BEGIN_MSG_MAP_EX(CODButtonImpl)
                  CHAIN_MSG_MAP_ALT(COwnerDraw<CODButtonImpl>, 1)
                  DEFAULT_REFLECTION_HANDLER()
              END_MSG_MAP()
           
              void DrawItem ( LPDRAWITEMSTRUCT lpdis );
          };

          DrawItem()使用了像BitBlt()這樣的GDI函數向按鈕的表面畫位圖,代碼應該很容易理解,因為WTL使用的類名和函數名都和MFC類似。

          void CODButtonImpl::DrawItem ( LPDRAWITEMSTRUCT lpdis )
          {
          // NOTE: m_bmp is a CBitmap init''ed in the constructor.
          CDCHandle dc = lpdis->hDC;
          CDC dcMem;
           
              dcMem.CreateCompatibleDC ( dc );
              dc.SaveDC();
              dcMem.SaveDC();
           
              // Draw the button''s background, red if it has the focus, blue if not.
              if ( lpdis->itemState & ODS_FOCUS ) 
                  dc.FillSolidRect ( &lpdis->rcItem, RGB(255,0,0) );
              else
                  dc.FillSolidRect ( &lpdis->rcItem, RGB(0,0,255) );
           
              // Draw the bitmap in the top-left, or offset by 1 pixel if the button
              // is clicked.
              dcMem.SelectBitmap ( m_bmp );
           
              if ( lpdis->itemState & ODS_SELECTED ) 
                  dc.BitBlt ( 1, 1, 80, 80, dcMem, 0, 0, SRCCOPY );
              else
                  dc.BitBlt ( 0, 0, 80, 80, dcMem, 0, 0, SRCCOPY );
           
              dcMem.RestoreDC(-1);
              dc.RestoreDC(-1);
          }

          我們的按鈕看起來是這個樣子:

           [Owner-drawn button - 11K]

          CCustomDraw

          CCustomDraw類使用和COwnerDraw類相同的方法處理NM_CUSTOMDRAW消息,對于自定繪制的每個階段都有相應的重載函數:

          DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
          DWORD OnPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
          DWORD OnPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
          DWORD OnPostErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
           
          DWORD OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
          DWORD OnItemPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
          DWORD OnItemPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
          DWORD OnItemPostEraset(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
           
          DWORD OnSubItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);

          這些函數默認都是返回CDRF_DODEFAULT,如果想自畫控件或返回一個不同的值,就需要重載這些函數:

          你可能注意到上面的屏幕截圖將“道恩”(Dawn:女名)顯示成綠色,這是因為CBuffyTreeCtrl將消息鏈入CCustomDraw并重載了OnPrePaint()和OnItemPrePaint()方法。向樹控件中添加節點時,節點的item data字段被設置成1,OnItemPrePaint()檢查這個值,然后改變文字的顏色。

          DWORD CBuffyTreeCtrl::OnPrePaint(int idCtrl, 
                                           LPNMCUSTOMDRAW lpNMCD)
          {
              return CDRF_NOTIFYITEMDRAW;
          }
           
          DWORD CBuffyTreeCtrl::OnItemPrePaint(int idCtrl, 
                                               LPNMCUSTOMDRAW lpNMCD)
          {
              if ( 1 == lpNMCD->lItemlParam )
                  pnmtv->clrText = RGB(0,128,0);
           
              return CDRF_DODEFAULT;
          }

          CCustomDraw類也有SetMsgHandled()函數,你可以像在COwnerDraw類那樣使用這個函數。

          WTL的新控件

          WTL有幾個新控件,它們要么是其他封裝類的擴展(像 CTreeViewCtrlEx),要么是提供windows標準控件沒有的新功能(像 CHyperLink)。

          CBitmapButton

          WTL的CBitmapButton類聲明在atlctrlx.h中,它比MFC的同名類使用起來要簡單的多。WTL的CBitmapButton類使用image list而不是單個的位圖資源,你可以將多個按鈕的圖像放到一個位圖文件中,減少GDI資源的占用。這對于使用很多圖片并需要在Windows 9X系統上運行的程序很有好處,因為使用太多的單個位圖將會很快耗盡GDI資源并導致系統崩潰。

          CBitmapButton是一個CWindowImpl派生類,它又很多特色:自動調整控件的大小,自動生成3D邊框,支持hot-tracking,每個按鈕可以使用多個圖像分別表示按鈕的不同狀態。

          在ControlMania2中,我們對前面的例子創建的自畫按鈕使用CBitmapButton類。現在CMainDlg對話框類中添加CBitmapButton類型的變量m_wndBmpBtn,調用SubclassWindow()函數或使用DDX將其和控件聯系起來,將位圖裝載到image list并告訴按鈕使用這個image list,還要告訴按鈕每個圖像分別對應按鈕的什么狀態。下面是OnInitDialog()函數中建立和使用這個按鈕的代碼段:

              // Set up the bitmap button
          CImageList iml;
           
              iml.CreateFromImage ( IDB_ALYSON_IMGLIST, 81, 1, CLR_NONE,
                                    IMAGE_BITMAP, LR_CREATEDIBSECTION );
           
              m_wndBmpBtn.SubclassWindow ( GetDlgItem(IDC_ALYSON_BMPBTN) );
              m_wndBmpBtn.SetToolTipText ( _T("Alyson") );
              m_wndBmpBtn.SetImageList ( iml );
              m_wndBmpBtn.SetImages ( 0, 1, 2, 3 );

          默認情況下,按鈕只是引用image list,所以OnInitDialog()不能delete它所創建的image list。下面顯示的是新按鈕的一般狀態,注意控件是如何根據圖像的大小來調整自己的大小。

           [WTL bitmap button - 12K]

          因為CBitmapButton是一個非常有用的類,我想介紹一下它的公有方法。

          CBitmapButton methods

          CBitmapButtonImpl類包含了實現一個按鈕的所有代碼,除非你想重載某個方法或消息處理,你可以對控件直接使用CBitmapButton類。

          CBitmapButtonImpl constructor
          CBitmapButtonImpl(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE,HIMAGELIST hImageList = NULL)

          構造函數可以指定按鈕的擴展樣式(這與窗口的樣式不沖突)和圖像列表,通常使用默認參數就足夠了,因為可以使用其他的方法設定這些屬性。

          SubclassWindow()
          BOOL SubclassWindow(HWND hWnd)

          SubclassWindow()是個重載函數,主要完成控件的子類化和初始化控件類保有的內部數據。

          Bitmap button extended styles
          DWORD GetBitmapButtonExtendedStyle()
          DWORD SetBitmapButtonExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)

          CBitmapButton支持一些擴展樣式,這些擴展樣式會對按鈕的外觀和操作方式產生影響:

          BMPBTN_HOVER
          使用hot-tracking,當鼠標移到按鈕上時按鈕被畫成焦點狀態。
          BMPBTN_AUTO3D_SINGLE, BMPBTN_AUTO3D_DOUBLE
          在按鈕圖像周圍自動產生一個三維邊框,當按鈕擁有焦點時會顯示一個表示焦點的虛線矩形框。另外如果你沒有指定按鈕按下狀態的圖像,將會自動生成一個。BMPBTN_AUTO3D_DOUBLE樣式生成的邊框稍微粗一些,其他特征和BMPBTN_AUTO3D_SINGLE一樣。
          BMPBTN_AUTOSIZE
          按鈕調整自己的大小以適應圖像大小,這是默認樣式。
          BMPBTN_SHAREIMAGELISTS
          如果指定這個樣式,按鈕不負責銷毀按鈕使用的image list,如果不使用這個樣式,CBitmapButton的析構函數會銷毀按鈕使用的image list。
          BMPBTN_AUTOFIRE
          如果設置這個樣式,在按鈕上按住鼠標左鍵不放將會產生連續的WM_COMMAND消息。

          調用SetBitmapButtonExtendedStyle()時,dwMask參數控制著那個樣式將被改變,默認值是0,意味著用新樣式完全替換舊的樣式。

          Image list management
          HIMAGELIST GetImageList()
          HIMAGELIST SetImageList(HIMAGELIST hImageList)

          調用SetImageList()設置按鈕使用的image list。

          Tooltip management
          int  GetToolTipTextLength()
          bool GetToolTipText(LPTSTR lpstrText, int nLength)
          bool SetToolTipText(LPCTSTR lpstrText)

          CBitmapButton支持顯示工具提示(tooltip),調用SetToolTipText()指定顯示的文字。

          Setting the images to use
          void SetImages(int nNormal, int nPushed = -1,int nFocusOrHover = -1, int nDisabled = -1)

          調用SetImages()函數告訴按鈕分別使用image list的拿一個圖像表示那個狀態。nNormal是必須的,其它是可選的,使用-1表示對應的狀態沒有圖像。

          CCheckListViewCtrl

          CCheckListViewCtrl類在atlctrlx.h中定義,它是一個CWindowImpl派生類,實現了一個帶檢查框的list view控件。它和MFC的CCheckListBox不同,CCheckListBox只是一個list box,不是list view。CCheckListViewCtrl類非常簡單,只添加了很少的函數,當然,它使用了一個新的輔助類CCheckListViewCtrlImplTraits,它和CWinTraits類的作用類似,只是第三個參數是list view控件的擴展樣式屬性,如果你沒有定義自己的CCheckListViewCtrlImplTraits,它將使用沒默認的樣式:LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT。

          下面是一個定義list view擴展樣式屬性的例子,加入了一個使用這個樣式的新類。(注意,擴展屬性必須包含LVS_EX_CHECKBOXES,否則會因起斷言錯誤消息。)

          typedef CCheckListViewCtrlImplTraits<
              WS_CHILD | WS_VISIBLE | LVS_REPORT, 
              WS_EX_CLIENTEDGE,
              LVS_EX_CHECKBOXES | LVS_EX_GRIDLINES | LVS_EX_UNDERLINEHOT |
                LVS_EX_ONECLICKACTIVATE> CMyCheckListTraits;
           
          class CMyCheckListCtrl :
              public CCheckListViewCtrlImpl<CMyCheckListCtrl, CListViewCtrl, 
                                            CMyCheckListTraits>
          {
          private:
              typedef CCheckListViewCtrlImpl<CMyCheckListCtrl, CListViewCtrl, 
                                             CMyCheckListTraits> baseClass;
          public:
              BEGIN_MSG_MAP(CMyCheckListCtrl)
                  CHAIN_MSG_MAP(baseClass)
              END_MSG_MAP()
          };
          CCheckListViewCtrl methods SubclassWindow()

          當子類化一個已經存在的list view控件時,SubclassWindow()查看CCheckListViewCtrlImplTraits的擴展樣式屬性并將之應用到控件上。未用到前兩個參數(窗口樣式和擴展窗口樣式)。

          SetCheckState() and GetCheckState()

          這些方法實際上是在CListViewCtrl中,SetCheckState()使用行的索引和一個布爾類型參數,該布爾參數的值表示是否check這一行。GetCheckState()以行索引未參數,返回改行的checked狀態。

          CheckSelectedItems()

          這個方法使用item的索引作為參數,它翻轉這個item的check狀態,這個item必須是被選定的,同時還將其他所有被選擇的item設置成相應狀態(譯者加:多選狀態下)。你大概不會用到這個方法,因為CCheckListViewCtrl會在check box被單擊或用戶按下了空格鍵時設置相應的item的狀態。

          下面是ControlMania2中的CCheckListViewCtrl的樣子:

           [Check list ctrl - 12K]

          CTreeViewCtrlEx and CTreeItem

          有兩個類使得樹控件的使用簡化了很多:CTreeItem類封裝了HTREEITEM,一個CTreeItem對象含有一個HTREEITEM和一個指向包含這個HTREEITEM的樹控件的指針,使你不必每次調用都引用樹控件;CTreeViewCtrlEx和CTreeViewCtrl一樣,只是它的方法操作CTreeItem而不是HTREEITEM。例如,InsertItem()函數返回一個CTreeItem而不是HTREEITEM,你可以使用CTreeItem操作新添加的item。下面是一個例子:

          // Using plain HTREEITEMs:
          HTREEITEM hti, hti2;
           
              hti = m_wndTree.InsertItem ( "foo", TVI_ROOT, TVI_LAST );
              hti2 = m_wndTree.InsertItem ( "bar", hti, TVI_LAST );
              m_wndTree.SetItemData ( hti2, 100 );
           
          // Using CTreeItems:
          CTreeItem ti, ti2;
           
              ti = m_wndTreeEx.InsertItem ( "foo", TVI_ROOT, TVI_LAST );
              ti2 = ti.AddTail ( "bar", 0 );
              ti2.SetData ( 100 );

          CTreeViewCtrl對HTREEITEM的每一個操作,CTreeItem都有與之對應的方法,正像每一個關于HWND的API都有一個CWindow方法與之對應一樣。查看ControlMania2的代碼可以看到更多的CTreeViewCtrlEx和CTreeItem類的方法的演示。

          CHyperLink

          CHyperLink是一個CWindowImpl派生類,它子類化一個static text控件,使之變成可點擊的超鏈接。CHyperLink根據用戶的IE使用的顏色畫鏈接對象,還支持鍵盤導航。CHyperLink類的構造函數沒有參數,下面是其它的公有方法。

          CHyperLink methods

          CHyperLinkImpl類內含實現一個超鏈接的全部代碼,如果不需要重載它的方法或處理消息的話,你可以直接使用CHyperLink類。

          SubclassWindow()
          BOOL SubclassWindow(HWND hWnd)

          重載函數SubclassWindow()完成控件子類化,然后初始化該類保有的內部數據。

          Text label management
          bool GetLabel(LPTSTR lpstrBuffer, int nLength)
          bool SetLabel(LPCTSTR lpstrLabel)

          獲得或設置控件顯示的文字,如果不指定顯示文字,控件會顯示資源編輯器指定給控件的靜態字符串。

          Hyperlink management
          bool GetHyperLink(LPTSTR lpstrBuffer, int nLength)
          bool SetHyperLink(LPCTSTR lpstrLink)

          獲得或設置控件關聯超鏈接的URL,如果不指定超鏈接URL,控件會使用顯示的文字字符串作為URL。

          Navigation
          bool Navigate()

          導航到當前超鏈接的URL,該URL或者是由SetHyperLink()函數指定的URL,或者就是控件的窗口文字。

          Tooltip management

          沒有公開的方法設置工具提示,所以需要直接使用CToolTipCtrl成員m_tip。

          下圖顯示的就是ControlMania2對話框中的超鏈接控件:

           [WTL hyperlink - 12K]

          在OnInitDialog()函數中設置URL:

              m_wndLink.SetHyperLink ( _T("http://www.codeproject.com/") );
          對話框中控件的UI Updating

          對話框中的的UI updating控制比MFC中簡單得多,在MFC中,你需要響應未公開的WM_KICKIDLE消息,處理這個消息并觸發控件的updating,在WTL中,沒有這個詭計,不過向導存在一個BUG,需要手工添加一行代碼解決這個問題。

          首先需要記住的是對話框必須是無模式的,因為CUpdateUI需要在程序的消息循環控制下工作。如果對話框是模式的,系統處理消息循環,我們程序的空閑處理函數就不會被調用,由于CUpdateUI是在空閑時間工作的,所以沒有空閑處理就沒有UI updating。

          ControlMania2的對話框是非模式的,類定義的開始部分很像是一個框架窗口類:

          class CMainDlg : public CDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>,
                           public CMessageFilter, public CIdleHandler
          {
          public:
              enum { IDD = IDD_MAINDLG };
           
              virtual BOOL PreTranslateMessage(MSG* pMsg);
              virtual BOOL OnIdle();
           
              BEGIN_MSG_MAP_EX(CMainDlg)
                  MSG_WM_INITDIALOG(OnInitDialog)
                  COMMAND_ID_HANDLER_EX(IDOK, OnOK)
                  COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
                  COMMAND_ID_HANDLER_EX(IDC_ALYSON_BTN, OnAlysonODBtn)
              END_MSG_MAP()
           
              BEGIN_UPDATE_UI_MAP(CMainDlg)
              END_UPDATE_UI_MAP()
          //...
          };

          注意CMainDlg類從CUpdateUI派生并含有一個update UI鏈。OnInitDialog()做了這些工作,這和前面介紹的框架窗口中的代碼很相似:

              // register object for message filtering and idle updates
              CMessageLoop* pLoop = _Module.GetMessageLoop();
              ATLASSERT(pLoop != NULL);
              pLoop->AddMessageFilter(this);
              pLoop->AddIdleHandler(this);
           
              UIAddChildWindowContainer(m_hWnd);

          只是這次我們不是調用UIAddToolbar()或UIAddStatusBar(),而是調用UIAddChildWindowContainer(),它告訴CUpdateUI我們的對話框含有需要updating的字窗口,只要看看OnIdle(),你會懷疑少了寫什么:

          BOOL CMainDlg::OnIdle()
          {
              return FALSE;
          }

          你可能猜想這里應該調用另一個CUpdateUI的方法做一些實在的updating工作,你是對的,應該是這樣的,向導在OnIdle()中漏掉了一行代碼,現在加上:

          BOOL CMainDlg::OnIdle()
          {
              UIUpdateChildWindows();
              return FALSE;
          }

          為了演示UI updating,我們設定鼠標點擊左邊的位圖按鈕,使得右邊的按鈕變得可用或禁用。先在update UI鏈中添加一個消息入口,使用UPDUI_CHILDWINDOW標志表示此入口是子窗口類型:

              BEGIN_UPDATE_UI_MAP(CMainDlg)
                  UPDATE_ELEMENT(IDC_ALYSON_BMPBTN, UPDUI_CHILDWINDOW)
              END_UPDATE_UI_MAP()

          在左邊的按鈕的單擊事件處理中,我們調用UIEnable()來翻轉另一個按鈕的使能狀態:

          void CMainDlg::OnAlysonODBtn ( UINT uCode, int nID, HWND hwndCtrl )
          {
          static bool s_bBtnEnabled = true;
          
              s_bBtnEnabled = !s_bBtnEnabled;
              UIEnable ( IDC_ALYSON_BMPBTN, s_bBtnEnabled );
          }
          DDV

          WTL的對話框數據驗證(DDV)比MFC簡單一些,在MFC中你需要分別使用DDX(對話框數據交換)宏和DDV(對話框數據驗證)宏,在WTL中只需一個宏就可以了,WTL包含基本的數據驗證支持,在DDV鏈中可以使用三個宏:

          DDX_TEXT_LEN
          和DDX_TEXT一樣,只是還要驗證字符串的長度(不包含結尾的空字符)小于或等于限制長度。
          DDX_INT_RANGE and DDX_UINT_RANGE
          和DDX_INT,DDX_UINT一樣,還加了對數字的最大最小值的驗證。
          DDX_FLOAT_RANGE
          除了像DDX_FLOAT一樣完成數據交換之外,還驗證數字的最大最小值。

          ControlMania2有一個ID是IDC_FAV_SEASON的edit box,它和成員變量m_nSeason相關聯。

           [Season selector edit box - 13K]

          由于有效的值是1到7,所以使用這樣的數據驗證宏:

              BEGIN_DDX_MAP(CMainDlg)
              //...
                  DDX_INT_RANGE(IDC_FAV_SEASON, m_nSeason, 1, 7)
              END_DDX_MAP()

          OnOK()調用DoDataExchange()獲得season的數值,并驗證是在1到7之間。

          處理DDV驗證失敗

          如果控件的數據驗證失敗,CWinDataExchange會調用重載函數OnDataValidateError(),默認到處理是驅動PC喇叭發出聲音,你可能想給出更友好的錯誤指示。OnDataValidateError()的函數原型是:

          void OnDataValidateError ( UINT nCtrlID, BOOL bSave, _XData& data );

          _XData是一個WTL的內部數據結構,CWinDataExchange根據輸入的數據和允許的數據范圍填充這個數據結構。下面是這個數據結構的定義:

          struct _XData
          {
              _XDataType nDataType;
              union
              {
                  _XTextData textData;
                  _XIntData intData;
                  _XFloatData floatData;
              };
          };

          nDataType指示聯合中的三個成員那個是有意義的,nDataType 的取值可以是:

          enum _XDataType
          {
              ddxDataNull = 0,
              ddxDataText = 1,
              ddxDataInt = 2,
              ddxDataFloat = 3,
              ddxDataDouble = 4
          };

          在我們的例子中,nDataType的值是ddxDataInt,這表示_XData中的_XIntData成員是有效的,_XIntData是個簡單的數據結構:

          struct _XIntData
          {
              long nVal;
              long nMin;
              long nMax;
          };

          我們重載OnDataValidateError()函數,顯示錯誤信息并告訴用戶允許的數值范圍:

          void CMainDlg::OnDataValidateError ( UINT nCtrlID, BOOL bSave, _XData& data )
          {
          CString sMsg;
           
              sMsg.Format ( _T("Enter a number between %d and %d"),
                            data.intData.nMin, data.intData.nMax );
           
              MessageBox ( sMsg, _T("ControlMania2"), MB_ICONEXCLAMATION );
           
              ::SetFocus ( GetDlgItem(nCtrlID) );
          }

          _XData中的另外兩個結構_XTextData和_XFloatData的定義在atlddx.h中,感興趣的話可以打開這個文件查看一下。

          改變對話框的大小

          WTL引起我的注意的第一件事是對可調整大小對話框的內建的支持。在這之前我曾寫過一篇關于這個主題的文章,詳情請參考這篇文章。簡單的說就是將CDialogResize類添加到對話框的集成列表,在OnInitDialog()中調用DlgResize_Init(),然后將消息鏈入CDialogResize。

          繼續

          下一章,我將介紹如何在對話框中使用ActiveX控件和如何處理控件觸發的事件。

          參考

          Using WTL''s Built-in Dialog Resizing Class - Michael Dunn

          Using DDX and DDV with WTL - Less Wright


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 辰溪县| 拉萨市| 罗平县| 昆山市| 文成县| 酒泉市| 临沂市| 仁寿县| 皮山县| 永和县| 太谷县| 洪湖市| 富宁县| 资阳市| 连城县| 淮阳县| 新乡县| 鹤岗市| 万全县| 彭山县| 松潘县| 昆山市| 兴仁县| 上高县| 鹿泉市| 阳谷县| 龙井市| 怀安县| 尤溪县| 陕西省| 大城县| 广安市| 阜城县| 长垣县| 贵州省| 尖扎县| 哈巴河县| 娄烦县| 肥乡县| 晋中市| 廊坊市|