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

时间:2016-09-10 16:38:01

标签: c++ winapi atl wtl

我第一次尝试创建一个子类控件,但我觉得我做错了。控件是一个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年的代码。

我在这里做错了什么?我现在真的迷失了。

2 个答案:

答案 0 :(得分:6)

按钮类应定义如下:

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

答案 1 :(得分:4)

也许最好的例子,或者至少有一个子类化按钮是在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())
...

您还将在此课程中提交外部资源: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控件扩展的一个令人困惑的事情是像CButtonCComboBox这样的基本类是标准控件的薄包装器。他们主要将方法转换为要发送的消息。您通常可以轻松地将此类的实例转换为HWND并返回。

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

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

一些更复杂的自定义控件实现还希望您将父窗口中的通知消息反映到控件,以便他们可以在同一个自定义控件类中处理它们。这通常要求您在对话框类消息映射中添加一行REFLECT_NOTIFICATIONS(请参阅this related question)。