我对 CButton 进行子类化的尝试有什么问题?
What's wrong with my attempt at subclassing CButton?
我第一次尝试创建子classed控件,但我觉得我做错了什么。控件是一个按钮,我放在设计器中。这是它的 class:
class TTTField : public CButton
{
public:
BEGIN_MSG_MAP_EX(TTTField)
MSG_WM_INITDIALOG(OnInitDialog);
END_MSG_MAP()
TTTField operator=(const CWindow& btn);
private:
const BOOL OnInitDialog(const CWindow wndFocus, const LPARAM lInitParam);
};
到目前为止没什么特别的。
但是,我无法真正实现在这个控件中接收windows消息。这很糟糕,考虑到尝试子 class 控件的主要原因是这应该是一个可重用 class 且具有可重用、自定义 Paint 行为的事实。我想覆盖某些消息处理程序,同时保留那些我没有明确要求的常规 CButton 例程。
如您所见,我实现了消息映射,但消息就是收不到。
这就是我尝试设置此 class 实例的方式:
TTTField fld;
是我主对话框的成员变量class。在此 class 我添加了以下内容 DDX_MAP:
BEGIN_DDX_MAP(TTTMainDialog)
DDX_CONTROL_HANDLE(IDC_BTN, fld)
END_DDX_MAP()
其中 IDC_BTN 是设计器上按钮的 ID。
在 TTTField 的赋值运算符重载中,我有以下内容:
TTTField TTTField::operator=(const CWindow& btn)
{
Attach(btn);
return *this;
}
我觉得这个运算符过载可能是我问题的根源,但我无法找到一个正确解释整个主题的网站,而不使用似乎已经过时 20 年的代码。
我在这里做错了什么?我现在真的迷路了。
按钮class应该定义如下:
class TTTField : public CWindowImpl<TTTField, CButton>
{
protected:
BEGIN_MSG_MAP_EX(TTTField)
MSG_WM_LBUTTONDOWN(OnLButtonDown)
END_MSG_MAP()
protected:
LRESULT OnLButtonDown(UINT, CPoint)
{
//Edit: this override is meant for testing the subclass only
//it's insufficient for handling button clicks
MessageBox(L"Testing override...");
return 0;
}
};
覆盖对话框的 OnInitDialog
,调用 SubclassWindow
以子class 按钮:
class TTTMainDialog: public CDialogImpl<CMainDialog>
{
public:
enum { IDD = IDD_MYDIALOG };
BEGIN_MSG_MAP(TTTMainDialog)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
END_MSG_MAP()
TTTField fld;
LRESULT OnInitDialog(UINT nMessage, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
fld.SubclassWindow(GetDlgItem(IDC_BTN));
return 0;
}
};
编辑,用于初始化
class TTTField : public CWindowImpl<TTTField , CButton>
{
public:
void Create(CWindow *wnd, int id)
{
SubclassWindow(wnd->GetDlgItem(id));
//add initialization here
}
...
}
然后创建按钮:
//fld.SubclassWindow(GetDlgItem(IDC_BTN));
fld.Create(this, IDC_BTN); //<== use this instead
也许最好的例子,或至少其中之一,subclass 按钮就在 WTL 的源代码中,在 atlctrlx.h:
的顶部
template <class T, class TBase = CButton, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CBitmapButtonImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
DECLARE_WND_SUPERCLASS(NULL, TBase::GetWndClassName())
...
您还将在此 class 上提交外部资源:Using WTL's CBitmapButton。
更不用说 WTL 对控制的评论了:
// These are wrapper classes for Windows standard and common controls.
// To implement a window based on a control, use following:
// Example: Implementing a window based on a list box
//
// class CMyListBox : CWindowImpl<CMyListBox, CListBox>
// {
// public:
// BEGIN_MSG_MAP(CMyListBox)
// // put your message handler entries here
// END_MSG_MAP()
// };
可以在 viksoe.dk 找到更多简单和复杂的自定义 WTL 控件的示例。
关于 WTL 控件扩展的一个令人困惑的事情是像 CButton
、CComboBox
这样的基本 classes 是标准控件的薄包装。他们主要将方法转换为要发送的消息。您通常可以轻松地将此类 classes 的实例转换为 HWND
并返回。
标准控件本身通过支持通知消息提供一定程度的自定义。
当您子class 一个控件时,您正在添加功能,这些功能需要以某种方式与现有实现进行互操作,并且控件 class 不再是简单的包装器。因此,您继承自 CWindowImpl
而不是直接继承自 CButton
。下一个挑战是专门针对 subclass:您需要创建原始的 window,然后,拥有一个 HWND
句柄,您可以修改它以通过您的消息映射路由消息。这是您需要 SubclassWindow
方法的地方。也就是说,您创建了控件,查找它的句柄,例如使用 GetDlgItem
,然后使用 class 实例 SubclassWindow
调用子 class window。或者,您也可以使用新的 class Create
方法创建控件,在这种情况下 CreateWindow
以及与消息映射的关联将为您完成。
自定义控件的一些更复杂的实现还需要您将父 window 的通知消息反映到控件,以便它们可以在同一自定义控件 class 中处理它们。这通常需要您在对话框 class 消息映射中添加一行 REFLECT_NOTIFICATIONS
(参见 this related question)。
我第一次尝试创建子classed控件,但我觉得我做错了什么。控件是一个按钮,我放在设计器中。这是它的 class:
class TTTField : public CButton
{
public:
BEGIN_MSG_MAP_EX(TTTField)
MSG_WM_INITDIALOG(OnInitDialog);
END_MSG_MAP()
TTTField operator=(const CWindow& btn);
private:
const BOOL OnInitDialog(const CWindow wndFocus, const LPARAM lInitParam);
};
到目前为止没什么特别的。
但是,我无法真正实现在这个控件中接收windows消息。这很糟糕,考虑到尝试子 class 控件的主要原因是这应该是一个可重用 class 且具有可重用、自定义 Paint 行为的事实。我想覆盖某些消息处理程序,同时保留那些我没有明确要求的常规 CButton 例程。
如您所见,我实现了消息映射,但消息就是收不到。
这就是我尝试设置此 class 实例的方式:
TTTField fld;
是我主对话框的成员变量class。在此 class 我添加了以下内容 DDX_MAP:
BEGIN_DDX_MAP(TTTMainDialog)
DDX_CONTROL_HANDLE(IDC_BTN, fld)
END_DDX_MAP()
其中 IDC_BTN 是设计器上按钮的 ID。
在 TTTField 的赋值运算符重载中,我有以下内容:
TTTField TTTField::operator=(const CWindow& btn)
{
Attach(btn);
return *this;
}
我觉得这个运算符过载可能是我问题的根源,但我无法找到一个正确解释整个主题的网站,而不使用似乎已经过时 20 年的代码。
我在这里做错了什么?我现在真的迷路了。
按钮class应该定义如下:
class TTTField : public CWindowImpl<TTTField, CButton>
{
protected:
BEGIN_MSG_MAP_EX(TTTField)
MSG_WM_LBUTTONDOWN(OnLButtonDown)
END_MSG_MAP()
protected:
LRESULT OnLButtonDown(UINT, CPoint)
{
//Edit: this override is meant for testing the subclass only
//it's insufficient for handling button clicks
MessageBox(L"Testing override...");
return 0;
}
};
覆盖对话框的 OnInitDialog
,调用 SubclassWindow
以子class 按钮:
class TTTMainDialog: public CDialogImpl<CMainDialog>
{
public:
enum { IDD = IDD_MYDIALOG };
BEGIN_MSG_MAP(TTTMainDialog)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
END_MSG_MAP()
TTTField fld;
LRESULT OnInitDialog(UINT nMessage, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
fld.SubclassWindow(GetDlgItem(IDC_BTN));
return 0;
}
};
编辑,用于初始化
class TTTField : public CWindowImpl<TTTField , CButton>
{
public:
void Create(CWindow *wnd, int id)
{
SubclassWindow(wnd->GetDlgItem(id));
//add initialization here
}
...
}
然后创建按钮:
//fld.SubclassWindow(GetDlgItem(IDC_BTN));
fld.Create(this, IDC_BTN); //<== use this instead
也许最好的例子,或至少其中之一,subclass 按钮就在 WTL 的源代码中,在 atlctrlx.h:
的顶部template <class T, class TBase = CButton, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CBitmapButtonImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
DECLARE_WND_SUPERCLASS(NULL, TBase::GetWndClassName())
...
您还将在此 class 上提交外部资源:Using WTL's CBitmapButton。
更不用说 WTL 对控制的评论了:
// These are wrapper classes for Windows standard and common controls.
// To implement a window based on a control, use following:
// Example: Implementing a window based on a list box
//
// class CMyListBox : CWindowImpl<CMyListBox, CListBox>
// {
// public:
// BEGIN_MSG_MAP(CMyListBox)
// // put your message handler entries here
// END_MSG_MAP()
// };
可以在 viksoe.dk 找到更多简单和复杂的自定义 WTL 控件的示例。
关于 WTL 控件扩展的一个令人困惑的事情是像 CButton
、CComboBox
这样的基本 classes 是标准控件的薄包装。他们主要将方法转换为要发送的消息。您通常可以轻松地将此类 classes 的实例转换为 HWND
并返回。
标准控件本身通过支持通知消息提供一定程度的自定义。
当您子class 一个控件时,您正在添加功能,这些功能需要以某种方式与现有实现进行互操作,并且控件 class 不再是简单的包装器。因此,您继承自 CWindowImpl
而不是直接继承自 CButton
。下一个挑战是专门针对 subclass:您需要创建原始的 window,然后,拥有一个 HWND
句柄,您可以修改它以通过您的消息映射路由消息。这是您需要 SubclassWindow
方法的地方。也就是说,您创建了控件,查找它的句柄,例如使用 GetDlgItem
,然后使用 class 实例 SubclassWindow
调用子 class window。或者,您也可以使用新的 class Create
方法创建控件,在这种情况下 CreateWindow
以及与消息映射的关联将为您完成。
自定义控件的一些更复杂的实现还需要您将父 window 的通知消息反映到控件,以便它们可以在同一自定义控件 class 中处理它们。这通常需要您在对话框 class 消息映射中添加一行 REFLECT_NOTIFICATIONS
(参见 this related question)。