WTL(4)_Dialog and Controls
Introduction to Part IV
对话框 和 控件 是一个区域 MFC确实用这个东西提高了工作效率。 如果没有MFC的控件类,你可能陷入 组织和书写SendMessagel来调用管理控件。
MFC总是提供Dialog data exchange(DDX), 这种方法在 variable 和 controls 之间 传递数据。
WTL支持所有这些特征, 别且 对那些常用的控件 还增强了其功能。 这篇文章,我们会看到一个基于Dialog的app,并且更多的WTL message-handing enhancements。
Advanced UI features 和 new controls 在WTL中出现。
Refresher on ATL Dialogs
回顾part1, 有两种对话框, CDialogImpl, CDAxialogImpl, 后者可以包含ActiveX控件, 本章我们无法涵盖ActvieX内容, 所以只讲解前者。
创建窗口的3步:
1. 窗口资源
2. 写一个派生自CDialogImpl的类
3. 创建一个public 成员变量调用IDD, 绑定窗口资源
接下来, 你可以增加消息处理,像其他的消息处理一样添加。
Control Wrapper Classes
WTL包装了 很多个控件, 你看起来一定会很熟悉, 因为他们的名字和MFC相似。
所以你完全可以 使用 MFC的文档, 来学习WTL的控件, 但糟糕的是 F12无法自动定位到定义处了。
以下是WTL包装的 控件:
o 用户控件: CStatic, CButton, CListBox, CComboBox, CEdit, CScrollBar, CDragListBox
o 普通控件: CImageList, CListViewCtrl (CListCtrl in MFC), CTreeViewCtrl (CTreeCtrl in MFC), CHeaderCtrl,CToolBarCtrl, CStatusBarCtrl, CTabCtrl, CToolTipCtrl, CTrackBarCtrl (CSliderCtrl in MFC), CUpDownCtrl(CSpinButtonCtrl in MFC), CProgressBarCtrl, CHotKeyCtrl, CAnimateCtrl, CRichEditCtrl, CReBarCtrl, CComboBoxEx,CDateTimePickerCtrl, CMonthCalendarCtrl, CIPAddressCtrl
o 普通控件(MFC中没有的): CPagerCtrl, CFlatScrollBar, CLinkCtrl
o 这里还有一些 WTL-Specific 类:CBitmapButton, CCheckListViewCtrl (list view control with check boxes), CTreeViewCtrlEx andCTreeItem (used together, CTreeItem wraps an HTREEITEM), CHyperLink (clickable hyperlink, available on all OSes)
注意一件事,许多包装类是 windows interface classes, 比如 CWindow. 他包装一个HWND 并提供发送相关信息(例如: CListBox::GetCurSEL()封装了LB_GETCURSEL消息)。
像CWindows, 他很小巧的可以去 创建 1个临时控件包装 和 绑定1个 已存控件。 像CWindow, 当CWindow包装被销毁时, 控件本身并没有被销毁。
例外是: CBitmapButton, CCheckListViewCtrl, and CHyperLink.
因为这个文章是面对 有经验的MFC程序员的, 所以在这里我不会说太多 有关类封装的问题, 因为他们和MFC封装和你相似, 我会说一些WTL中新出现的内容。
CBitMapButton和CHyperLink在MFC中不好找到原型。
Creating a Dialog-Based App with the AppWizard
创建1个基于Dialog的app......
首先, initializes COM, 创建1个单线程程序。 这对于ActiveX controls同样必须。
如果,你的程序不需要COM, 则可以移除CoInitialize()和CoUninitialize().
接下来, AtlInitCommonControls(), 是InitCommonControlsEx()的包装.
全局_Module初始化+ Dialog显示。
(注意 ATL对话框 创建 DoModal()实际上是模态, 不像MFC所有对话框是非模态, MFC simulates modality by manually disabling the dialog's parent.)
最后, _Module和COM被卸载, DoModal()返回值作为APP退出返回代码。
CMainDlg的变量是重要的,因为CMainDlg可能有成员用到ATL,WTL. 如果没有present, CMainDl可能在_Module.Term()之后,这样会引起很难诊断的错误。
运行程序,现在程序很赤裸。
Using the Control Wrapper Classes
连接 成员变量 和 控件的方法:
一些使用 普通CWindows()(或者另一个窗口接口类, 像CListViewCtrl)
一些使用 CWindowsImpl派生类。
用CWindows好,如果你只是临时变量, 但CWindowImpl允许你派生控件并 处理发给他的消息。
ATL Way 1 - Attaching a CWindow
HWND hwndList = GetDlgItem(IDC_LIST);
CListViewCtrl wndList1 (hwndList); // use constructor
CListViewCtrl wndList2, wndList3;
wndList2.Attach ( hwndList ); // use Attach method
wndList3 = hwndList; // use assignment operator
...这有毛用??
ATL Way 2 - CContainedWindow
CContainedWindow是 介于CWindow和 CWindowsImpl的方法
1. 他允许你派生子类
2. 他允许你处理消息, 但是在父亲窗口中。
这是的你 你将所有消息处理放在Dialog类中, 而不是CWindowImpl类一样为每个控件写消息。
注意: 你不能用CContainedWindow处理WM_COMMAND, WM_NOTIFY消息。
使用CContainedWindow的4个步骤。
1.声明1个变量。
class CMainDlg : public CDialogImpl<CMainDlg>
{
// ...
protected:
CContainedWindow m_wndOKBtn, m_wndExitBtn;
};
2. 添加ALT_MSG_MAP消息过滤
class CMainDlg : public CDialogImpl<CMainDlg>
{
public:
BEGIN_MSG_MAP_EX(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
ALT_MSG_MAP(1)
MSG_WM_SETCURSOR(OnSetCursor_OK)
ALT_MSG_MAP(2)
MSG_WM_SETCURSOR(OnSetCursor_Exit)
END_MSG_MAP()
LRESULT OnSetCursor_OK(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);
LRESULT OnSetCursor_Exit(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);
};
3. 构造函数时, 链接 CContainedWindows变量 与 ALT_MSG_MAP
CMainDlg::CMainDlg() : m_wndOKBtn(this, 1),
m_wndExitBtn(this, 2)
{
}
1参表示, 使用本类的message map, 2参表示, 哪个ALT_MSG_MAP送去他的消息。
4. Attach Control!!!!到这儿好像看明白点是用来连接控件的了....
Collapse
LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
return TRUE;
}
ATL Way 3 - Subclassing
这个方法和 方法2很像。 但是消息处理跑到了 CWindowImpl 中, 而非在 CDialog中。
3步
1. 声明类, 继承自CWindowImpl
class CButtonImpl : public CWindowImpl<CButtonImpl, CButton>
{
.....
}
class CMainDlg : public CDialogImpl<CMainDlg>
{
// ...
protected:
CContainedWindow m_wndOKBtn, m_wndExitBtn;
CButtonImpl m_wndAboutBtn;
};
3. OnInitDialog中, 通过子类窗口关联
LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
// CButtonImpl: subclass the About button
m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );
return TRUE;
}
第三种方法说完了,,,还是没明白他要干什么,,,只知道, 他绑定了窗口中的控件。
WTL Way 1 - DDX_CONTROL
WTL的DDX和 MFC的很像, 但是他能够很轻松的 让control 连接 connect.
你需要一个CWindowImpl派生类, 像前边那个例子。
这次我们会用一个 新的类, CEditImpl, 因为这个例子会 派生 edit control.
#include atlddx.h in stdafx.h 来加入DDX code.
CMainDlg中加CWinDataExchange接口:
class CMainDlg : public CDialogImpl<CMainDlg>,
public CWinDataExchange<CMainDlg>
{//...};
CMainDlg中绑定 控件ID 和 类变量
class CMainDlg : public CDialogImpl<CMainDlg>,
public CWinDataExchange<CMainDlg>
{
BEGIN_DDX_MAP(CMainDlg)
DDX_CONTROL(IDC_EDIT, m_wndEdit)
END_DDX_MAP()
protected:
CEditImpl m_wndEdit;
};
链接消息 与 OnXXXX
void OnContextMenu ( HWND hwndCtrl, CPoint ptClick )
{
MessageBox("Edit control handled WM_CONTEXTMENU");
}
Handling Notifications from Controls
一个control送给他的父窗口一个notification消息,以WM_COMMAND或WM_NOTIFY消息方式。然后父窗口处理。
还有一些消息可以被认为是 notifications消息, 例如WM_DRAWITEM, 当一个控件需要绘制的时候发送这个消息。然后父窗口处理。
Reflection 的工组方式向MFC一样, 可以把notifications消息的处理,放在类自身,这样方便移至其他工程中。(buttonctrl.h, buttonctrl.cpp包含所有button处理, 方便的放到其他工程使用button)
Handling notifications in the parent
WM_NOTIFY,WM_COMMAND.包含ID, HWND, notification code.
WM_NOTIFY,将这些都包含在NMHDR结构体中。
ATL,WTL的处理过程有些不同, 这里只讲述WTL的处理过程。
*1 需要BEGIN_MSG_MAP_EX
Message map macros
COMMAND_HANDLER_EX(id, code, func)
指定ID, 指定NOTIFY_CODE, FUNC
COMMAND_CODE_HANDLER_EX(id, func)
处理1个指定ID控件的 所有NOTIFY_CODE
COMMAND_ID_HANDLER_EX(code, func)
处理所有ID的指定NOTIFY_CODE消息
COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func)
处理一定范围内的ID的,所有NOTIFY
COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func)
处理一定范围内的ID的,指定NOTIFY
Examples:
WM_COMMAND的func
void func ( UINT uCode, int nCtrlID, HWND hwndCtrl );
WM_NOTIFY的func
LRESULT func ( NMHDR* phdr );
Reflecting Notifications
如果你有一个 CWindowImpl派生的control, 像我们的CEditImpl,你可以在 类中处理 notifications, 而不必传给父窗口处理,这叫做 反映notification.
他与MFC的工作方式很像, 但不同在于 父类与派生类 都需要参与到 reflection中, 而MFC只需要 Control类 自己处理。
当你想要把notify消息 反映回control自身时, 需要在父窗口中增加*2 REFLECT_NOTIFICATIONS();
class CMainDlg : public ...
{
public:
BEGIN_MSG_MAP_EX(CMainDlg)
//...
NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
};
这个宏会添加一些处理代码,使得messages不会被之前的macros处理。
它检查 message的HWND,然后把消息送会到这个HWND。
但是message值变了, 变成了*3 OCM_XXX替换掉原来的WM_XXX。
之后的处理就像普通消息一样。
这里列举了18个可以反映的消息:
o Control notifications: WM_COMMAND, WM_NOTIFY, WM_PARENTNOTIFY
o Owner drawing: WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM, WM_DELETEITEM
o List Box keyboard messages : WM_VKEYTOITEM, WM_CHARTOITEM
o Others: WM_HSCROLL, WM_VSCROLL, WM_CTLCOLOR*
在这个控件类中, 你处理需要处理的 reflected messages, 在最后 加上 DEFAULT_REFLECTION_HANDLER().
*4 DEFAULT_REFLECTION_HANDLER()保证把没有处理的消息 发送给DefWindowProc()处理。
class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>
{
public:
BEGIN_MSG_MAP_EX(CODButtonImpl)
MSG_OCM_DRAWITEM(OnDrawItem)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
void OnDrawItem ( UINT idCtrl, LPDRAWITEMSTRUCT lpdis )
{
// do drawing here...
}
};
WTL macros for handling reflected messages
我们只看了一个reflected的宏, MSG_OCM_DRAWITEM.
这里还有另外17个message的宏, 组成了MSG_OCM_*系列。
..
这些宏和COMMAND_HANDLER_EX和NOTIFY_HANDLER_EX很像,只是加了 REFLECTED_前缀。
Dialog Fonts
如果你对UI非常讲究,像我一样,并且你在使用XP 或者2000, 你可能想要将 Tahoma字体换成 MS Sans Serif 字体。
因为VC 6实在太老了, 产生的资源文件在NT4上运行很好, 但在之后的NT版本上无法运行。
你可以修改它们, 但是需要 手动修改 资源文件。
你需要做3件事:
1. 对话框类型 : DIALOG 改成 DIALOGEX
2. 窗口类型: 增加 DS_SHELLFONT
3. 对话框字体: 把 MS Sans Serif 改成 MS Shell Dlg
不幸的是, 你每次修改,保存resources都会将前两步自动丢失。
所以你要不同的重设。
改前
IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 187, 102
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About"
FONT 8, "MS Sans Serif"
BEGIN
...
END
改后
IDD_ABOUTBOX DIALOGEX DISCARDABLE 0, 0, 187, 102
STYLE DS_SHELLFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About"
FONT 8, "MS Shell Dlg"
BEGIN
...
END
VC7中你只需要改变一个设置, 系统会自动改变你的字体成 MS Shell Dlg。
浙公网安备 33010602011771号