Detect when a child CButton control is added to CDialog using WM_PARENTNOTIFY

Viewed 402

Working with:

  • Visual Studio 2017;
  • MFC, C++.

I'm trying to modify a MFC project, for a CDialog derived class to detect when a child control is added to it ( CButton derived classes ). Preferably getting a handle (HWND) to those CButtons to process them further, like I can get from the parent dialog window with the following code:

HWND handleParent = ::GetTopWindow(this->GetSafeHwnd());

I've read one way to do it, by handling WM_PARENTNOTIFY, but I cannot trigger it by any way using main event function: OnParentNotify (or WindowProc from some sources).

I've done the following, at least for OnParentNotify:

  1. added a message export:

ON_WM_PARENTNOTIFY()

  1. in member function DerivedDialog::OnInitDialog() removed WS_EX_NOPARENTNOTIFY style from all control handles possible, after CDialog::OnInitDialog() line of code:
CDialog::OnInitDialog();

HWND hwnd = ::GetTopWindow(this->GetSafeHwnd());
while (hwnd)
{
    LONG lExStyle;
    lExStyle = GetWindowLong(hwnd, GWL_EXSTYLE);

    if (lExStyle & WS_EX_NOPARENTNOTIFY)
    {
        lExStyle &= ~WS_EX_NOPARENTNOTIFY;
        SetWindowLong(hwnd, GWL_EXSTYLE, lExStyle);
    }

    hwnd = ::GetNextWindow(hwnd, GW_HWNDNEXT);
}
  1. declared and defined function just to see if it is called:
OnParentNotify(UINT message, LPARAM lParam)
{

CDialog::OnParentNotify(message, lParam)
{
    switch (LOWORD(message))
    {
        case WM_CREATE:
        {
            int a = 3;
            int b = 2;
        }
        break;
        case WM_PARENTNOTIFY:
        {
            int c = 1;
            int d = 0;
        }
    }
}

Unfortunately, only WM_CREATE is called once (don't think it is related or correct, since I have 2 buttons that must be added to dialog..so I expect 2 WM_CREATES if it is the case ??).

I am really not sure how to trigger that message to be called. Any tip would be very helpful !

1 Answers

From MSDN documentation:

The system also sends WM_PARENTNOTIFY messages when it creates and destroys a window, but not for controls created from a dialog box template. The system prevents these messages by specifying the WS_EX_NOPARENTNOTIFY style when creating the controls. An application cannot override this default behavior unless it creates its own controls for the dialog box.

According to this, all buttons that are created from dialog template will not receive WM_PARENTNOTIFY. (your code in DerivedDialog::OnInitDialog() has no affect).

The WM_PARENTNOTIFY works if you dynamically create a button (or child control).

Sample (add to your existing code):

  1. Add CButton m_sampleButton member in the dialog header.
  2. Add the creation to OnInitDialog code

    m_sampleButton.Create(L"Sample", WS_CHILD|WS_VISIBLE, CRect(10, 10, 100, 100), this, 10);
    

Edit: (inspired by @Adrian comment)

An alternative solution could be overriding the PreSubclassWindow function of your button's derived class and posting a user-defined message to the parent window.

Button class:

#define CUSTOM_CREATE_NOTIFY WM_USER+1001 // (add to header file)

void CCustomButton::PreSubclassWindow()
{   
    CButton::PreSubclassWindow();

    GetParent()->PostMessage(CUSTOM_CREATE_NOTIFY, (WPARAM)m_hWnd);
}

Dialog class:

// add to message map
ON_MESSAGE(CUSTOM_CREATE_NOTIFY, OnCustomNotify)

LRESULT CMFCApplication2Dlg::OnCustomNotify(WPARAM wParam, LPARAM)
{   
    // wparam is the HWND to the button.

    return 0;
}
Related