在ATL中实现窗口(精)

转载 2006年06月26日 12:30:00

                                在ATL中实现窗口

 

    ATL提供了一些工具类以实现窗口,这样在ATL组件里就可以不依赖MFC而很方便的创建窗口。

    一、实现对话框

    ATL中有三个模板类可用于创建一个对话框:

    (1)CSimpleDialog:创建模式对话框, 可以host Windows Controls

template < WORD t_wDlgTemplateID, BOOL t_bCenter = TRUE >
class CSimpleDialog : public CDialogImplBase

    (2)CDialogImpl:创建模式或非模式对话框, 可以host Windows Controls

template < class T, class TBase = CWindow >
class ATL_NO_VTABLE CDialogImpl : public CDialogImplBaseT< TBase >

    (3)CAxDialogImpl:创建模式或非模式对话框, 可以host Windows Controls和ActiveX Controls

template < class T, class TBase = CWindow >
class ATL_NO_VTABLE CAxDialogImpl : public CDialogImplBaseT< TBase >

    用CSimpleDialog和CDialogImpl不能显示含有ActiveX控件的对话框,只有CAxDialogImpl可以。如果想处理对话框中ActiveX控件的事件,在OnInitDialog()中加入AtlAdviseSinkMap(this, TRUE)。在退出时加入 AtlAdviseSinkMap(this, FALSE)。通过Insert/New ATL Object/miscellaneous/Dialog生成的对话框缺省就是从CAxDialogImpl继承的。

    这三个类的使用方法很类似。都是派生出一个新类,并确保有一个IDD的成员指明资源ID。如:

class CMyDialog : public CDialogImpl, ...
{
public:
enum { IDD = IDD_MYDIALOG }; //必须要有IDD这个成员,一般都是enum型

BEGIN_MSG_MAP(CMyDialog)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
END_MSG_MAP()

LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return 1;
}
};
CMyDialog dlg ;
dlg.DoModal() ;

    CSimpleDialog的使用可以更简单一些,如下:

CSimpleDialog< IDD_MYDIALOG > dlg ;
dlg.DoModal() ;

    另外CSimpleDialog对IDOK和IDCANCEL有内在的支持,即会自动调用EndDialog,在其定义中包含如下代码:

BEGIN_MSG_MAP(thisClass)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_RANGE_HANDLER(IDOK, IDNO, OnCloseCmd)
END_MSG_MAP()

......

LRESULT OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
::EndDialog(m_hWnd, wID);
return 0;
}

    但CDialogImpl和CAxDialogImpl没有内在的支持,所以必须自己加消息处理函数调用EndDialog,比如:

COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)

LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}

LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}

    二、subclass和superclass

    subclass的作用就是截获消息,指向自己的容器过程函数。有两种subclass。
    (1)instance subclass:子类化一个已有的容器对象实例。每个窗口对象实例都由系统分配了一段内存空间(HWND指的就是这个内存块),保存有窗口的各种信息,包括WndProc的地址。所以可以改写这个地址为自己的WndProc地址,从而可以截获对消息的处理。(当然也要保留原来的WndProc地址,以pass message)
    (2)global subclass:子类化一个window class。在WNDCLASS中保留有WndProc的地址,所以可以改变它为自己的WndProc的地址,这样所有基于这个新的WNDCLASS创建的对象实例都将指向新的WndProc的地址。如图所示:

instance subclass global subclass

    superclass作用与subclass一样,有两点区别:(1)没有instance superclass,只能superclass窗口类。(2)subclass不能截获WM_CREATE、WM_NCCREATE等创建消息,而superclass可以截获,但也一定要pass给原来的WndProc以完成初始化工作。

    三、使用CWindowImpl实现一个窗口

    CWindowImpl是个模板类,其定义如下:

template < class T, class TBase = CWindow, class TWinTraits = CControlWinTraits >
class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT< TBase, TWinTraits >

template < class TBase = CWindow, class TWinTraits = CControlWinTraits >
class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase >

template < class TBase = CWindow >
class ATL_NO_VTABLE CWindowImplRoot : public TBase, public CMessageMap

    可见,对于CWindowImpl< CMyWindow >来说,最终的基类仍是CWindow(缺省情况下)。模板参数TWinTraits主要用于指明所要创建窗口的style。ATL已经内建了一些类型:CControlWinTraits、CFrameWinTraits、CMDIChildWinTraits等,缺省的是CControlWinTraits,包含的style有 WM_CHILD | WM_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS。当然也可以自己定义窗口的style和extended styles。

template < DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0 >
class CWinTraits
{
public:
static DWORD GetWndStyle(DWORD dwStyle)
{
return dwStyle == 0 ? t_dwStyle : dwStyle;
}
static DWORD GetWndExStyle(DWORD dwExStyle)
{
return dwExStyle == 0 ? t_dwExStyle : dwExStyle;
}
};

typedef CWinTraits< WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0 >
CControlWinTraits;
typedef CWinTraits< WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE >
CFrameWinTraits;
typedef CWinTraits< WS_OVERLAPPEDWINDOW | WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_MDICHILD >
CMDIChildWinTraits;

    (1)使用superclass

class CMyWindow : public CWindowImpl< CMyWindow >, ...
{
public:
DECLARE_WND_SUPERCLASS(NULL, "EDIT")
// 消息映射宏
// 消息处理函数
} ;

CMyWindow wnd ;
wnd.Create(...) ;

    (2)使用instance subclass

class CMyWindow : public CWindowImpl< CMyWindow >, ...
{
public:
// 消息映射宏
// 消息处理函数
} ;
CMyWindow wnd ;
wnd.SubclassWindow(hSomeWnd) ;// 已有窗口的句柄

    (3)创建新的窗口

class CMyWindow : public CWindowImpl< CMyWindow >, ...
{
public:
DECLARE_WND_CLASS("MyWindow") // 仅仅定义一个类名,也可以为NULL,系统会自动生成一个类名
// 消息映射宏
// 消息处理函数
} ;

CMyWindow wnd ;
wnd.Create(...) ;

    四、消息映射宏

    ATL里定义了一组消息映射宏,一个典型的例子如下:

BEGIN_MSG_MAP(CMyClass)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
CHAIN_MSG_MAP(CMyBaseClass) // CMyBaseClass表示基类
ALT_MSG_MAP(1) // ATL_MSG_MAP主要用于"contained window"
CHAIN_MSG_MAP(CMyBaseClass)
ALT_MSG_MAP(2)
MESSAGE_HANDLER(WM_CHAR, OnChar)
CHAIN_MSG_MAP_ALT(CMyBaseClass, 1)
END_MSG_MAP()
    宏展开的结果大致如下:
BOOL CMyClass::ProcessWindowMessage(HWND, UINT, WPARAM, LPARAM, LRESULT& lResult, DWORD dwMsgMapID)
{
BOOL bHandled = TRUE ;// 该变量用于判断消息是否已处理过
...
switch(dwMsgMapID) // 缺省为0
{
case 0:
if (uMsg == WM_PAINT)
{
bHandled = TRUE ;
lResult = OnPaint(uMsg, wParam, lParam, bHandled) ; // bHandled是引用型参数
if (bHandled) return TRUE ;
}
if (CMyBaseClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult))
return TRUE ;
break ;
case 1: // ALT_MSG_MAP(1),分支作用
if CMyBaseClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult))
return TRUE ;
break ;
case 2: // ALT_MSG_MAP(1)
if (uMsg == WM_CHAR)
{
bHandled = TRUE ;
lResult = OnChar(uMsg, wParam, lParam, bHandled) ;
if (bHandled) return TRUE ;
}
if CMyBaseClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, 1)) // 选择分支1
return TRUE ;
break ;
default:
...
}
return FALSE ;
}

    ProcessWindowMessage()是虚函数,论其根源在CMessageMap中定义,CMessageMap是CWindowImpl的基类之一(另一个是CWindow)。一般在WindowProc中调用ProcessWindowMessage,若调用失败,即返回FALSE,则调用DefWindowProc,而DefWindowProc则调用成员变量m_pfnSuperWindowProc所指向的窗口过程,这个变量用于subclass和superclass时保留原始的窗口过程指针。

    CHAIN_MSG_MAP给了一个机会,可以执行theChainClass(一般就是父类)的ProcessWindowMessage函数,它一般用于ActiveX Control的实现中,如:CHAIN_MSG_MAP(CComControl< CMyCtl >)。这时该宏是必要的,因为父类可能处理了某些消息,经如在CComControl的类定义中就有下面几条语句:

MESSAGE_HANDLER(WM_PAINT, CComControlBase::OnPaint)
MESSAGE_HANDLER(WM_SETFOCUS, CComControlBase::OnSetFocus)
MESSAGE_HANDLER(WM_KILLFOCUS, CComControlBase::OnKillFocus)
MESSAGE_HANDLER(WM_MOUSEACTIVATE, CComControlBase::OnMouseActivate)

    可见CComControl包含了对WM_PAINT、WM_SETFORCE、WM_KILLFOCUS、WM_MOUSEACTIVE四个消息的处理。只有在父类也处理不了这个消息的情况下才应该去调用DefWindowProc。

    五、使用Contained Window

    contained window被包含在其他对象中,典型用法如下:

class CMyContainer : public CComControl< CMyContainer >, ...	// a full control
{
public:
CContainedWindow m_wndEdit, m_wndList ;
CMyContainer()
: m_wndEdit("Edit", this, 1) // 1表示分支
, m_wndList("List", this, 2) // 2表示分支
{ ... }

BEGIN_MSG_MAP(CMyContainer)
// 处理自己的消息
ALT_MSG_MAP(1) // case 1 :
// 处理edit容器的消息
ALT_MSG_MAP(2) // case 2 :
// 处理list容器的消息
END_MSG_MAP()

LRESULT OnCreate(...)
{
m_wndEdit.Create(...) ;
m_wndList.Create(...) ;
}
......
} ;

    比较CWindowImpl和CContainedWindow的消息处理过程:

    (1)CWindowImpl:

    (2)CContainedWindow:

    可见contained window先是由包含它的container处理它的消息,如果处理不了才转到它自己的DefWindowProc。

    六、实现hosting ActiveX control。

    ATL也提供了一些类以实现可以hosting ActiveX control的窗口。前面提过的CAxDialogImpl就是这样的类,另外CAxWindow可适用于更通用的窗口。它的使用方法也非常简单,下面是一个简单的例子,实现一个窗口,包含一个日历控件。

    (1)声明如下形式的类定义:

class CAtlAxWindow
: public CWindowImpl< CAtlAxWindow, CWindow, CFrameWinTraits >
, public IDispEventImpl< 1, CAtlAxWindow, &DIID_DCalendarEvents, &LIBID_MSACAL, 7, 0 >
{
public:
// 窗口消息
BEGIN_MSG_MAP(CAtlAxWindow)
...
END_MSG_MAP()

// 事件处理
BEGIN_SINK_MAP(CAtlAxWindow)
SINK_ENTRY_EX(1, DIID_DCalendarEvents, 0x1, OnAfterUpdateCalendar)
...
END_SINK_MAP()
STDMETHOD(OnAfterUpdateCalendar)() ;

private:
CAxWindow m_axwnd ;// host activex control

};

    从CWindowImpl继承,是为了创建一个包含ActiveX控件的父窗口,这里用了CFrameWinTraits,表明创建Frame样式的窗口。从IDispEventImpl继承,是为了能接受被包含控件的事件。

    (2)创建hosting窗口有两种方法,分别如下:

	// 方法一
RECT rect = { 0, 0, 400, 300 };
m_axwnd.Create(m_hWnd, rect, _T("MSCAL.Calendar.7"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_axwnd.CenterWindow() ;
m_axwnd.SetWindowText(_T("ActiveX Host Window")) ;
SubclassWindow(m_axwnd.m_hWnd) ;

// 方法二,也可以不使用m_axwnd,在Create中设置WindowName为"AtlAxWin"直接创建
RECT rect = { 0, 0, 400, 300 };
Create(NULL, rect, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
CenterWindow() ;
SetWindowText(_T("ActiveX Host Window")) ;
m_axwnd.Attach(m_hWnd) ;
m_axwnd.CreateControl(OLESTR("MSCAL.Calendar.7")) ;

    这几句话非常简单,具体函数的含义可查查MSDN。

    (3)创建完hosting窗口后,要建立事件连接,如下代码:

	// 建立事件连接
CComPtr< IUnknown > spIUnknown ;
m_axwnd.QueryControl(IID_IUnknown, (void**)&spIUnknown) ;
DispEventAdvise(spIUnknown, &DIID_DCalendarEvents) ;

    效果如图所示:

相关文章推荐

用ATL实现无窗口(Windowless)的Flash播放器

用ATL实现无窗口(Windowless)的Flash播放器   首先,说明一点:我不会写文章,如果有表达不清晰的地方,还请各位包涵一二了,可以在评论中告诉我。   其次,看这篇文章的至少要对...

[ATL/WTL]_[初级]_[窗口如何实现WM_MOUSELEAVE和WM_MOUSEHOVER]

场景: 1. WTL的控件默认不支持进入和移出的 WM_MOUSEHOVER ,WM_MOUSELEAVE 事件, 即使写了映射也没用, 必须要使用函数 _TrackMouseEvent 添加监听...

ATL 窗口代码

从ATL窗口销毁想到的对象生命周期管理

使用ATL窗口时,经常会手动销毁窗口,大致的代码如下:     DestroyWindow();     m_hWnd = NULL; DestoryWindow()调用是同步的,函数返回时窗口...

ATL窗口(2)(转)

WTL窗口 (ATL窗口 第2部分)                  翻译:孙凯 -------------------...

MFC、ATL窗口消息封装机制对比分析

到个人博客阅读 » 新产品在不紧不慢的进行中,这应该是有史以来开发比较“自由”的一个项目。在折腾完一个功能服务器的demo之后,开始折腾起PC客户端。Leader说客户端界面需用ATL来实现。这...
  • cnpsl
  • cnpsl
  • 2012-03-11 10:40
  • 317

[ATL/WTL]_[初级]_[拖放文件到窗口]

场景: 1. 软件需要支持从桌面拖动文件到软件里,避免从文件打开窗口选择文件,这样效率快很多,这时就需要窗口支持拖放技术. drag and drop. 2. 软件需要复制文件到远程或设备里,支持拖放...

ATL布幔下的秘密之窗口类的秘密

介绍   很多人认为ATL只是用来编写COM组件的,其实你也可以使用ATL中的窗口类来创建基于窗口的应用程序。虽然你可以将基于MFC的程序转换为ATL,但是ATL中对于UI(译注:用户界面)组...

关于VC6.0 MFC+ATL做出的COM,其内部的windows窗口不能以XP Theme模式显示的问题彻底解决

最近用vc6.0做windows SHELL CONTEXT开发,其框架是用ATL向导生成的,并且用了MFC窗体,想使窗体以XP风格显示,在网上搜遍了所有的文章,发现采用通用的方法只能在.exe中实现...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)