场景
- 在开发
WTL
程序时,有时候会需要一些简单的容器窗口来存放子控件(窗口),有了容器窗口,可以通过管理容器窗口的大小,显示隐藏等来统一管理它的所有子窗口,那么WTL
有提供容器窗口吗?能否不新建一个子类通过组合的方式来管理子控件?
说明
- 目前
WTL
没有发现可以直接使用的容器窗口,但是可以通过CreateWindowEx
或CWindow::Create
来创建容器窗口。
// 通过CreateWindowEx或者是CWindow::Create创建
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = {0};
wc.lpfnWndProc = ::DefWindowProc;
wc.hInstance = _AtlBaseModule.GetModuleInstance();
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = ::AtlGetStockBrush(GRAY_BRUSH);
RegisterClass(&wc);
CWindow w;
w.Create(CLASS_NAME,m_hWnd,CRect(100,210,250,310),
NULL,WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
HWND Create(
_In_opt_z_ LPCTSTR lpstrWndClass,
_In_opt_ HWND hWndParent,
_In_ _U_RECT rect = NULL,
_In_opt_z_ LPCTSTR szWindowName = NULL,
_In_ DWORD dwStyle = 0,
_In_ DWORD dwExStyle = 0,
_In_ _U_MENUorID MenuOrID = 0U,
_In_opt_ LPVOID lpCreateParam = NULL) throw()
{
ATLASSUME(m_hWnd == NULL);
if(rect.m_lpRect == NULL)
rect.m_lpRect = &rcDefault;
m_hWnd = ::CreateWindowEx(dwExStyle, lpstrWndClass, szWindowName,
dwStyle, rect.m_lpRect->left, rect.m_lpRect->top, rect.m_lpRect->right - rect.m_lpRect->left,
rect.m_lpRect->bottom - rect.m_lpRect->top, hWndParent, MenuOrID.m_hMenu,
_AtlBaseModule.GetModuleInstance(), lpCreateParam);
return m_hWnd;
}
问题
- 这种通过原始的
Win32
函数创建窗口的方式是有限制的。比如,如果我们有一个自定义的按钮,并处理类似WM_NOTIFY
消息的通知BCN_HOTITEMCHANGE
,这个消息是需要父窗口通过REFLECT_NOTIFICATIONS
来转发到子窗口的.不然的话子窗口接收不到这个通知.
class DhContainerDelegate : public CWindowImpl<DhContainerDelegate>
{
public:
~DhContainerDelegate(){}
BEGIN_MSG_MAP_EX(DhContainerDelegate)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
};
- 这个
BCN_HOTITEMCHANGE
通知触发的流程是以下。
子CButton接受到鼠标消息->子窗口把WM_NOTIFY消息转发给父窗口->父窗口转换为OCM__BASE+uMsg再转发给子窗口
在类里的声明 ATL::CWindowImplRoot<TBase>。
template <class TBase>
LRESULT CWindowImplRoot< TBase >::ReflectNotifications(
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam,
_Out_ BOOL& bHandled)
{
HWND hWndChild = NULL;
switch(uMsg)
{
case WM_COMMAND:
if(lParam != NULL) // not from a menu
hWndChild = (HWND)lParam;
break;
case WM_NOTIFY:
hWndChild = ((LPNMHDR)lParam)->hwndFrom;
break;
case WM_PARENTNOTIFY:
switch(LOWORD(wParam))
{
case WM_CREATE:
case WM_DESTROY:
hWndChild = (HWND)lParam;
break;
default:
hWndChild = GetDlgItem(HIWORD(wParam));
break;
}
break;
case WM_DRAWITEM:
if(wParam) // not from a menu
hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;
break;
case WM_MEASUREITEM:
if(wParam) // not from a menu
hWndChild = GetDlgItem(((LPMEASUREITEMSTRUCT)lParam)->CtlID);
break;
case WM_COMPAREITEM:
if(wParam) // not from a menu
hWndChild = ((LPCOMPAREITEMSTRUCT)lParam)->hwndItem;
break;
case WM_DELETEITEM:
if(wParam) // not from a menu
hWndChild = ((LPDELETEITEMSTRUCT)lParam)->hwndItem;
break;
case WM_VKEYTOITEM:
case WM_CHARTOITEM:
case WM_HSCROLL:
case WM_VSCROLL:
hWndChild = (HWND)lParam;
break;
case WM_CTLCOLORBTN:
case WM_CTLCOLORDLG:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX:
case WM_CTLCOLORMSGBOX:
case WM_CTLCOLORSCROLLBAR:
case WM_CTLCOLORSTATIC:
hWndChild = (HWND)lParam;
break;
default:
break;
}
if(hWndChild == NULL)
{
bHandled = FALSE;
return 1;
}
ATLASSERT(::IsWindow(hWndChild));
return ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);
}
- 因此要进行通知转发,那么必须使用<atlwin.h>里声明的宏
REFLECT_NOTIFICATIONS
,而这个宏的实现只在实现类ATL::CWindowImplRoot
里,CWindow
类是没有处理的。也就是说我们通过CWindow
或者直接CreateWindowEx
创建的容器窗口不会处理通知转发,只能直接通过自定义窗口处理函数来解决,也就是需要自定义lpfnWndProc
窗口处理函数。如果要写这类代码无疑会浪费时间,因此CWindow::Create
或CreateWindowEx
方式虽然不需要新建类文件,但是不能处理通知,用处不大。
方案
- 要处理通知转发,只能新建一个容器子类,并添加通知转发来解决以上的问题。
dh_container_delegate.h
#ifndef __DH_CONTAINER_DELEGATE
#define __DH_CONTAINER_DELEGATE
#include <Windows.h>
#include "atlcrack.h"
class DhContainerDelegate : public CWindowImpl<DhContainerDelegate>
{
public:
~DhContainerDelegate(){}
BEGIN_MSG_MAP_EX(DhContainerDelegate)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
};
#endif
dh_container_delegate.cpp
#include "stdafx.h"
#include "dh_container_delegate.h"
调用方式
auto contentWindow = new DhContainerDelegate();
// 1.设置白色背景.不过这个范围是类静态存储区的,因为它的 CWndClassInfo 是局部静态变量.
contentWindow->GetWndClassInfo().m_wc.hbrBackground = ::AtlGetStockBrush(WHITE_BRUSH);
auto u = contentWindow->Create(m_hWnd,CRect(100,100,250,200),
NULL,WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
例子
- 以下例子是分别使用
DhContainerDelegate
容器窗口和CreateWindowEx
来说明自定义按钮的响应鼠标问题。按钮例子调用了[实用的自定义按钮][1]。
View.h
// View.h : interface of the CView class
//
/
#pragma once
typedef enum CViewWindowId1{
kCViewButtonMinId = 5000,
kCViewButtonMaxId = 5010
}CViewWindowId;
class CView : public CWindowImpl<CView>
{
public:
DECLARE_WND_CLASS(NULL)
BOOL PreTranslateMessage(MSG* pMsg);
BEGIN_MSG_MAP_EX(CView)
MSG_WM_CREATE(OnCreate)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
COMMAND_RANGE_HANDLER_EX(kCViewButtonMinId,kCViewButtonMaxId,OnCommandIDHandlerEX)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
protected:
int OnCreate(LPCREATESTRUCT lpCreateStruct);
LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
void OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl);
private:
};
View.cpp
// View.cpp : implementation of the CView class
//
/
#include "stdafx.h"
#include "resource.h"
#include <string>
#include <GdiPlus.h>
#include "View.h"
#include "dh_container_delegate.h"
#include "atlmisc.h"
#include "tool/utils.h"
#include "view/dh_img_button.h"
using namespace std;
using namespace Gdiplus;
wstring GetDllModuleDir(HMODULE module)
{
static wchar_t szbuf[MAX_PATH];
::GetModuleFileNameW(module,szbuf,MAX_PATH);
::PathRemoveFileSpecW(szbuf);
int length = lstrlen(szbuf);
szbuf[length] = L'\\';
szbuf[length+1] = 0;
return szbuf;
}
void CView::OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl)
{
wchar_t buf[64] = {0};
wsprintf(buf,L"Click Window ID %d",nID);
MessageBox(buf,L"OnCommandIDHandlerEX");
}
int CView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
auto contentWindow = new DhContainerDelegate();
// 1.设置白色背景.不过这个范围是类静态存储区的,因为它的 CWndClassInfo 是局部静态变量.
contentWindow->GetWndClassInfo().m_wc.hbrBackground = ::AtlGetStockBrush(WHITE_BRUSH);
auto u = contentWindow->Create(m_hWnd,CRect(100,100,250,200),
NULL,WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
// 2.通过CreateWindowEx或者是CWindow::Create创建
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = {0};
wc.lpfnWndProc = ::DefWindowProc;
wc.hInstance = _AtlBaseModule.GetModuleInstance();
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = ::AtlGetStockBrush(GRAY_BRUSH);
RegisterClass(&wc);
CWindow w;
w.Create(CLASS_NAME,m_hWnd,CRect(100,210,250,310),
NULL,WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
// 3. 在容器上添加按钮.
Font* font = Utils::GetFont(GetDC(),16,1,true);
auto binDir = GetDllModuleDir(NULL);
auto imageNormal = new Bitmap((binDir+L"home.png").c_str());
auto funcAddButton = [this,&lpCreateStruct,&font](HWND parent,Bitmap* normal,Bitmap* pressed,
int _id,const wchar_t* desc,CRect rect)->DhImgButton*{
auto button = new DhImgButton();
button->Create(parent,rect,NULL,WS_VISIBLE|WS_CHILD,0,_id);
button->SetFont(font);
button->SetImages(normal,pressed,pressed,normal);
button->SetColorOfNormalStatus(RGB(91,155,213),RGB(255,255,255),RGB(91,155,213));
button->SetColorOfPressedStatus(RGB(41,90,135),RGB(128,198,244),RGB(41,90,135));
button->SetColorOfHoverStatus(RGB(41,90,135),RGB(128,198,244),RGB(41,90,135));
button->SetImageAndTextGap(6);
button->Center(true);
::SetWindowText(*button,desc);
return button;
};
auto color = GetSysColor(COLOR_WINDOW);
int _id = kCViewButtonMinId;
auto button = funcAddButton(*contentWindow,imageNormal,imageNormal,_id++,L"Open",CRect(10,10,100,40));
button->SetHMargin(4);
button->SetVMargin(4);
button->SetFixWidth(true);
button->SetImgAndTextLayoutType(kImgButtonImgAndTextLayoutHorizontal);
button->Load(true);
button = funcAddButton(w,imageNormal,imageNormal,_id++,L"Open",CRect(10,10,100,40));
button->SetHMargin(4);
button->SetVMargin(4);
button->SetFixWidth(true);
button->SetImgAndTextLayoutType(kImgButtonImgAndTextLayoutHorizontal);
button->Load(true);
return 0;
}
BOOL CView::PreTranslateMessage(MSG* pMsg)
{
pMsg;
return FALSE;
}
LRESULT CView::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
CPaintDC dc(m_hWnd);
//TODO: Add your drawing code here
return 0;
}
图示
主线程的调用堆栈可看出消息的转发过程。
下载
- 使用
VS2010
以上版本打开,项目自带了WTL10
库.
https://download.csdn.net/download/infoworld/15451353