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 featuresnew 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  用户控件: CStaticCButtonCListBoxCComboBoxCEditCScrollBarCDragListBox

o  普通控件:  CImageListCListViewCtrl (CListCtrl in MFC), CTreeViewCtrl (CTreeCtrl in MFC), CHeaderCtrl,CToolBarCtrlCStatusBarCtrlCTabCtrlCToolTipCtrlCTrackBarCtrl (CSliderCtrl in MFC), CUpDownCtrl(CSpinButtonCtrl in MFC), CProgressBarCtrlCHotKeyCtrlCAnimateCtrlCRichEditCtrlCReBarCtrlCComboBoxEx,CDateTimePickerCtrlCMonthCalendarCtrlCIPAddressCtrl

o  普通控件(MFC中没有的):  CPagerCtrlCFlatScrollBarCLinkCtrl 

o  这里还有一些 WTL-Specific 类:CBitmapButtonCCheckListViewCtrl (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包装被销毁时, 控件本身并没有被销毁。 

例外是: CBitmapButtonCCheckListViewCtrl, 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>
{
.....
}
2. 声明对象
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。

posted on 2011-03-06 18:27  oleeceo  阅读(2009)  评论(0)    收藏  举报

导航