• VC 控件集合


    常用类
    CRect:用来表示矩形的类,拥有四个成员变量:top left bottom right。分别表是左上角和右下角的坐标。可以通过以下的方法构造:
    CRect( int l, int t, int r, int b ); 指明四个坐标
    CRect( const RECT& srcRect ); 由RECT结构构造
    CRect( LPCRECT lpSrcRect ); 由RECT结构构造
    CRect( POINT point, SIZE size ); 有左上角坐标和尺寸构造
    CRect( POINT topLeft, POINT bottomRight ); 有两点坐标构造
    下面介绍几个成员函数:
    int Width( ) const; 得到宽度
    int Height( ) const; 得到高度
    CSize Size( ) const; 得到尺寸
    CPoint& TopLeft( ); 得到左上角坐标
    CPoint& BottomRight( ); 得到右下角坐标
    CPoint CenterPoint( ) const; 得当中心坐标
    此外矩形可以和点(CPoint)相加进行位移,和另一个矩形相加得到“并”操作后的矩形。

    CPoint:用来表示一个点的坐标,有两个成员变量:x y。 可以和另一个点相加。
    CString:用来表示可变长度的字符串。使用CString可不指明内存大小,CString会根据需要自行分配。下面介绍几个成员函数:
    GetLength 得到字符串长度
    GetAt 得到指定位置处的字符
    operator + 相当于strcat
    void Format( LPCTSTR lpszFormat, ... ); 相当于sprintf
    Find 查找指定字符,字符串
    Compare 比较
    CompareNoCase 不区分大小写比较
    MakeUpper 改为小写
    MakeLower 改为大写
    CStringArray:用来表示可变长度的字符串数组。数组中每一个元素为CString对象的实例。下面介绍几个成员函数:
    Add 增加CString
    RemoveAt 删除指定位置CString对象
    RemoveAll 删除数组中所有CString对象
    GetAt 得到指定位置的CString对象
    SetAt 修改指定位置的CString对象
    InsertAt 在某一位置插入CString对象
    常用宏
    RGB
    TRACE
    ASSERT
    VERIFY

    常用函数
    CWindApp* AfxGetApp();
    HINSTANCE AfxGetInstanceHandle( );
    HINSTANCE AfxGetResourceHandle( );
    int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );用于弹出一个消息框
     
    ///////////////////////////////////////////////////////////////控件////////////////////////////////////////
    4.1 Button
    按钮窗口(控件)在MFC中使用CButton表示,CButton包含了三种样式的按钮,Push Button,Check Box,Radio Box。所以在利用CButton对象生成按钮窗口时需要指明按钮的风格。
    创建按钮:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其中lpszCaption是按钮上显示的文字,dwStyle为按钮风格,除了Windows风格可以使用外(如WS_CHILD|WS_VISUBLE|WS_BORDER)还有按钮专用的一些风格。
    BS_AUTOCHECKBOX 检查框,按钮的状态会自动改变   Same as a check box, except that a check mark appears in the check box when the user selects the box; the check mark disappears the next time the user selects the box.

    BS_AUTORADIOBUTTON 圆形选择按钮,按钮的状态会自动改变   Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group.

    BS_AUTO3STATE 允许按钮有三种状态即:选中,未选中,未定   Same as a three-state check box, except that the box changes its state when the user selects it.

    BS_CHECKBOX 检查框   Creates a small square that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style).

    BS_DEFPUSHBUTTON 默认普通按钮   Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option).

    BS_LEFTTEXT 左对齐文字   When combined with a radio-button or check-box style, the text appears on the left side of the radio button or check box.

    BS_OWNERDRAW 自绘按钮   Creates an owner-drawn button. The framework calls the DrawItem member function when a visual aspect of the button has changed. This style must be set when using the CBitmapButton class.

    BS_PUSHBUTTON 普通按钮   Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button.

    BS_RADIOBUTTON 圆形选择按钮   Creates a small circle that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). Radio buttons are usually used in groups of related but mutually exclusive choices.

    BS_3STATE 允许按钮有三种状态即:选中,未选中,未定   Same as a check box, except that the box can be dimmed as well as checked. The dimmed state typically is used to show that a check box has been disabled.
    rect为窗口所占据的矩形区域,pParentWnd为父窗口指针,nID为该窗口的ID值。

    获取/改变按钮状态:对于检查按钮和圆形按钮可能有两种状态,选中和未选中,如果设置了BS_3STATE或BS_AUTO3STATE风格就可能出现第三种状态:未定,这时按钮显示灰色。通过调用int CButton::GetCheck( ) 得到当前是否被选中,返回0:未选中,1:选中,2:未定。调用void CButton::SetCheck( int nCheck );设置当前选中状态。
    处理按钮消息:要处理按钮消息需要在父窗口中进行消息映射,映射宏为ON_BN_CLICKED( id, memberFxn )id为按钮的ID值,就是创建时指定的nID值。处理函数原型为afx_msg void memberFxn( );
    4.2 Static Box
    静态文本控件的功能比较简单,可作为显示字符串,图标,位图用。创建一个窗口可以使用成员函数:
    BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
    其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对静态控件指明专门的风格。
    SS_CENTER,SS_LEFT,SS_RIGHT 指明字符显示的对齐方式。
    SS_GRAYRECT 显示一个灰色的矩形
    SS_NOPREFIX 如果指明该风格,对于字符&将直接显示,否则&将作为转义符,&将不显示而在其后的字符将有下划线,如果需要直接显示&必须使用&&表示。
    SS_BITMAP 显示位图
    SS_ICON 显示图标
    SS_CENTERIMAGE 图象居中显示
    控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。
    控制显示的图标利用成员函数SetIcon/GetIcon用于设置/得到当前显示的图标。
    控制显示的位图利用成员函数SetBitmap/GetBitmap用于设置/得到当前显示的位图。下面一段代码演示如何创建一个显示位图的静态窗口并设置位图
    CStatic* pstaDis=new CStatic;
    pstaDis->Create("",WS_CHILD|WS_VISIBLE|SS_BITMAP|SSCENTERIMAGE,
    CRect(0,0,40,40),pWnd,1);
    CBitmap bmpLoad;
    bmpLoad.LoadBitmap(IDB_TEST);
    pstaDis->SetBitmap(bmpLoad.Detach());

    4.3 Edit Box
    Edit窗口是用来接收用户输入最常用的一个控件。创建一个输入窗口可以使用成员函数:
    BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
    其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对输入控件指明专门的风格。
    ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明输入文字超出显示范围时自动滚动。
    ES_CENTER,ES_LEFT,ES_RIGHT 指定对齐方式
    ES_MULTILINE 是否允许多行输入
    ES_PASSWORD 是否为密码输入框,如果指明该风格则输入的文字显示为*
    ES_READONLY 是否为只读
    ES_UPPERCASE,ES_LOWERCASE 显示大写/小写字符
    控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。
    通过GetLimitText/SetLimitText可以得到/设置在输入框中输入的字符数量。
    由于在输入时用户可能选择某一段文本,所以通过void CEdit::GetSel( int& nStartChar, int& nEndChar )得到用户选择的字符范围,通过调用void CEdit::SetSel( int nStartChar, int nEndChar, BOOL bNoScroll = FALSE )可以设置当前选择的文本范围,如果指定nStartChar=0 nEndChar=-1则表示选中所有的文本。void ReplaceSel( LPCTSTR lpszNewText, BOOL bCanUndo = FALSE )可以将选中的文本替换为指定的文字。
    此外输入框还有一些和剪贴板有关的功能,void Clear( );删除选中的文本,void Copy( );可将选中的文本送入剪贴板,void Paste( );将剪贴板中内容插入到当前输入框中光标位置,void Cut( );相当于Copy和Clear结合使用。
    最后介绍一下输入框几种常用的消息映射宏:
    ON_EN_CHANGE 输入框中文字更新后产生
    ON_EN_ERRSPACE 输入框无法分配内存时产生
    ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在输入框失去/得到输入焦点时产生
    使用以上几种消息映射的方法为定义原型如:afx_msg void memberFxn( );的函数,并且定义形式如ON_Notification( id, memberFxn )的消息映射。如果在对话框中使用输入框,Class Wizard会自动列出相关的消息,并能自动产生消息映射代码。

    4.4 Scroll Bar
    Scroll Bar一般不会单独使用,因为SpinCtrl可以取代滚动条的一部分作用,但是如果你需要自己生成派生窗口,滚动条还是会派上一些用场。创建一个滚动条可以使用成员函数: :
    BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
    其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对滚动条指明专门的风格。
    SBS_VERT 风格将创建一个垂直的滚动条。
    SBS_HORZ 风格将创建一个水平的滚动条。
    在创建滚动条后需要调用void SetScrollRange( int nMinPos, int nMaxPos, BOOL bRedraw = TRUE )设置滚动范围,
    int GetScrollPos( )/int SetScrollPos( )用来得到和设置当前滚动条的位置。
    void ShowScrollBar( BOOL bShow = TRUE );用来显示/隐藏滚动条。
    BOOL EnableScrollBar( UINT nArrowFlags = ESB_ENABLE_BOTH )用来设置滚动条上箭头是否为允许状态。nArrowFlags可取以下值:
    ESB_ENABLE_BOTH 两个箭头都为允许状态
    ESB_DISABLE_LTUP 上/左箭头为禁止状态
    ESB_DISABLE_RTDN 下/右箭头为禁止状态
    ESB_DISABLE_BOTH 两个箭头都为禁止状态

    如果需要在滚动条位置被改变时得到通知,需要在父窗口中定义对消息WM_VSCROLL/WM_HSCROLL的映射。方法为在父窗口类中重载
    afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )/afx_msg void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )
    所使用的消息映射宏为:ON_WM_VSCROLL( ),ON_WM_HSCROLL( ),在映射宏中不需要指明滚动条的ID,因为所有滚动条的滚动消息都由同样的函数处理。在OnHScroll/OnVScroll的第三个参数会指明当前滚动条的指针。第一个参数表示滚动条上发生的动作,可取以下值:
    SB_TOP/SB_BOTTOM 已滚动到顶/底部
    SB_LINEUP/SB_LINEDOWN 向上/下滚动一行
    SB_PAGEDOWN/SB_PAGEUP 向上/下滚动一页
    SB_THUMBPOSITION/SB_THUMBTRACK 滚动条拖动到某一位置,参数nPos指明当前位置(参数nPos在其它的情况下是无效的)
    SB_ENDSCROLL 滚动条拖动完成(用户松开鼠标)

    4.5 List Box/Check List Box
    ListBox窗口用来列出一系列的文本,每条文本占一行。创建一个列表窗口可以使用成员函数:
    BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
    其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对列表控件指明专门的风格。
    LBS_MULTIPLESEL 指明列表框可以同时选择多行
    LBS_EXTENDEDSEL 可以通过按下Shift/Ctrl键选择多行
    LBS_SORT 所有的行按照字母顺序进行排序
    在列表框生成后需要向其中加入或是删除行,可以利用:
    int AddString( LPCTSTR lpszItem )添加行,
    int DeleteString( UINT nIndex )删除指定行,
    int InsertString( int nIndex, LPCTSTR lpszItem )将行插入到指定位置。
    void ResetContent( )可以删除列表框中所有行。
    通过调用int GetCount( )得到当前列表框中行的数量。
    如果需要得到/设置当前被选中的行,可以调用int GetCurSel( )/int SetCurSel(int iIndex)。如果你指明了选择多行的风格,你就需要先调用int GetSelCount( )得到被选中的行的数量,然后int GetSelItems( int nMaxItems, LPINT rgIndex )得到所有选中的行,参数rgIndex为存放被选中行的数组。通过调用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框内指定行的字符串。
    此外通过调用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在当前所有行中查找指定的字符传的位置,nStartAfter指明从那一行开始进行查找。
    int SelectString( int nStartAfter, LPCTSTR lpszItem )可以选中包含指定字符串的行。
    在MFC 4.2版本中添加了CCheckListBox类,该类是由CListBox派生并拥有CListBox的所有功能,不同的是可以在每行前加上一个检查框。必须注意的是在创建时必须指明LBS_OWNERDRAWFIXED或LBS_OWNERDRAWVARIABLE风格。
    通过void SetCheckStyle( UINT nStyle )/UINT GetCheckStyle( )可以设置/得到检查框的风格,关于检查框风格可以参考4.1 Button中介绍。通过void SetCheck( int nIndex, int nCheck )/int GetCheck( int nIndex )可以设置和得到某行的检查状态,关于检查框状态可以参考4.1 Button中介绍。
    最后介绍一下列表框几种常用的消息映射宏:
    ON_LBN_DBLCLK 鼠标双击
    ON_EN_ERRSPACE 输入框无法分配内存时产生
    ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在输入框失去/得到输入焦点时产生
    ON_LBN_SELCHANGE 选择的行发生改变
    使用以上几种消息映射的方法为定义原型如:afx_msg void memberFxn( );的函数,并且定义形式如ON_Notification( id, memberFxn )的消息映射。如果在对话框中使用列表框,Class Wizard会自动列出相关的消息,并能自动产生消息映射代码。

    4.6 Combo Box/Combo Box Ex
    组合窗口是由一个输入框和一个列表框组成。创建一个组合窗口可以使用成员函数:
    BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
    其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对列表控件指明专门的风格。
    CBS_DROPDOWN 下拉式组合框
    CBS_DROPDOWNLIST 下拉式组合框,但是输入框内不能进行输入
    CBS_SIMPLE 输入框和列表框同时被显示
    LBS_SORT 所有的行按照字母顺序进行排序
    由于组合框内包含了列表框,所以列表框的功能都能够使用,如可以利用:
    int AddString( LPCTSTR lpszItem )添加行,
    int DeleteString( UINT nIndex )删除指定行,
    int InsertString( int nIndex, LPCTSTR lpszItem )将行插入到指定位置。
    void ResetContent( )可以删除列表框中所有行。
    通过调用int GetCount( )得到当前列表框中行的数量。
    如果需要得到/设置当前被选中的行的位置,可以调用int GetCurSel( )/int SetCurSel(int iIndex)。通过调用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框内指定行的字符串。
    此外通过调用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在当前所有行中查找指定的字符传的位置,nStartAfter指明从那一行开始进行查找。
    int SelectString( int nStartAfter, LPCTSTR lpszItem )可以选中包含指定字符串的行。
    此外输入框的功能都能够使用,如可以利用:
    DWORD GetEditSel( ) /BOOL SetEditSel( int nStartChar, int nEndChar )得到或设置输入框中被选中的字符位置。
    BOOL LimitText( int nMaxChars )设置输入框中可输入的最大字符数。
    输入框的剪贴板功能Copy,Clear,Cut,Paste动可以使用。
    最后介绍一下列表框几种常用的消息映射宏:
    ON_CBN_DBLCLK 鼠标双击
    ON_CBN_DROPDOWN 列表框被弹出
    ON_CBN_KILLFOCUS / ON_CBN_SETFOCUS 在输入框失去/得到输入焦点时产生
    ON_CBN_SELCHANGE 列表框中选择的行发生改变
    ON_CBN_EDITUPDATE 输入框中内容被更新
    使用以上几种消息映射的方法为定义原型如:afx_msg void memberFxn( );的函数,并且定义形式如ON_Notification( id, memberFxn )的消息映射。如果在对话框中使用组合框,Class Wizard会自动列出相关的消息,并能自动产生消息映射代码。

    4.7 Tree Ctrl

    树形控件TreeCtrl和下节要讲的列表控件 ListCtrl在系统中大量被使用,例如Windows资源管理器就是一个典型的例子。
    树形控件可以用于树形的结构,其中有一个根接点(Root)然后下面有许多子结点,而每个子结点上有允许有一个或多个或没有子结点。MFC中使用CTreeCtrl类来封装树形控件的各种操作。通过调用
    BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );创建一个窗口,dwStyle中可以使用以下一些树形控件的专用风格:
    TVS_HASLINES 在父/子结点之间绘制连线
    TVS_LINESATROOT 在根/子结点之间绘制连线
    TVS_HASBUTTONS 在每一个结点前添加一个按钮,用于表示当前结点是否已被展开
    TVS_EDITLABELS 结点的显示字符可以被编辑
    TVS_SHOWSELALWAYS 在失去焦点时也显示当前选中的结点
    TVS_DISABLEDRAGDROP 不允许Drag/Drop
    TVS_NOTOOLTIPS 不使用ToolTip显示结点的显示字符
    在树形控件中每一个结点都有一个句柄(HTREEITEM),同时添加结点时必须提供的参数是该结点的父结点句柄,(其中根Root结点只有一个,既不可以添加也不可以删除)利用
    HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一个结点,pszItem为显示的字符,hParent代表父结点的句柄,当前添加的结点会排在hInsertAfter表示的结点的后面,返回值为当前创建的结点的句柄。下面的代码会建立一个如下形式的树形结构:
    +--- Parent1
        +--- Child1_1
        +--- Child1_2
        +--- Child1_3
    +--- Parent2
    +--- Parent3
    /*假设m_tree为一个CTreeCtrl对象,而且该窗口已经创建*/
    HTREEITEM hItem,hSubItem;
    hItem = m_tree.InsertItem("Parent1",TVI_ROOT);
    在根结点上添加Parent1
    hSubItem = m_tree.InsertItem("Child1_1",hItem);
    //在Parent1上添加一个子结点
    hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem);
    //在Parent1上添加一个子结点,排在Child1_1后面
    hSubItem = m_tree.InsertItem("Child1_3",hItem,hSubItem);
    hItem = m_tree.InsertItem("Parent2",TVI_ROOT,hItem);   
    hItem = m_tree.InsertItem("Parent3",TVI_ROOT,hItem);   
    如果你希望在每个结点前添加一个小图标,就必需先调用CImageList* SetImageList( CImageList * pImageList, int nImageListType );指明当前所使用的ImageList,nImageListType为TVSIL_NORMAL。在调用完成后控件中使用图片以设置的ImageList中图片为准。然后调用
    HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加结点,nImage为结点没被选中时所使用图片序号,nSelectedImage为结点被选中时所使用图片序号。下面的代码演示了ImageList的设置。
    /*m_list 为CImageList对象
    IDB_TREE 为16*(16*4)的位图,每个图片为16*16共4个图标*/
    m_list.Create(IDB_TREE,16,4,RGB(0,0,0));
    m_tree.SetImageList(&m_list,TVSIL_NORMAL);
    m_tree.InsertItem("Parent1",0,1);
    //添加,选中时显示图标1,未选中时显示图标0
    此外CTreeCtrl还提供了一些函数用于得到/修改控件的状态。
    HTREEITEM GetSelectedItem( );将返回当前选中的结点的句柄。BOOL SelectItem( HTREEITEM hItem );将选中指明结点。
    BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用于得到/修改某结点所使用图标索引。
    CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用于得到/修改某一结点的显示字符。
    BOOL DeleteItem( HTREEITEM hItem );用于删除某一结点,BOOL DeleteAllItems( );将删除所有结点。
    此外如果想遍历树可以使用下面的函数:
    HTREEITEM GetRootItem( );得到根结点。
    HTREEITEM GetChildItem( HTREEITEM hItem );得到子结点。
    HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明结点的上/下一个兄弟结点。
    HTREEITEM GetParentItem( HTREEITEM hItem );得到父结点。
    树形控件的消息映射使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode为通知代码,id为产生该消息的窗口ID,memberFxn为处理函数,函数的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR为一数据结构,在具体使用时需要转换成其他类型的结构。对于树形控件可能取值和对应的数据结构为:
    TVN_SELCHANGED 在所选中的结点发生改变后发送,所用结构:NMTREEVIEW
    TVN_ITEMEXPANDED 在某结点被展开后发送,所用结构:NMTREEVIEW
    TVN_BEGINLABELEDIT 在开始编辑结点字符时发送,所用结构:NMTVDISPINFO
    TVN_ENDLABELEDIT 在结束编辑结点字符时发送,所用结构:NMTVDISPINFO
    TVN_GETDISPINFO 在需要得到某结点信息时发送,(如得到结点的显示字符)所用结构:NMTVDISPINFO
    关于ON_NOTIFY有很多内容,将在以后的内容中进行详细讲解。

    关于动态提供结点所显示的字符:首先你在添加结点时需要指明lpszItem参数为:LPSTR_TEXTCALLBACK。在控件显示该结点时会通过发送TVN_GETDISPINFO来取得所需要的字符,在处理该消息时先将参数pNMHDR转换为LPNMTVDISPINFO,然后填充其中item.pszText。但是我们通过什么来知道该结点所对应的信息呢,我的做法是在添加结点后设置其lParam参数,然后在提供信息时利用该参数来查找所对应的信息。下面的代码说明了这种方法:
    char szOut[8][3]={"No.1","No.2","No.3"};
    //添加结点
    HTREEITEM hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...)
    m_tree.SetItemData(hItem, 0 );
    hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...)
    m_tree.SetItemData(hItem, 1 );
    //处理消息
    void CParentWnd::OnGetDispInfoTree(NMHDR* pNMHDR, LRESULT* pResult)
    {
     TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR;
     pTVDI->item.pszText=szOut[pTVDI->item.lParam];
    //通过lParam得到需要显示的字符在数组中的位置
     *pResult = 0;
    }
    关于编辑结点的显示字符:首先需要设置树形控件的TVS_EDITLABELS风格,在开始编辑时该控件将会发送TVN_BEGINLABELEDIT,你可以通过在处理函数中返回TRUE来取消接下来的编辑,在编辑完成后会发送TVN_ENDLABELEDIT,在处理该消息时需要将参数pNMHDR转换为LPNMTVDISPINFO,然后通过其中的item.pszText得到编辑后的字符,并重置显示字符。如果编辑在中途中取消该变量为NULL。下面的代码说明如何处理这些消息:
    //处理消息 TVN_BEGINLABELEDIT
    void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult)
    {
     TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR;
     if(pTVDI->item.lParam==0);//判断是否取消该操作
      *pResult = 1;
     else
      *pResult = 0;
    }
    //处理消息 TVN_BEGINLABELEDIT
    void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult)
    {
     TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR;
     if(pTVDI->item.pszText==NULL);//判断是否已经取消取消编辑
      m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText);
    //重置显示字符
     *pResult = 0;
    }
     
    上面讲述的方法所进行的消息映射必须在父窗口中进行(同样WM_NOTIFY的所有消息都需要在父窗口中处理)。
     
    4.8 List Ctrl

    列表控件可以看作是功能增强的ListBox,它提供了四种风格,而且可以同时显示一列的多中属性值。MFC中使用CListCtrl类来封装列表控件的各种操作。通过调用
    BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );创建一个窗口,dwStyle中可以使用以下一些列表控件的专用风格:
    LVS_ICON LVS_SMALLICON LVS_LIST LVS_REPORT 这四种风格决定控件的外观,同时只可以选择其中一种,分别对应:大图标显示,小图标显示,列表显示,详细报表显示
    LVS_EDITLABELS 结点的显示字符可以被编辑,对于报表风格来讲可编辑的只为第一列。
    LVS_SHOWSELALWAYS 在失去焦点时也显示当前选中的结点
    LVS_SINGLESEL 同时只能选中列表中一项
    首先你需要设置列表控件所使用的ImageList,如果你使用大图标显示风格,你就需要以如下形式调用:
    CImageList* SetImageList( CImageList* pImageList, LVSIL_NORMAL);
    如果使用其它三种风格显示而不想显示图标你可以不进行任何设置,否则需要以如下形式调用:
    CImageList* SetImageList( CImageList* pImageList, LVSIL_SMALL);

    通过调用int InsertItem( int nItem, LPCTSTR lpszItem );可以在列表控件中nItem指明位置插入一项,lpszItem为显示字符。除LVS_REPORT风格外其他三种风格都只需要直接调用InsertItem就可以了,但如果使用报表风格就必须先设置列表控件中的列信息。
    通过调用int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat , int nWidth, int nSubItem);可以插入列。iCol为列的位置,从零开始,lpszColumnHeading为显示的列名,nFormat为显示对齐方式,nWidth为显示宽度,nSubItem为分配给该列的列索引。
    在有多列的列表控件中就需要为每一项指明其在每一列中的显示字符,通过调用
    BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );可以设置每列的显示字符。nItem为设置的项的位置,nSubItem为列位置,lpszText为显示字符。下面的代码演示了如何设置多列并插入数据:
    m_list.SetImageList(&m_listSmall,LVSIL_SMALL);//设置ImageList
    m_list.InsertColumn(0,"Col 1",LVCFMT_LEFT,300,0);//设置列
    m_list.InsertColumn(1,"Col 2",LVCFMT_LEFT,300,1);
    m_list.InsertColumn(2,"Col 3",LVCFMT_LEFT,300,2);
    m_list.InsertItem(0,"Item 1_1");//插入行
    m_list.SetItemText(0,1,"Item 1_2");//设置该行的不同列的显示字符
    m_list.SetItemText(0,2,"Item 1_3");
    此外CListCtrl还提供了一些函数用于得到/修改控件的状态。
    COLORREF GetTextColor( )/BOOL SetTextColor( COLORREF cr );用于得到/设置显示的字符颜色。
    COLORREF GetTextBkColor( )/BOOL SetTextBkColor( COLORREF cr );用于得到/设置显示的背景颜色。
    void SetItemCount( int iCount );用于得到添加进列表中项的数量。
    BOOL DeleteItem(int nItem);用于删除某一项,BOOL DeleteAllItems( );将删除所有项。
    BOOL SetBkImage(HBITMAP hbm, BOOL fTile , int xOffsetPercent, int yOffsetPercent);用于设置背景位图。
    CString GetItemText( int nItem, int nSubItem );用于得到某项的显示字符。
    列表控件的消息映射同样使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode为通知代码,id为产生该消息的窗口ID,memberFxn为处理函数,函数的原型如同void OnXXXList(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR为一数据结构,在具体使用时需要转换成其他类型的结构。对于列表控件可能取值和对应的数据结构为:
    LVN_BEGINLABELEDIT 在开始某项编辑字符时发送,所用结构:NMLVDISPINFO
    LVN_ENDLABELEDIT 在结束某项编辑字符时发送,所用结构:NMLVDISPINFO
    LVN_GETDISPINFO 在需要得到某项信息时发送,(如得到某项的显示字符)所用结构:NMLVDISPINFO
    关于ON_NOTIFY有很多内容,将在以后的内容中进行详细讲解。

    关于动态提供结点所显示的字符:首先你在项时需要指明lpszItem参数为:LPSTR_TEXTCALLBACK。在控件显示该结点时会通过发送TVN_GETDISPINFO来取得所需要的字符,在处理该消息时先将参数pNMHDR转换为LPNMLVDISPINFO,然后填充其中item.pszText。通过item中的iItem,iSubItem可以知道当前显示的为那一项。下面的代码演示了这种方法:
    char szOut[8][3]={"No.1","No.2","No.3"};
    //添加结点
    m_list.InsertItem(LPSTR_TEXTCALLBACK,...)
    m_list.InsertItem(LPSTR_TEXTCALLBACK,...)
    //处理消息
    void CParentWnd::OnGetDispInfoList(NMHDR* pNMHDR, LRESULT* pResult)
    {
     LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR;
     pLVDI->item.pszText=szOut[pTVDI->item.iItem];
    //通过iItem得到需要显示的字符在数组中的位置
     *pResult = 0;
    }
    关于编辑某项的显示字符:(在报表风格中只对第一列有效)首先需要设置列表控件的LVS_EDITLABELS风格,在开始编辑时该控件将会发送LVN_BEGINLABELEDIT,你可以通过在处理函数中返回TRUE来取消接下来的编辑,在编辑完成后会发送LVN_ENDLABELEDIT,在处理该消息时需要将参数pNMHDR转换为LPNMLVDISPINFO,然后通过其中的item.pszText得到编辑后的字符,并重置显示字符。如果编辑在中途中取消该变量为NULL。下面的代码说明如何处理这些消息:
    //处理消息 LVN_BEGINLABELEDIT
    void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult)
    {
     LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR;
     if(pLVDI->item.iItem==0);//判断是否取消该操作
      *pResult = 1;
     else
      *pResult = 0;
    }
    //处理消息 LVN_BEGINLABELEDIT
    void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult)
    {
     LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR;
     if(pLVDI->item.pszText==NULL);//判断是否已经取消取消编辑
      m_list.SetItemText(pLVDI->item.iItem,0,pLVDI->pszText);
    //重置显示字符
     *pResult = 0;
    }
    上面讲述的方法所进行的消息映射必须在父窗口中进行(同样WM_NOTIFY的所有消息都需要在父窗口中处理)。

    如何得到当前选中项位置:在列表控件中没有一个类似于ListBox中GetCurSel()的函数,但是可以通过调用GetNextItem( -1, LVNI_ALL | LVNI_SELECTED);得到选中项位置

    4.9 Tab Ctrl
    Tab属性页控件可以在一个窗口中添加不同的页面,然后在页选择发生改变时得到通知。MFC中使用CTabCtrl类来封装属性页控件的各种操作。通过调用
    BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );创建一个窗口,dwStyle中可以使用以下一些属性页控件的专用风格:
    TCS_BUTTONS 使用按钮来表示页选择位置
    TCS_MULTILINE 分行显示页选择位置
    TCS_SINGLELINE 只使用一行显示页选择位置
    在控件创建后必需向其中添加页面才可以使用,添加页面的函数为:
    BOOL InsertItem( int nItem, LPCTSTR lpszItem );nItem为位置,从零开始,lpszItem为页选择位置上显示的文字。如果你希望在页选择位置处显示一个图标,你可以调用
    BOOL InsertItem( int nItem, LPCTSTR lpszItem, int nImage );nImage指明所使用的图片位置。(在此之前必须调用CImageList * SetImageList( CImageList * pImageList );设置正确的ImageList)
    此外CTabCtrl还提供了一些函数用于得到/修改控件的状态。
    int GetCurSel( )/int SetCurSel( int nItem );用于得到/设置当前被选中的页位置。
    BOOL DeleteItem( int nItem )/BOOL DeleteAllItems( );用于删除指定/所有页面。
    void RemoveImage( int nImage );用于删除某页选择位置上的图标。
    属性页控件的消息映射同样使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode为通知代码,id为产生该消息的窗口ID,memberFxn为处理函数,函数的原型如同void OnXXXTab(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR为一数据结构,在具体使用时需要转换成其他类型的结构。对于列表控件可能取值和对应的数据结构为:
    TCN_SELCHANGE 在当前页改变后发送,所用结构:NMHDR
    TCN_SELCHANGING 在当前页改变时发送可以通过返回TRUE来禁止页面的改变,所用结构:NMHDR
    一般来讲在当前页发生改变时需要隐藏当前的一些子窗口,并显示其它的子窗口。下面的伪代码演示了如何使用属性页控件:
    CParentWnd::OnCreate(...)
    {
    m_tab.Create(...);
    m_tab.InsertItem(0,"Option 1");
    m_tab.InsertItem(1,"Option 2");
    Create a edit box as the m_tab's Child
    Create a static box as the m_tab's Child
    edit_box.ShowWindow(SW_SHOW); // edit box在属性页的第一页
    static_box.ShowWindow(SW_HIDE); // static box在属性页的第二页
    }
    void CParentWnd::OnSelectChangeTab(NMHDR* pNMHDR, LRESULT* pResult)
    {//处理页选择改变后的消息
    if(m_tab.GetCurSel()==0)
    {//根据当前页显示/隐藏不同的子窗口
    edit_box.ShowWindow(SW_SHOW);
    static_box.ShowWindow(SW_HIDE);
    }
    else
    {//
    edit_box.ShowWindow(SW_HIDE);
    static_box.ShowWindow(SW_SHOW);
    }
    }

    4.A Tool Bar
    工具条也是常用的控件。MFC中使用CToolBar类来封装工具条控件的各种操作。通过调用
    BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP, UINT nID = AFX_IDW_TOOLBAR );创建一个窗口,dwStyle中可以使用以下一些工具条控件的专用风格:
    CBRS_TOP 工具条在父窗口的顶部
    TCBRS_BOTTOM 工具条在父窗口的底部
    CBRS_FLOATING 工具条是浮动的
    创建一个工具条的步骤如下:先使用Create创建窗口,然后使用BOOL LoadToolBar( LPCTSTR lpszResourceName );直接从资源中装入工具条,或者通过装入位图并指明每个按钮的ID,具体代码如下:
    UINT uID[5]={IDM_1,IDM_2,IDM_3,IDM_4,IDM_5};
    m_toolbar.Create(pParentWnd);
    m_toolbar.LoadBitmap(IDB_TOOLBAR);
    m_toolbar.SetSizes(CSize(20,20),CSize(16,16));//设置按钮大尺寸和按钮上位图的尺寸
    m_toolbar.SetButtons(uID,5);
    AppWizard在生成代码时也会同时生成工具条的代码,同时还可以支持停靠功能。所以一般是不需要直接操作工具条对象。

    工具条上的按钮被按下时发送给父窗口的消息和菜单消息相同,所以可以使用ON_COMMAND宏进行映射,同样工具条中的按钮也支持ON_UPDATE_COMMAND_UI的相关操作,如SetCheck,Enable,你可以将按钮的当作菜单上的一个具有相同ID菜单项。
    在以后的章节4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar会给出使用的方法。

    4.B Status Bar
    状态条用于显示一些提示字符。MFC中使用CStatusBar类来封装状态条控件的各种操作。通过调用
    BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR );创建一个窗口,dwStyle中可以使用以下一些状态条控件的专用风格:
    CBRS_TOP 状态条在父窗口的顶部
    TCBRS_BOTTOM 状态条在父窗口的底部
    创建一个状态条的步骤如下:先使用Create创建窗口,然后调用BOOL SetIndicators( const UINT* lpIDArray, int nIDCount );设置状态条上各部分的ID,具体代码如下:
    UINT uID[2]={ID_SEPARATOR,ID_INDICATOR_CAPS};
    m_stabar.Create(pParentWnd);
    m_stabar.SetIndicators(uID,2);
    通过CString GetPaneText( int nIndex )/BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE )可以得到/设置状态条上显示的文字。
    Tip:在创建状态条时最好将状态条中所有的部分ID(除MFC自定义的几个用于状态条的ID外)都设置为ID_SEPARATOR,在生成后调用
    void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth );改变其风格,ID和宽度。
    AppWizard在生成代码时也会同时生成状态条的代码。所以一般是不需要直接创建状态条对象。此外状态条上会自动显示菜单上的命令提示(必须先在资源中定义),所以也不需要人为设置显示文字。
    状态条支持ON_UPDATE_COMMAND_UI的相关操作,如SetText,Enable。
    在以后的章节4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar会给出使用的方法
     
    4.C Dialog Bar
    Dialog Bar类似一个静态的附在框架窗口上的对话框,由于Dialog Bar可以使用资源编辑器进行编辑所以使用起来就很方便,在设计时就可以对Dialog Bar上的子窗口进行定位。用于显示一些提示字符。MFC中使用CDialogBar类来Dialog Bar控件的各种操作。通过调用
    BOOL Create( CWnd* pParentWnd, UINT nIDTemplate, UINT nStyle, UINT nID );创建一个窗口,nIDTemplate为对话框资源,nID为该Dialog Bar对应的窗口ID,nStyle中可以使用以下一些状态条控件的专用风格:
    CBRS_TOP Dialog Bar在父窗口的顶部
    TCBRS_BOTTOM Dialog Bar在父窗口的底部
    CBRS_LEFT Dialog Bar在父窗口的左部
    CBRS_RIGHT Dialog Bar在父窗口的右部
    对于Dialog Bar的所产生消息需要在父窗口中进行映射和处理,例如Dialog Bar上的按钮,需要在父窗口中进行ON_BN_CLICKED或ON_COMMAND映射,Dialog Bar上的输入框可以在父窗口中进行ON_EN_CHANGE,ON_EN_MAXTEXT等输入框对应的映射。
    Dialog Bar支持ON_UPDATE_COMMAND_UI的相关操作,如SetText,Enable。
    在以后的章节4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar会给出使用的方法

    4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar

    运行时程序界面如界面图,该程序拥有一个工具条用于显示两个命令按钮,一个用于演示如何使按钮处于检查状态,另一个根据第一个按钮的状态来禁止/允许自身。(设置检查状态和允许状态都通过OnUpdateCommand实现)此外Dialog Bar上有一个输入框和按钮,这两个子窗口的禁止/允许同样是根据工具条上的按钮状态来确定,当按下Dialog Bar上的按钮时将显示输入框中的文字内容。状态条的第一部分用于显示各种提示,第二部分用于利用OnUpdateCommand显示当前时间。同时在程序中演示了如何设置菜单项的命令解释字符(将在状态条的第一部分显示)和如何设置工具条的提示字符(利用一个小的ToolTip窗口显示)。
    生成应用:利用AppWizard生成一个MFC工程,图例,并设置为单文档界面图例,最后选择工具条,状态条和ReBar支持,图例
    修改菜单:利用资源编辑器删除多余的菜单并添加一个新的弹出菜单和三个子菜单,图例,分别是: 名称
     ID
     说明字符
     
    Check
     IDM_CHECK
     SetCheck Demo\nSetCheck Demo
     
    Disable
     IDM_DISABLE
     Disable Demo\nDisable Demo
     
    ShowText on DialogBar
     IDM_SHOW_TXT
     ShowText on DialogBar Demo\nShowText on DialogBar
     
     
    \n前的字符串将显示在状态条中作为命令解释,\n后的部分将作为具有相同ID的工具条按钮的提示显示在ToolTip窗口中。
    修改Dialog Bar:在Dialog Bar中添加一个输入框和按钮,按钮的ID为IDM_SHOW_TXT与一个菜单项具有相同的ID,这样可以利用映射菜单消息来处理按钮消息(当然使用不同ID值也可以利用ON_COMMAND来映射Dialog Bar上的按钮消息,但是ClassWizard没有提供为Dialog Bar上按钮进行映射的途径,只能手工添加消息映射代码)。图例
    修改工具条:在工具条中添加两个按钮,ID值为IDM_CHECK和IDM_DISABLE和其中两个菜单项具有相同的ID值。图例
    利用ClassWizard为三个菜单项添加消息映射和更新命令。图例
    修改MainFrm.h文件
    //添加一个成员变量来记录工具条上Check按钮的检查状态。
    protected:
     BOOL m_fCheck;
    //手工添加状态条第二部分用于显示时间的更新命令,和用于禁止/允许输入框的更新命令
     //{{AFX_MSG(CMainFrame)
     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
     afx_msg void OnCheck();
     afx_msg void OnUpdateCheck(CCmdUI* pCmdUI);
     afx_msg void OnDisable();
     afx_msg void OnUpdateDisable(CCmdUI* pCmdUI);
     afx_msg void OnShowTxt();
     afx_msg void OnUpdateShowTxt(CCmdUI* pCmdUI);
     //}}AFX_MSG
     //上面的部分为ClassWizard自动产生的代码
     afx_msg void OnUpdateTime(CCmdUI* pCmdUI); //显示时间
     afx_msg void OnUpdateInput(CCmdUI* pCmdUI); //禁止/允许输入框
    修改MainFrm.cpp文件
    //修改状态条上各部分ID
    #define ID_TIME   0x705 //作为状态条上第二部分ID
    static UINT indicators[] =
    {
     ID_SEPARATOR,           // status line indicator
     ID_SEPARATOR,   
    //先设置为ID_SEPARATOR,在状态条创建后再进行修改
    };
    //修改消息映射
     //{{AFX_MSG_MAP(CMainFrame)
     ON_WM_CREATE()
     ON_COMMAND(IDM_CHECK, OnCheck)
     ON_UPDATE_COMMAND_UI(IDM_CHECK, OnUpdateCheck)
     ON_COMMAND(IDM_DISABLE, OnDisable)
     ON_UPDATE_COMMAND_UI(IDM_DISABLE, OnUpdateDisable)
     ON_COMMAND(IDM_SHOW_TXT, OnShowTxt)
     ON_UPDATE_COMMAND_UI(IDM_SHOW_TXT, OnUpdateShowTxt)
     //}}AFX_MSG_MAP
     //以上部分为ClassWizard自动生成代码
     ON_UPDATE_COMMAND_UI(ID_TIME, OnUpdateTime) ////显示时间
     ON_UPDATE_COMMAND_UI(IDC_INPUT_TEST, OnUpdateInput) //禁止/允许输入框
    //修改OnCreate函数,重新设置状态条第二部分ID值
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    ....
     // by wenyy 修改状态条上第二部分信息
     m_wndStatusBar.SetPaneInfo(1,ID_TIME,SBPS_NORMAL,60);//set the width
     return 0;
    }
    //修改经过映射的消息处理函数代码
    void CMainFrame::OnCheck()
    {
     //在Check按钮被按下时改变并保存状态
     m_fCheck=!m_fCheck;
    }
    void CMainFrame::OnUpdateCheck(CCmdUI* pCmdUI)
    {
     //Check按钮是否设置为检查状态
     pCmdUI->SetCheck(m_fCheck);
    }
    void CMainFrame::OnDisable()
    {
     //Disable按钮被按下
     AfxMessageBox("you press disable test");
    }
    void CMainFrame::OnUpdateDisable(CCmdUI* pCmdUI)
    {
     //根据Check状态决定自身禁止/允许状态
     pCmdUI->Enable(m_fCheck);
    }
    void CMainFrame::OnShowTxt()
    {
     //得到Dialog Bar上输入框中文字并显示
     CEdit* pE=(CEdit*)m_wndDlgBar.GetDlgItem(IDC_INPUT_TEST);
     CString szO;
     pE->GetWindowText(szO);
     AfxMessageBox(szO);
    }
    void CMainFrame::OnUpdateShowTxt(CCmdUI* pCmdUI)
    {
     //Dialog Bar上按钮根据Check状态决定自身禁止/允许状态
     pCmdUI->Enable(m_fCheck);
    }
    void CMainFrame::OnUpdateInput(CCmdUI* pCmdUI)
    {
     //Dialog Bar上输入框根据Check状态决定自身禁止/允许状态
     pCmdUI->Enable(m_fCheck);
    }
    void CMainFrame::OnUpdateTime(CCmdUI* pCmdUI)
    {
     //根据当前时间设置状态条上第二部分文字
     CTime timeCur=CTime::GetCurrentTime();
     char szOut[20];
     sprintf( szOut, "%02d:%02d:%02d", timeCur.GetHour(),
     timeCur.GetMinute(),timeCur.GetSecond());
     pCmdUI->SetText(szOut);
    }

    4.E General Window
    从VC提供的MFC类派生图中我们可以看出窗口的派生关系,派生图,所有的窗口类都是由CWnd派生。所有CWnd的成员函数在其派生类中都可以使用。本节介绍一些常用的功能给大家。
    改变窗口状态:
    BOOL EnableWindow( BOOL bEnable = TRUE );可以设置窗口的禁止/允许状态。BOOL IsWindowEnabled( );可以查询窗口的禁止/允许状态。
    BOOL ModifyStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 )/BOOL ModifyStyleEx( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );可以修改窗口的风格,而不需要调用SetWindowLong
    BOOL IsWindowVisible( ) 可以检查窗口是否被显示。
    BOOL ShowWindow( int nCmdShow );将改变窗口的显示状态,nCmdShow可取如下值:
    SW_HIDE 隐藏窗口
    SW_MINIMIZE SW_SHOWMAXIMIZED 最小化窗口
    SW_RESTORE 恢复窗口
    SW_SHOW 显示窗口
    SW_SHOWMINIMIZED 最大化窗口
    改变窗口位置:
    void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );可以移动窗口。
    void GetWindowRect( LPRECT lpRect ) ;可以得到窗口的矩形位置。
    BOOL IsIconic( ) ;可以检测窗口是否已经缩为图标。
    BOOL SetWindowPos( const CWnd* pWndInsertAfter, int x, int y, int cx, int cy, UINT nFlags );可以改变窗口的Z次序,此外还可以移动窗口位置。
    使窗口失效,印发重绘:
    void Invalidate( BOOL bErase = TRUE );使整个窗口失效,bErase将决定窗口是否产生重绘。
    void InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE )/void InvalidateRgn( CRgn* pRgn, BOOL bErase = TRUE );将使指定的矩形/多边形区域失效。
    窗口查找:
    static CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName );可以以窗口的类名和窗口名查找窗口。任一参数设置为NULL表对该参数代表的数据进行任意匹配。如FindWindow("MyWnd",NULL)表明查找类名为MyWnd的所有窗口。
    BOOL IsChild( const CWnd* pWnd ) 检测窗口是否为子窗口。
    CWnd* GetParent( ) 得到父窗口指针。
    CWnd* GetDlgItem( int nID ) 通过子窗口ID得到窗口指针。
    int GetDlgCtrlID( ) 得到窗口ID值。
    static CWnd* PASCAL WindowFromPoint( POINT point );将从屏幕上某点坐标得到包含该点的窗口指针。
    static CWnd* PASCAL FromHandle( HWND hWnd );通过HWND构造一个CWnd*指针,但该指针在空闲时会被删除,所以不能保存供以后使用。
    时钟:
    UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );可以创建一个时钟,如果lpfnTimer回调函数为NULL,窗口将会收到WM_TIMER消息,并可以在afx_msg void OnTimer( UINT nIDEvent );中安排处理代码
    BOOL KillTimer( int nIDEvent );删除一个指定时钟。
    可以利用重载来添加消息处理的虚函数:
    afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct );窗口被创建时被调用
    afx_msg void OnDestroy( );窗口被销毁时被调用
    afx_msg void OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI );需要得到窗口尺寸时被调用
    afx_msg void OnSize( UINT nType, int cx, int cy );窗口改变大小后被调用
    afx_msg void OnMove( int x, int y );窗口被移动后时被调用
    afx_msg void OnPaint( );窗口需要重绘时时被调用,你可以填如绘图代码,对于视图类不需要重载OnPaint,所有绘图代码应该在OnDraw中进行
    afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags );接收到字符输入时被调用
    afx_msg void OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );键盘上键被按下/放开时被调用
    afx_msg void OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point );鼠标左/右键按下时被调用
    afx_msg void OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point );鼠标左/右键放开时被调用
    afx_msg void OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point );鼠标左/右键双击时被调用
    afx_msg void OnMouseMove( UINT nFlags, CPoint point );鼠标在窗口上移动时被调用

    4.F 关于WM_NOTIFY的使用方法
    WM_NOTIF在WIN32中得到大量的应用,同时也是随着CommControl的出现WM_NOTIFY成为了CommControl的基本消息。可以这样说CommControl的所有的新增特性都通过WM_NOTIFY来表达。同时WM_NOTIFY也为CommControl的操作带来了一致性。
    WM_NOTIFY消息中的参数如下:
    idCtrl = (int) wParam;
    pnmh = (LPNMHDR) lParam; 其中lParam为一个
    typedef struct tagNMHDR
    {
    HWND hwndFrom;
    UINT idFrom;
    UINT code;
    } NMHDR; 结构指针
    从消息的参数我们已经可以分辩出消息的来源,但是这些信息还不足以分辩出消息的具体含义。所以我们需要更多的数据来得到更多的信息。MS的做法是对每种不同用途的通知消息都定义另一种结构来表示,同时这中结构里包含了struct tagNMHDR,所以你只要进行一下类型转换就可以得到数据指针。例如对于LVN_COLUMNCLICK消息(用于在ListCtrl的列表头有鼠标点击是进行通知),结构为;
    typedef struct tagNMLISTVIEW{
    NMHDR hdr;
    int iItem;
    int iSubItem;
    UINT uNewState;
    UINT uOldState;
    UINT uChanged;
    POINT ptAction;
    LPARAM lParam;
    } NMLISTVIEW, FAR *LPNMLISTVIEW;
    在这个结构的最开始也就包含了struct tagNMHDR,所以在不损失数据和产生错误的情况下向处理消息的进程提供了更多的信息。
    此外通过WM_NOTIFY我们可以一种完全一样的方式进行消息映射,如同在前几章中所见到的一样。
    使用如下形式:ON_NOTIFY( wNotifyCode, id, memberFxn )。
    处理函数也有统一的原型:afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result );
    在MFC消息映射的内部将根据定义消息映射时所使用的wNotifyCode和WM_NOTIFY中参数中pnmh->code(pnmh = (LPNMHDR) lParam)进行匹配,然后调用相应的处理函数。
    还有一点是利用WM_NOTIFY/ON_NOTIFY_REFLECT可以在窗口内部处理一些消息,从而建立可重用的控件。
     
    ////////////////////////////////////////////////对话框/////////////////////////////////////////////////////
    5.1 使用资源编辑器编辑对话框
    在Windows开发中弹出对话框是一种常用的输入/输出手段,同时编辑好的对话框可以保存在资源文件中。Visual C++提供了对话框编辑工具,利用编辑工具可以方便的添加各种控件到对话框中,而且利用ClassWizard可以方便的生成新的对话框类和映射消息。
    首先资源列表中按下右键,可以在弹出菜单中选择“插入对话框”,如图1。然后再打开该对话框进行编辑,你会在屏幕上看到一个控件板,如图2。你可以将所需要添加的控件拖到对话框上,或是先选中后再在对话框上用鼠标画出所占的区域。
    接下来我们在对话框上产生一个输入框,和一个用于显示图标的图片框。之后我们使用鼠标右键单击产生的控件并选择其属性,如图3。我们可以在属性对话框中编辑控件的属性同时也需要指定控件ID,如图4,如果在选择对话框本身的属性那么你可以选择对话框的一些属性,包括字体,外观,是否有系统菜单等等。最后我们编辑图片控件的属性,如图5,我们设置控件的属性为显示图标并指明一个图标ID。
    接下来我们添加一些其他的控件,最后的效果如图6。按下Ctrl-T可以测试该对话框。此外在对话框中还有一个有用的特性,就是可以利用Tab键让输入焦点在各个控件间移动,要达到这一点首先需要为控件设置在Tab键按下时可以接受焦点移动的属性Tab Stop,如果某一个控件不打算利用这一特性,你需要清除这一属性。然后从菜单“Layout”选择Tab Order来确定焦点移动顺序,如图7。使用鼠标依此点击控件就可以重新规定焦点移动次序。最后按下Ctrl-T进行测试。
    最后我们需要为对话框产生新的类,ClassWizard可以替我们完成大部分的工作,我们只需要填写几个参数就可以了。在编辑好的对话框上双击,然后系统回询问是否添加新的对话框,选择是并在接下来的对话框中输入类名就可以了。ClassWizard会为你产生所需要的头文件和CPP文件。然后在需要使用的地方包含相应的头文件,对于有模式对话框使用DoModal()产生,对于无模式对话框使用Create()产生。相关代码如下
    void CMy51_s1View::OnCreateDlg()
    {//产生无模式对话框
     CTestDlg *dlg=new CTestDlg;
     dlg->Create(IDD_TEST_DLG);
     dlg->ShowWindow(SW_SHOW);
    }
    void CMy51_s1View::OnDoModal()
    {//产生有模式对话框
     CTestDlg dlg;
     int iRet=dlg.DoModal();
     TRACE("dlg return %d\n",iRet);
    }
    下载例子。如果你在调试这个程序时你会发现程序在退出后会有内存泄漏,这是因为我没有释放无模式对话框所使用的内存,这一问题会在以后的章节5.3 创建无模式对话框中专门讲述。

    关于在使用对话框时Enter键和Escape键的处理:在使用对话框是你会发现当你按下Enter键或Escape键都会退出对话框,这是因为Enter键会引起CDialog::OnOK()的调用,而Escape键会引起CDialog::OnCancel()的调用。而这两个调用都会引起对话框的退出。在MFC中这两个成员函数都是虚拟函数,所以我们需要进行重载,如果我们不希望退出对话框那么我们可以在函数中什么都不做,如果需要进行检查则可以添加检查代码,然后调用父类的OnOK()或OnCancel()。相关代码如下;
    void CTestDlg::OnOK()
    {
     AfxMessageBox("你选择确定");
     CDialog::OnOK();
    }
    void CTestDlg::OnCancel()
    {
     AfxMessageBox("你选择取消");
     CDialog::OnCancel();
    }
     
    5.2 创建有模式对话框
    使用有模式对话框时在对话框弹出后调用函数不会立即返回,而是等到对话框销毁后才会返回(请注意在对话框弹出后其他窗口的消息依然会被传递)。所以在使用对话框时其他窗口都不能接收用户输入。创建有模式对话框的方法是调用CDialog::DoModal()。下面的代码演示了这种用法:
    CYourView::OnOpenDlg()
    {
     CYourDlg dlg;
     int iRet=dlg.DoModal();
    }
    CDialog::DoModal()的返回值为IDOK,IDCANCEL。表明操作者在对话框上选择“确认”或是“取消”。由于在对话框销毁前DoModal不会返回,所以可以使用局部变量来引用对象。在退出函数体后对象同时也会被销毁。而对于无模式对话框则不能这样使用,下节5.3 创建无模式对话框中会详细讲解。

    你需要根据DoModal()的返回值来决定你下一步的动作,而得到返回值也是使用有模式对话框的一个很大原因。
    使用有模式对话框需要注意一些问题,比如说不要在一些反复出现的事件处理过程中生成有模式对话框,比如说在定时器中产生有模式对话框,因为在上一个对话框还未退出时,定时器消息又会引起下一个对话框的弹出。
    同样的在你的对话框类中为了向调用者返回不同的值可以调用CDialog::OnOK()或是CDialog::OnCancel()以返回IDOK或IDCANCEL,如果你希望返回其他的值,你需要调用
    CDialog::EndDialog( int nResult );其中nResult会作为DoModal()调用的返回值。
    下面的代码演示了如何使用自己的函数来退出对话框:
    void CMy52_s1View::OnLButtonDown(UINT nFlags, CPoint point)
    {//创建对话框并得到返回值
     CView::OnLButtonDown(nFlags, point);
     CTestDlg dlg;
     int iRet=dlg.DoModal();
     CString szOut;
     szOut.Format("return value %d",iRet);
     AfxMessageBox(szOut);
    }
    //重载OnOK,OnCancel
    void CTestDlg::OnOK()
    {//什么也不做
    }
    void CTestDlg::OnCancel()
    {//什么也不做
    }
    //在对话框中对三个按钮消息进行映射
    void CTestDlg::OnExit1()
    {
     CDialog::OnOK();
    }
    void CTestDlg::OnExit2()
    {
     CDialog::OnCancel();
    }
    void CTestDlg::OnExit3()
    {
     CDialog::EndDialog(0XFF);
    }
    由于重载了OnOK和OnCancel所以在对话框中按下Enter键或Escape键时都不会退出,只有按下三个按钮中的其中一个才会返回。

    此外在对话框被生成是会自动调用BOOL CDialog::OnInitDialog(),你如果需要在对话框显示前对其中的控件进行初始化,你需要重载这个函数,并在其中填入相关的初始化代码。利用ClassWizard可以方便的产生一些默认代码,首先打开ClassWizard,选择相应的对话框类,在右边的消息列表中选择WM_INITDIALOG并双击,如图,ClassWizard会自动产生相关代码,代码如下:
    BOOL CTestDlg::OnInitDialog()
    {
     /*先调用父类的同名函数*/
     CDialog::OnInitDialog();
     /*填写你的初始化代码*/
     return TRUE; 
    }
    有关对对话框中控件进行初始化会在5.4 在对话框中进行消息映射中进行更详细的讲解。
     
    5.3 创建无模式对话框
    无模式对话框与有模式对话框不同的是在创建后其他窗口都可以继续接收用户输入,因此无模式对话框有些类似一个弹出窗口。创建无模式对话框需要调用
    BOOL CDialog::Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );之后还需要调用
    BOOL CDialog::ShowWindow( SW_SHOW);进行显示,否则无模式对话框将是不可见的。相关代码如下:
    void CYourView::OnOpenDlg(void)
    {
     /*假设IDD_TEST_DLG为已经定义的对话框资源的ID号*/
     CTestDlg *dlg=new CTestDlg;
     dlg->Create(IDD_TEST_DLG,NULL);
     dlg->ShowWindows(SW_SHOW);
     /*不要调用 delete dlg;*/
    }
    在上面的代码中我们新生成了一个对话框对象,而且在退出函数时并没有销毁该对象。因为如果此时销毁该对象(对象被销毁时窗口同时被销毁),而此时对话框还在显示就会出现错误。那么这就提出了一个问题:什么时候销毁该对象。我时常使用的方法有两个:
    在对话框退出时销毁自己:在对话框中重载OnOK与OnCancel在函数中调用父类的同名函数,然后调用DestroyWindow()强制销毁窗口,在对话框中映射WM_DESTROY消息,在消息处理函数中调用delete this;强行删除自身对象。相关代码如下:
    void CTestDlg1::OnOK()
    {
     CDialog::OnOK();
     DestroyWindow();
    }
    void CTestDlg1::OnCancel()
    {
     CDialog::OnCancel();
     DestroyWindow();
    }
    void CTestDlg1::OnDestroy()
    {
     CDialog::OnDestroy();
     AfxMessageBox("call delete this");
     delete this;
    }
    这种方法的要点是在窗口被销毁的时候,删除自身对象。所以你可以在任何时候调用DestroyWindow()以达到彻底销毁自身对象的作用。(DestroyWindow()的调用会引起OnDestroy()的调用)
    通过向父亲窗口发送消息,要求其他窗口对其进行销毁:首先需要定义一个消息用于进行通知,然后在对话框中映射WM_DESTROY消息,在消息处理函数中调用消息发送函数通知其他窗口。在接收消息的窗口中利用ON_MESSAGE映射处理消息的函数,并在消息处理函数中删除对话框对象。相关代码如下:
    /*更改对话框的有关文件*/
    CTestDlg2::CTestDlg2(CWnd* pParent /*=NULL*/)
     : CDialog(CTestDlg2::IDD, pParent)
    {/*m_pParent为一成员变量,用于保存通知窗口的指针,
    所以该指针不能是一个临时指针*/
     ASSERT(pParent);
     m_pParent=pParent;
     //{{AFX_DATA_INIT(CTestDlg2)
      // NOTE: the ClassWizard will add member
    initialization here
     //}}AFX_DATA_INIT
    }
    void CTestDlg2::OnOK()
    {
     CDialog::OnOK();
     DestroyWindow();
    }
    void CTestDlg2::OnCancel()
    {
     CDialog::OnCancel();
     DestroyWindow();
    }
    void CTestDlg2::OnDestroy()
    {
     CDialog::OnDestroy();
     /*向其他窗口发送消息,将自身指针作为一个参数发送*/
     m_pParent->PostMessage(WM_DELETE_DLG,
    (WPARAM)this);
    }
    /*在消息接收窗口中添加消息映射*/
    /*在头文件中添加函数定义*/
     afx_msg LONG OnDelDlgMsg(WPARAM wP,
    LPARAM lP);
    /*添加消息映射代码*/
     ON_MESSAGE(WM_DELETE_DLG,OnDelDlgMsg)
    END_MESSAGE_MAP()
    /*实现消息处理函数*/
    LONG CMy53_s1View::OnDelDlgMsg(WPARAM wP,LPARAM lP)
    {
     delete (CTestDlg2*)wP;
     return 0;
    }
    /*创建对话框*/
    void CMy53_s1View::OnTest2()
    {
     CTestDlg2 *dlg=new CTestDlg2(this);
     dlg->Create(IDD_TEST_DLG_2);
     dlg->ShowWindow(SW_SHOW);
    }
    在这种方法中我们利用消息来进行通知,在Window系统中利用消息进行通知和传递数据的用法是很多的。
    同样无模式对话框的另一个作用还可以用来在用户在对话框中的输入改变时可以及时的反映到其他窗口。下面的代码演示了在对话框中输入一段文字,然后将其更新到视图的显示区域中,这同样也是利用了消息进行通知和数据传递。
    /*在对话框中取出数据,并向其他窗口发送消息和数据,
    将数据指针作为一个参数发送*/
    void CTestDlg2::OnCommBtn()
    {
     char szOut[30];
     GetDlgItemText(IDC_OUT,szOut,30);
     m_pParent->SendMessage(WM_DLG_NOTIFY,
    (WPARAM)szOut);
    }
    /*在消息接收窗口中*/
    /*映射消息处理函数*/
     ON_MESSAGE(WM_DLG_NOTIFY,OnDlgNotifyMsg)
    /*在视图中绘制出字符串 m_szOut*/
    void CMy53_s1View::OnDraw(CDC* pDC)
    {
     CMy53_s1Doc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     // TODO: add draw code for native data here
     pDC->TextOut(0,0,"Display String");
     pDC->TextOut(0,20,m_szOut);
    }
    /*处理通知消息,保存信息并更新显示*/
    LONG CMy53_s1View::OnDlgNotifyMsg(WPARAM wP,LPARAM lP)
    {
     m_szOut=(char*)wP;
     Invalidate();
     return 0;
    }
    此外这种用法利用消息传递数据的方法对有模式对话框和其他的窗口间通信也一样有效。
     
    5.4 在对话框中进行消息映射
    利用对话框的一个好处是可以利用ClassWizard对对话框中各个控件产生的消息进行映射,ClassWizrd可以列出各种控件可以使用的消息,并能自动产生代码。在本节中我们以一个例子来讲解如何在对话框中对子窗口消息进行映射同时还讲解如何对对话框中的子窗口进行初始化。
    首先我们产生编辑好一个对话框,如图,在对话框中使用的控件和ID号如下表: ID 类型
    IDC_RADIO_TEST_1 圆形按钮
    IDC_RADIO_TEST_2 圆形按钮
    IDC_BUTTON_TEST 按钮
    IDC_CHECK_TEST 检查按钮
    IDC_TREE_TEST 树形控件
    IDC_LIST_CTRL List Ctrl
    IDC_TAB_CTRL Tab Ctrl
    IDC_LIST_TEST 列表框
    IDC_COMBO_TEST 组合框
    IDC_EDIT_TEST 输入框
    首先我们需要在对话框的OnInitDialog()函数中对各个控件进行初始化,这里我们使用CWnd* GetDlgItem( int nID )来通过ID号得到子窗口指针。(类似的函数还有UINT GetDlgItemInt( int nID, BOOL* lpTrans = NULL, BOOL bSigned = TRUE ) 通过ID号得到子窗口中输入的数字,int GetDlgItemText( int nID, CString& rString ) 通过ID号得到子窗口中输入的文字)。代码如下:
    BOOL CMy54_s1Dlg::OnInitDialog()
    {
     CDialog::OnInitDialog();
     /*添加初始化代码*/
     //初始化输入框
     ((CEdit*)GetDlgItem(IDC_EDIT_TEST))->SetWindowText("this is a edit box");
     //初始化列表框
     CListBox* pListB=(CListBox*)GetDlgItem(IDC_LIST_TEST);
     pListB->AddString("item 1");
     pListB->AddString("item 2");
     pListB->AddString("item 3");
     //初始化组合框
     CComboBox* pCB=(CComboBox*)GetDlgItem(IDC_COMBO_TEST);
     pCB->AddString("item 1");
     pCB->AddString("item 2");
     pCB->AddString("item 3");
     file://初始化Tab Ctrl
     CTabCtrl* pTab=(CTabCtrl*)GetDlgItem(IDC_TAB_TEST);
     pTab->InsertItem(0,"Tab Page1");
     pTab->InsertItem(1,"Tab Page2");
     pTab->InsertItem(2,"Tab Page3");
     file://初始化ListCtrl
     CListCtrl* pList=(CListCtrl*)GetDlgItem(IDC_LIST_CTRL);
     pList->InsertColumn(0,"Column 1",LVCFMT_LEFT,100);
     pList->InsertItem(0,"Item 1");
     pList->InsertItem(1,"Item 2");
     pList->InsertItem(2,"Item 3");
     file://初始化TreeCtrl
     CTreeCtrl* pTree=(CTreeCtrl*)GetDlgItem(IDC_TREE_TEST);
     pTree->InsertItem("Node1",0,0);
     HTREEITEM hNode=pTree->InsertItem("Node2",0,0);
     pTree->InsertItem("Node2-1",0,0,hNode);
     pTree->InsertItem("Node2-2",0,0,hNode);
     pTree->Expand(hNode,TVE_EXPAND);
     return TRUE;  // return TRUE  unless you set the focus to a control
    }
    接下来我们需要利用ClassWizard对控件所产生的消息进行映射,打开ClassWizard对话框,选中相关控件的ID,在右边的列表中就会显示出可用的消息。如我们对按钮的消息进行映射,在选中按钮ID(IDC_BUTTON_TEST)后,会看到两个消息,如图,一个是BN_CLICKED,一个是BN_DOUBLECLICKED。双击BN_CLICKED后在弹出的对话框中输入函数名,ClassWizard会产生按钮被按的消息映射。

    然后我们看看对TabCtrl的TCN_SELCHANGE消息进行映射,如图,在TabCtrl的当前页选择发生改变时这个消息会被发送,所以通过映射该消息可以在当前页改变时及时得到通知。
    最后我们对输入框的EN_CHANGE消息进行映射,如图,在输入框中的文本改变后该消息会被发送。相关的代码如下:
    //头文件中相关的消息处理函数定义
     afx_msg void OnButtonTest();
     afx_msg void OnSelchangeTabTest(NMHDR* pNMHDR, LRESULT* pResult);
     afx_msg void OnChangeEditTest();
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
    //CPP文件中消息映射代码
     ON_BN_CLICKED(IDC_BUTTON_TEST, OnButtonTest)
     ON_NOTIFY(TCN_SELCHANGE, IDC_TAB_TEST, OnSelchangeTabTest)
     ON_EN_CHANGE(IDC_EDIT_TEST, OnChangeEditTest)
     //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    //消息处理函数
    void CMy54_s1Dlg::OnButtonTest()
    {
     AfxMessageBox("you pressed a button");
    }
    void CMy54_s1Dlg::OnSelchangeTabTest(NMHDR* pNMHDR, LRESULT* pResult)
    {
     TRACE("Tab Select changed\n");
     *pResult = 0;
    }
    void CMy54_s1Dlg::OnChangeEditTest()
    {
     TRACE("edit_box text changed\n");
    }
    对于其他的控件都可以采取类似的方法进行消息映射。此外如果你对各种控件可以使用的消息不熟悉,你可以通过使用对话框,然后利用ClassWizard产生相关代码的方法来进行学习,你也可以将ClassWizard产生的代码直接拷贝到其他需要的地方(不瞒你说,我最开始就是这样学的 :-D 这也算一个小窍门)。
     
    5.5 在对话框中进行数据交换和数据检查
    MFC提供两种方法在对话框中进行数据交换和数据检查(Dialog data exchange/Dialog data validation),数据交换和数据检查的思想是将某一变量和对话框中的一个子窗口进行关联,然后通过调用BOOL UpdateData( BOOL bSaveAndValidate = TRUE )来指示MFC将变量中数据放入子窗口还是将子窗口中数据取到变量中并进行合法性检查。
    在进行数据交换时一个子窗口可以和两种类型的变量相关联,一种是控件(Control)对象,比如说按钮子窗口可以和一个CButton对象相关联,这种情况下你可以通过该对象直接控制子窗口,而不需要象上节中讲的一样使用GetDlgItem(IDC_CONTROL_ID)来得到窗口指针;一种是内容对象,比如说输入框可以和一个CString对象关联,也可以和一个UINT类型变量关联,这种情况下你可以直接设置/获取窗口中的输入内容。
    而数据检查是在一个子窗口和一个内容对象相关联时在存取内容时对内容进行合法性检查,比如说当一个输入框和一个CString对象关联时,你可以设置检查CString的对象的最长/最小长度,当输入框和一个UINT变量相关联时你可以设置检查UINT变量的最大/最小值。在BOOL UpdateData( BOOL bSaveAndValidate = TRUE )被调用后,合法性检查会自动进行,如果无法通过检查MFC会弹出消息框进行提示,并返回FALSE。
    设置DDX/DDV在VC中非常简单,ClassWizard可以替你完成所有的工作,你只需要打开ClassWizard并选中Member Variables页,如图,你可以看到所有可以进行关联的子窗口ID列表,双击一个ID会弹出一个添加变量的对话框,如图,填写相关的信息后按下确定按钮就可以了。然后选中你刚才添加的变量在底部的输入框中输入检查条件,如图。
    下面我们看一个例子,对话框上的子窗口如图设置,各子窗口的ID和关联的变量如下表: ID 关联的变量 作用
    IDC_CHECK_TEST BOOL m_fCheck 检查框是否被选中
    IDC_RADOI_TEST_1 int m_iSel 当前选择的圆形按钮的索引
    IDC_COMBO_TEST CString m_szCombo 组合框中选中的文本或是输入的文本
    IDC_EDIT_TEST CString m_szEdit 输入框中输入的文本,最大长度为5
    IDC_LIST_TEST CListBox m_lbTest 列表框对象
    这时候ClassWizard会自动生成变量定义和相关代码,在对话框的构造函数中可以对变量的初始值进行设置,此外在BOOL CDialog::OnInitDialog()中会调用UpdateData(FALSE),即会将变量中的数据放入窗口中 。相关代码如下:
    //头文件中的变量定义,ClassWizard自动产生
    // Dialog Data
     //{{AFX_DATA(CMy55_s1Dlg)
     enum { IDD = IDD_MY55_S1_DIALOG };
     CListBox m_lbTest;
     int  m_iSel;
     CString m_szEdit;
     CString m_szCombo;
     BOOL m_fCheck;
     //}}AFX_DATA
    //构造函数中赋初值
    CMy55_s1Dlg::CMy55_s1Dlg(CWnd* pParent /*=NULL*/)
     : CDialog(CMy55_s1Dlg::IDD, pParent)
    {
     //{{AFX_DATA_INIT(CMy55_s1Dlg)
     m_iSel = -1;
     m_szEdit = _T("");
     m_szCombo = _T("");
     m_fCheck = FALSE;
     //}}AFX_DATA_INIT
    .....
    }
    //ClassWizard产生的关联和检查代码
    void CMy55_s1Dlg::DoDataExchange(CDataExchange* pDX)
    {
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CMy55_s1Dlg)
     DDX_Control(pDX, IDC_LIST_TEST, m_lbTest);
     DDX_Radio(pDX, IDC_RADIO_TEST_1, m_iSel);
     DDX_Text(pDX, IDC_EDIT_TEST, m_szEdit);
     DDV_MaxChars(pDX, m_szEdit, 5);
     DDX_CBString(pDX, IDC_COMBO_TEST, m_szCombo);
     DDX_Check(pDX, IDC_CHECK_TEST, m_fCheck);
     //}}AFX_DATA_MAP
    }
    //在OnInitDialog中利用已经关联过的变量m_lbTest
    BOOL CMy55_s1Dlg::OnInitDialog()
    {
     CDialog::OnInitDialog();
    ...
     // TODO: Add extra initialization here
     file://设置列表框中数据
     m_lbTest.AddString("String 1");
     m_lbTest.AddString("String 2");
     m_lbTest.AddString("String 3");
     m_lbTest.AddString("String 4");
     return TRUE;  // return TRUE  unless you set the focus to a control
    }
    //对两个按钮消息处理
    //通过UpdateData(TRUE)得到窗口中数据
    void CMy55_s1Dlg::OnGet()
    {
     if(UpdateData(TRUE))
     {
      //数据合法性检查通过,可以使用变量中存放的数据
      CString szOut;
      szOut.Format("radio =%d \ncheck is %d\nedit input is %s\ncomboBox input is %s\n",      m_iSel,m_fCheck,m_szEdit,m_szCombo);  AfxMessageBox(szOut);
     }
      //else 未通过检查
    }
    //通过UpdateData(FALSE)将数据放入窗口
    void CMy55_s1Dlg::OnPut()
    {
     m_szEdit="onPut test";
     m_szCombo="onPut test";
     UpdateData(FALSE);
    }
    在上面的例子中我们看到利用DDX/DDV和UpdateData(BOOL)调用我们可以很方便的存取数据,而且也可以同时进行合法性检查。
     
    5.6 使用属性对话框
    属性对话框不同于普通对话框的是它能同时提供多个选项页,而每页都可以由资源编辑器以编辑对话框的方式进行编辑,这样给界面开发带来了方便。同时使用上也遵守普通对话框的规则,所以学习起来很方便。属性对话框由两部分构成:多个属性页(CPropertyPage)和属性对话框(CPropertySheet)。
    首先需要编辑属性页,在资源编辑器中选择插入,并且选择属性对话框后就可以插入一个属性页,如图,或者选择插入一个对话框,然后将其属性中的Style设置为Child,Border设置为Thin也可以,如图。然后根据这个对话框资源生成一个新类,在选择基类时选择CPropertyPage,ClassWizard会自动生成相关的代码。
    而对于CPropertySheet也需要生成新类,并且将所有需要加入的属性页对象都作为成员变量。属性对话框也分为有模式和无模式两种,有模式属性对话框使用DoModal()创建,无模式属性对话框使用Create()创建。下面的代码演示了如何创建属性对话框并添加属性页:
    //修改CPropertySheet派生类的构造函数为如下形式
    CSheet::CSheet()
     :CPropertySheet("test sheet", NULL, 0)
    {
     m_page1.Construct(IDD_PAGE_1);
     m_page2.Construct(IDD_PAGE_2);
     AddPage(&m_page1);
     AddPage(&m_page2);
    }
    //创建有模式属性对话框
    void CMy56_s1Dlg::OnMod()
    {
     CSheet sheet;
     sheet.DoModal();
    }
    //创建无模式属性对话框
    void CMy56_s1Dlg::OnUnm()
    {
     CSheet *sheet=new CSheet;
     sheet->Create();
    }
    对于属性对话框可以使用下面的一些成员函数:
    CPropertyPage* CPropertySheet::GetActivePage( )得到当前活动页的指针。
    BOOL CPropertySheet::SetActivePage( int nPage )用于设置当前活动页。
    int CPropertySheet::GetPageCount()用于得到当前页总数。
    void CPropertySheet::RemovePage( int nPage )用于删除一页。
    而对于属性页来将主要通过重载一些函数来达到控制的目的:
    void CPropertyPage::OnOK() 在属性对话框上按下“确定”按钮后被调用
    void CPropertyPage::OnCancel() 在属性对话框上按下“取消”按钮后被调用
    void CPropertyPage::OnApply() 在属性对话框上按下“应用”按钮后被调用
    void CPropertyPage::SetModified( BOOL bChanged = TRUE ) 设置当前页面上的数据被修改标记,这个调用可以使“应用”按钮为允许状态。
    此外利用属性对话框你可以生成向导对话框,向导对话框同样拥有多个属性页,但同时只有一页被显示,而且对话框上显示的按钮为“上一步”,“下一步”/“完成”,向导对话框会按照你添加页面的顺序依次显示所有的页。在显示属性对话框前你需要调用void CPropertySheet::SetWizardMode()。使用向导对话框时需要对属性页的BOOL CPropertyPage::OnSetActive( )进行重载,并在其中调用void CPropertySheet::SetWizardButtons( DWORD dwFlags )来设置向导对话框上显示的按钮。dwFlags的取值可为以下值的“或”操作:
    PSWIZB_BACK 显示“上一步”按钮
    PSWIZB_NEXT 显示“下一步”按钮
    PSWIZB_FINISH 显示“完成”按钮
    PSWIZB_DISABLEDFINISH 显示禁止的“完成”按钮
    void CPropertySheet::SetWizardButtons( DWORD dwFlags )也可以在其他地方调用,比如说在显示最后一页时先显示禁止的“完成”按钮,在完成某些操作后再显示允许的“完成”按钮。

    在使用向导对话框时可以通过重载一些函数来达到控制的目的:
    void CPropertyPage::OnWizardBack() 按下了“上一步”按钮。返回0表示有系统决定需要显示的页面,-1表示禁止页面转换,如果希望显示一个特定的页面需要返回该页面的ID号。
    void CPropertyPage::OnOnWizardNext() 按下了“下一步”按钮。返回值含义与void CPropertyPage::OnWizardBack()相同。
    void CPropertyPage::OnWizardFinish() 按下了“完成”按钮。返回FALSE表示不允许继续,否则返回TRUE向导对话框将被结束。
    在向导对话框的DoModal()返回值为ID_WIZFINISH或IDCANCEL。下面的代码演示了如何创建并使用向导对话框:
    //创建有模式向导对话框
    void CMy56_s1Dlg::OnWiz()
    {
     CSheet sheet;
     sheet.SetWizardMode();
     int iRet=sheet.DoModal();//返回ID_WIZFINISH或IDCANCEL
    }
    //重载BOOL CPropertyPage::OnSetActive( )来控制显示的按钮
    BOOL CPage1::OnSetActive()
    {
     ((CPropertySheet*)GetParent())->SetWizardButt
    ons(PSWIZB_BACK|PSWIZB_NEXT);
     return CPropertyPage::OnSetActive();
    }
    BOOL CPage2::OnSetActive()
    {
     ((CPropertySheet*)GetParent())->SetWizardButt
    ons(PSWIZB_BACK|PSWIZB_FINISH);
     return CPropertyPage::OnSetActive();
    }
     
    5.7 使用通用对话框
    在Windows系统中提供了一些通用对话框如:文件选择对话框如图,颜色选择对话框如图,字体选择对话框如图。在MFC中使用CFileDialog,CColorDialog,CFontDialog来表示。一般来讲你不需要派生新的类,因为基类已经提供了常用的功能。而且在创建并等待对话框结束后你可以通过成员函数得到用户在对话框中的选择。
    CFileDialog文件选择对话框的使用:首先构造一个对象并提供相应的参数,构造函数原型如下:
    CFileDialog::CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL );参数意义如下:
    bOpenFileDialog 为TRUE则显示打开对话框,为FALSE则显示保存对话文件对话框。
    lpszDefExt 指定默认的文件扩展名。
    lpszFileName 指定默认的文件名。
    dwFlags 指明一些特定风格。
    lpszFilter 是最重要的一个参数,它指明可供选择的文件类型和相应的扩展名。参数格式如:
    "Chart Files (*.xlc)|*.xlc|Worksheet Files (*.xls)|*.xls|Data Files (*.xlc;*.xls)|*.xlc; *.xls|All Files (*.*)|*.*||";文件类型说明和扩展名间用 | 分隔,同种类型文件的扩展名间可以用 ; 分割,每种文件类型间用 | 分隔,末尾用 || 指明。
    pParentWnd 为父窗口指针。
    创建文件对话框可以使用DoModal(),在返回后可以利用下面的函数得到用户选择:
    CString CFileDialog::GetPathName( ) 得到完整的文件名,包括目录名和扩展名如:c:\test\test1.txt
    CString CFileDialog::GetFileName( ) 得到完整的文件名,包括扩展名如:test1.txt
    CString CFileDialog::GetExtName( ) 得到完整的文件扩展名,如:txt
    CString CFileDialog::GetFileTitle ( ) 得到完整的文件名,不包括目录名和扩展名如:test1
    POSITION CFileDialog::GetStartPosition( ) 对于选择了多个文件的情况得到第一个文件位置。
    CString CFileDialog::GetNextPathName( POSITION& pos ) 对于选择了多个文件的情况得到下一个文件位置,并同时返回当前文件名。但必须已经调用过POSITION CFileDialog::GetStartPosition( )来得到最初的POSITION变量。
    CColorDialog颜色选择对话框的使用:首先通过CColorDialog::CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL )构造一个对象,其中clrInit为初始颜色。通过调用DoModal()创建对话框,在返回后调用COLORREF CColorDialog::GetColor( )得到用户选择的颜色值。
    CFontDialog字体选择对话框的使用:首先构造一个对象并提供相应的参数,构造函数原型如下:
    CFontDialog::CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL );构造一个对象,其中参数lplfInitial指向一个LOGFONG结构(该结构介绍请见2.2 在窗口中输出文字),如果该参数设置为NULL表示不设置初始字体。pdcPrinter指向一个代表打印机设备环境的DC对象,若设置该参数则选择的字体就为打印机所用。pParentWnd用于指定父窗口。通过调用DoModal()创建对话框,在返回后通过调用以下函数来得到用户选择:
    void CFontDialog::GetCurrentFont( LPLOGFONT lplf );用来获得所选字体的属性。该函数有一个参数,该参数是指向LOGFONT结构的指针,函数将所选字体的各种属性写入这个LOGFONT结构中。
    CString CFontDialog::GetFaceName( ) 得到所选字体名字。
    int CFontDialog::GetSize( ) 得到所选字体的尺寸(以10个象素为单位)。
    COLORREF CFontDialog::GetColor( ) 得到所选字体的颜色。
    BOOL CFontDialog::IsStrikeOut( )
    BOOL CFontDialog::IsUnderline( )
    BOOL CFontDialog::IsBold( )
    BOOL CFontDialog::IsItalic( )
    得到所选字体的其他属性,是否有删除线,是否有下划线,是否为粗体,是否为斜体。
     
    5.8 建立以对话框为基础的应用
    我认为初学者使用以对话框为基础的应用是一个比较好的选择,因为这样一来可以摆脱一些开发界面的麻烦,此外也可以利用ClassWizard自动的添加消息映射。
    在VC中提供了生成“以对话框为基础的应用”的功能,你所需要选择的是在使用AppWizard的第一步选择“对话框为基础的应用”,如图。VC会生成包含有应用派生类和对话框派生类的代码。在应用类的InitInstance()成员函数中可以看到如下的代码:
    BOOL CMy58_s1App::InitInstance()
    {
     CMy58_s1Dlg 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
     }
     return FALSE;
    }
    这是产生一个有模式对话框并创建它,在对话框返回后通过返回FALSE来直接退出。在设计时通过编辑对话框资源你可以设计好界面,然后通过ClassWizard映射消息来处理客户的输入,由于前几节已经讲过本节也就不再重复。

    同样基于对话框的应用也同样可以使用属性对话框做为界面,或者是通过使用经过派生的通用对话框作为界面。
    提示:当你使用有模式对话框时最开始是无法隐藏窗口的,而只能在对话框显示后再隐藏窗口,所以这会造成屏幕的闪烁。一个解决办法就是采用无模式的对话框,无模式的对话框在创建后是隐藏的,直到你调用ShowWindow(SW_SHOW)才会显示。相关代码如下:
    BOOL CMy58_s1App::InitInstance()
    {
     //必须新生成一个对象,而不能使用局部变量
     CMy58_s1Dlg* pdlg=new CMy58_s1Dlg;
     m_pMainWnd = pdlg;
     pdlg->Create();
     return TRUE;
    }
     
    5.9 使用对话框作为子窗口
    使用对话框作为子窗口是一种很常用的技术,这样可以使界面设计简化而且修改起来更加容易。
    简单的说这种技术的关键就在于创建一个无模式的对话框,并在编辑对话框资源时指明Child风格和无边框风格,如图。接下来利用产生一个CDialog的派生类,并进行相关的消息映射。在创建子窗口时需要利用下面的代码:
    int CMy59_s1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
     if (CView::OnCreate(lpCreateStruct) == -1)
      return -1;
     
     //创建子窗口
     m_dlgChild.Create(IDD_CHILD_DLG,this);
     //重新定位
     m_dlgChild.MoveWindow(0,0,400,200);
     //显示窗口
     m_dlgChild.ShowWindow(SW_SHOW);
     return 0;
    }
    此外还有一中类似的技术是利用CFormView派生类作为子窗口,在编辑对话框资源时也需要指明Child风格和无边框风格。然后利用ClassWizard产生以CFormView为基类的派生类,但是由于该类的成员函数都是受保护的,所以需要对产生的头文件进行如下修改:
    class CTestForm : public CFormView
    {
    //将构造函数和构析函数改为共有函数
    public:
     CTestForm();
     virtual ~CTestForm();
     DECLARE_DYNCREATE(CTestForm)
    ...
    }
    有关创建子窗口的代码如下:
    int CMy59_s1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
     if (CView::OnCreate(lpCreateStruct) == -1)
      return -1;
     
     //对于CFormView派生类必须新生成对象而不能使用成员对象
     m_pformChild = new CTestForm;
     //由于CFormView的成员受保护,所以必须对指针进行强制转换
     CWnd* pWnd=m_pformChild;
     pWnd->Create(NULL,NULL,WS_CHILD|WS_VISIBLE,CRect(0,210,400,400)
    ,this,1001,NULL);
     return 0;
    }
    最后你会看到如图的窗口界面,上方的对话框子窗口和下方的FormView子窗口都可以通过资源编辑器预先编辑好。

    提示:对于CFormView派生类必须新生成对象而不能使用成员对象,因为在CView的OnDestroy()中会有如下代码:delete this;所以使用成员对象的结果会造成对象的二次删除而引发异常。
  • 相关阅读:
    【WindowsAPI之MoveWindow】 C#调整目标窗体的位置、大小
    Visual Studio 2022 community 社区版本离线安装
    MS SQL SERVER 创建表、索引、添加字段等常用脚本
    支付宝支付jemter 插件,导入到高版本jmeter 中使用
    ASP.NET Core定时之Quartz.NET使用
    PB通过OLE方式调用C#.NET DLL时,DLL获取自身根目录
    C#.NET 操作Windows服务(安装、卸载)
    数据库的基本概念
    TCP 与 UDP的区别
    静静地想
  • 原文地址:https://www.cnblogs.com/ciey/p/1536528.html
Copyright © 2020-2023  润新知