我尝试对 CButton 进行子类化有什么问题?

What's wrong with my attempt at subclassing CButton?

本文关键字:什么 问题 子类 CButton      更新时间:2023-10-16

我第一次尝试创建一个子类控件,但我觉得我做错了什么。控件是一个Button,我将其放置在设计器中。这是它的类:

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消息。这很糟糕,考虑到尝试子类化控件的主要原因是,这应该是一个具有可重用、自定义Paint行为的可重用类。我想覆盖某些消息处理程序,同时保留那些我没有显式请求的通常的CButton例程。

正如您所看到的,我实现了一个消息映射,但是消息没有传入。

我是这样设置这个类的实例的:

TTTField fld;

是我的主对话框类的成员变量。在这个类中,我添加了以下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 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 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

也许对按钮进行子类化的最好的例子,或者至少是其中之一,就在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())
...

你也将文件外部资源在这个类:使用WTL的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()
// };

更多简单和复杂的自定义WTL控件的示例可以在vikso .dk找到。

关于WTL控件扩展的一个令人困惑的事情是,像CButton, CComboBox这样的基本类是标准控件的薄包装。它们主要将方法转换为要发送的消息。通常可以很容易地将这些类的实例强制转换为HWND并返回。

标准控件本身通过支持通知消息提供一定程度的自定义。

当你子类化一个控件时,你是在你的一侧添加功能,以某种方式需要与现有实现互操作,并且控件类不再是薄包装器。因此,您直接继承CWindowImpl而不是CButton。下一个挑战是具体子类化:您需要创建原始窗口,之后,使用HWND句柄,修改它以通过消息映射路由消息。这就是需要SubclassWindow方法的地方。也就是说,你有控件创建,你查找它的句柄,例如与GetDlgItem,然后你子类化的窗口使用你的类实例SubclassWindow调用。或者,您也可以使用新的类Create方法创建控件,在这种情况下,CreateWindow和与消息映射的关联将为您完成。

一些更复杂的自定义控件的实现还需要您将来自父窗口的通知消息反映到控件中,以便它们可以在相同的自定义控件类中处理它们。这通常需要您在对话框类消息映射中添加一行REFLECT_NOTIFICATIONS(请参阅此相关问题)。