【问题标题】:What's wrong with my attempt at subclassing CButton?我尝试继承 CButton 有什么问题?
【发布时间】:2017-01-18 14:09:24
【问题描述】:

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

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

【问题讨论】:

  • 我很好奇,您从哪里读到表明按钮会收到WM_INITDIALOG 消息的信息? WM_NCCREATEWM_CREATE 消息,当然。但是用于对话的消息?嗯嗯嗯。除非我弄错了,否则在 CodeProject 上有无数关于 CButton 类的子类化的文章。 (我没有 MFC - 我更喜欢光着背)
  • @enhzflep 是的,关于消息的事情是真的。我只是将其更改为 WM_CREATE 但仍然没有得到任何东西
  • 你看过CP的东西吗?这是我从谷歌搜索“CButton CodeProject”codeproject.com/Articles/1911/CButton-with-icon 中找到的第一个 - 请注意提到在 VS2010 中将 VC6 中的返回值从 UINT 更改为 LRESULT 的评论
  • @Sossenbinder 有点跑题了,那是你头像上的柴犬吗?
  • @enhzflep:尽管你的 cmets 在 WM_INITDIALOG,但这不是 MFC。它是 WTL,就 "bare-back" 而言,生成的代码几乎就是这样。 (你不能从代码中真正看出这一点,因为它是错误的。不过,正确的代码会泄露它。请参阅答案。)

标签: c++ winapi atl wtl


【解决方案1】:

按钮类应该定义如下:

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

【讨论】:

  • Button actions typically happen on mouse up, not mouse down. 还记得按钮捕获鼠标;您要确保鼠标在按钮上的按钮内。
  • @andlabs:我不知道答案,但我认为 WTL 已经处理了这个问题,即不会调用 OnLButtonUp 成员,除非 WM_LBUTTONUP 在控件的窗口区域。
  • 是的,我只是用它来测试子类。但这是一个不好的例子。处理鼠标点击更复杂。按钮也必须响应键盘敲击。
  • 这可能是一个很长的镜头,但我又遇到了类似的问题,我想我也许可以问你。我目前需要在子类化完成后立即获取某种初始化消息以对我的子类按钮进行一些更改。是否有合适的 windows 消息或者我应该只实现一个方法并在子类化后调用它?
  • 我不认为有任何特殊信息可以用于此目的。您只需在调用SubclassWindow 后立即调用初始化。很容易将它放在一个函数中以使其独立(见编辑)。
【解决方案2】:

也许对按钮进行子类化的最佳示例,或者至少其中一个示例就在 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()
// };

有关简单和复杂的自定义 WTL 控件的更多示例,请访问 viksoe.dk

关于 WTL 控件扩展的一个令人困惑的事情是,CButtonCComboBox 等基本类是标准控件的薄包装器。他们主要将方法转换为要发送的消息。您通常可以轻松地将此类类的实例转换为 HWND 并返回。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-24
    • 1970-01-01
    • 1970-01-01
    • 2013-01-24
    • 1970-01-01
    • 2020-04-07
    • 1970-01-01
    相关资源
    最近更新 更多