2

I tried to create a subclassed control for the first time, but I feel like I did something wrong. The control is a Button, which I placed in the designer. This is its 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);

};

Nothing fancy so far.

However, I can't really achieve to receive windows messages in this control. This is bad, considering the main reason for trying to subclass a control was the fact that this should be a reusable class with reusable, custom Paint behaviour. I want to overwrite certain message handlers, while keeping those I didn't explicitely ask for to the usual CButton routine.

As you can see, I implemented a message map, but the messages are just not coming in.

This is how I tried to setup the instance of this class:

TTTField fld;

is a member variable of my main dialog class. In this class I added the following DDX_MAP:

BEGIN_DDX_MAP(TTTMainDialog)
    DDX_CONTROL_HANDLE(IDC_BTN, fld)
END_DDX_MAP()

with IDC_BTN being the id of the button on the designer.

In the assignment operator overload for TTTField I have the following:

TTTField TTTField::operator=(const CWindow& btn)
{
    Attach(btn);
    return *this;
}

I feel like this operator overload might be the source of my problems, but I just can't manage to find a website which is properly explaining the whole topic without using code which seems outdated for like 20 years.

What am I doing wrong here? I am really lost right now.

Sossenbinder
  • 4,852
  • 5
  • 35
  • 78
  • 1
    I'm curious, where did you read info that suggests a button would ever receive a `WM_INITDIALOG` message? A `WM_NCCREATE` and a `WM_CREATE` message, sure. But a message intended for dialogs? Hmmmmmmm. There's a zillion articles on subclassing the CButton class at CodeProject, unless I'm mistaken. (I dont MFC - I prefer to ride bare-back) – enhzflep Sep 10 '16 at 16:48
  • @enhzflep yeah, the thing about the messages is true. I just changed it to WM_CREATE but still don't get any at all – Sossenbinder Sep 10 '16 at 16:52
  • Did you look at any of the CP stuff? This is the first one I found from googling "CButton CodeProject" http://www.codeproject.com/Articles/1911/CButton-with-icon - Take note of the comment that mentions the change of a return value in VC6 from UINT to LRESULT in VS2010 – enhzflep Sep 10 '16 at 17:25
  • @Sossenbinder Slightly off topic, is that a shiba inu on your profile picture? –  Sep 10 '16 at 17:31
  • @enhzflep Thanks for the link. I've been going to quite some code snippets off of CodeProject now but none are really showing any difference to my custom Button class. 90% of the code is usually rather explaining their drawing etc., which I don't need. They all just take for granted that messages are coming to the custom control class. – Sossenbinder Sep 10 '16 at 17:36
  • @RawN Yes, I've been through that already. However, I don't think those articles can help me. I guess the problem is rather the DDX procedure or the call to actually subclass. – Sossenbinder Sep 10 '16 at 17:48
  • This isn't MFC, why do you use MFC tag? – Barmak Shemirani Sep 10 '16 at 18:19
  • @BarmakShemirani deleted it, my bad – Sossenbinder Sep 10 '16 at 18:22
  • 1
    @enhzflep: Notwithstanding your comments on `WM_INITDIALOG`, but this isn't MFC. It's WTL, and as far as *"bare-back"* goes, the generated code pretty much is. (You cannot tell this from the code really, because it's wrong. Correct code would have given it away, though. See answers.) – IInspectable Sep 10 '16 at 22:05
  • @IInspectable - Ahhh, thanks for that, I'd not have re-visited this question if not for your comment. The other answers provide interesting and useful information to me.Looking forward to the time I can stop cringing every time I see your username and recall how poorly behaved and rude I was to you quite some time back. I imagine you've moved on - I haven't yet. I'm sorry about that fateful day. – enhzflep Sep 11 '16 at 01:26

2 Answers2

6

The button class should be defined as follows:

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;
    }
};

Override dialog box's OnInitDialog, call SubclassWindow to subclass the button:

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;
    }
};

Edit, for initialization

class TTTField : public CWindowImpl<TTTField , CButton>
{
public:
    void Create(CWindow *wnd, int id)
    {
        SubclassWindow(wnd->GetDlgItem(id));
        //add initialization here
    }
    ...
}

Then to create the button:

//fld.SubclassWindow(GetDlgItem(IDC_BTN));
fld.Create(this, IDC_BTN); //<== use this instead
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • [Button actions typically happen on mouse up, not mouse down.](https://blogs.msdn.microsoft.com/oldnewthing/20061216-13/?p=28703) Also remember that buttons capture the mouse; you want to make sure the mouse is inside the button on the button up. – andlabs Sep 10 '16 at 20:34
  • @andlabs: I don't know the answer, but I would assume, that WTL handles this already, i.e. the `OnLButtonUp` member won't be called, unless the `WM_LBUTTONUP` was inside the control's window area. – IInspectable Sep 10 '16 at 22:09
  • Yes, I just used that for testing the subclass. But it's a bad example. Handling the mouse click is more complicated. Also the button has to respond to keyboard hits. – Barmak Shemirani Sep 10 '16 at 22:14
  • This might be a long shot, but I'm back at a similar problem and thought I might be able to ask you. I currently need to get some kind of initialisation message to make some changes to my subclass button right after the subclassing has been done. Is there a suitable windows message or should I just implement a method and call it after subclassing? – Sossenbinder Oct 01 '16 at 09:16
  • I don't think there is any special message for that purpose. You just have to call the initialization immediately after calling `SubclassWindow`. It's easy to put it in one function to make it self-contained (see edit). – Barmak Shemirani Oct 01 '16 at 18:07
4

Perhaps the best example, or at least one of, of subclassing a button is right in the sources of WTL, at the top of 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())
...

You will also file external resources on this class: Using WTL's CBitmapButton.

That's not to mention WTL's comment on doing the controls:

// 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()
// };

More examples of simple and sophisticated custom WTL controls can be found at viksoe.dk.

A confusing thing about WTL control extension is that basic classes like CButton, CComboBox are thin wrappers over standard controls. They mostly translate methods into messages to be sent. You can often easily cast instances of such classes to HWND and back.

Standard controls themselves offer a level of customization through support of notification messages.

When you subclass a control, you are adding functionality on your side which somehow needs to interoperate with stock implementation, and control classes are no longer thin wrappers. Hence, you inherit from CWindowImpl and not CButton directly. Next challenge is to specifically subclass: you need to have original window created and after that, having a HWND handle, you modify it to route the messages through your message map. This is where you need SubclassWindow method. That is, you have the control created, you look it up its handle, e.g. with GetDlgItem and then you subclass the window using your class instance SubclassWindow call. Or, alternatively you can create the control using your new class Create method in which case CreateWindow and association with your message map will be done for you.

Some, more complicated, implementations of custom controls will also want you to reflect notification messages from parent window to the controls, so that they could handle them within the same custom control class. This will typically require that you add a line REFLECT_NOTIFICATIONS in your dialog class message map (see this related question on this).

Community
  • 1
  • 1
Roman R.
  • 68,205
  • 6
  • 94
  • 158