Equivalent of OnFinalMessage for MFC windows? - c++

ATL CWindow class has a useful virtual method OnFinalMessage which is called after the last window message for the window is processed - at this point it is safe to destroy or deleted any objects associated with the window. Is there any equivalent for windows derived from the MFC CWnd class?

PostNcDestroy() is what you're looking for.
By the way, if you're implementing a modeless dialog and are looking for where to "delete this;", PostNcDestroy() is the place.

This answer describes how I eventually solved my problem. I'll note that while the answer by John Dibling was helpful, this was not the final solution to my problem. This is because the WM_NC_DESTROY message is sent as the final message to the window, but this can be handled before the last message to the window has finished being handled. See for example http://support.microsoft.com/?kbid=202110 for an explanation of the problem.
DialogProc() is called with WM_CLOSE.
ProcessWindowMessage() calls your WM_CLOSE handler.
In your WM_CLOSE handler, you call DestroyWindow().
This ends up calling DialogProc again with WM_NCDESTROY.
ProcessWindowMessage() calls your WM_NCDESTROY handler.
You call "delete this" in your WM_NCDESTROY handler.
After having called delete this, the object is no longer valid, but you are still technically in the WM_CLOSE handler so you will probably crash when you eventually get back there. This means it is not really safe to assume that you can do delete this in PostNcDestroy, since the object may still be live in some other stack frame.
///
/// A window designed to allow any window to use the "OnFinalMessage" method from the ATL CWindow class
/// You must call SubclassWindow for this instance so that the window procedure runs
template<class T>
class FinalMessageWindow : public CWindowImpl<FinalMessageWindow<T> >
{
T *_t; /// The object wanting to receive the final message notification
public:
BEGIN_MSG_MAP(FinalMessageWindow<T>)
END_MSG_MAP()
///
/// The constructor
/// \param t The object that wants to get the OnFinalMessage notification
FinalMessageWindow(T *t)
: _t(t)
{
}
///
/// Called when the final window message for the window has been processed - this is often a good time to delete the object
/// \param hWnd The window handle
virtual void OnFinalMessage(HWND hWnd)
{
_t->OnFinalMessage(hWnd);
}
};
I created the above class, note that it is derived from the ATL CWindow class - this allows me to use the OnFinalMessage handler for this class. The OnFinalMessage handler is different from the PostNcDestroy in MFC windows in that it is guaranteed to be called only after the final message handler on the stack has completed.
We then use window subclassing to insert this window as the window procedure for my own window:
// roughly speaking
FinalMessageWindow<MyWindow> _finalMessageWindow(this);
finalMessageWindow.SubclassWindow(m_hWnd);
Then we implement the OnFinalMessage handler for our window:
void MyWindow::OnFinalMessage(HWND hWnd)
{
delete this;
}

Related

MFC: Notification after a control is created

I'm new to MFC. I chose to create an office style MFC app through the wizard in VS2017. I now want to extend CMFCShellTreeCtrl so I created another class with that as the base class. The basics are fine. My issue is that I want to do something like:
whatever MyClass::FuncitonCalledAfterControlCreated(...)
{
SetFlags(GetFlags() | SHCONTF_NONFOLDERS);
ModifyStyle(0x0, TVS_CHECKBOXES);
}
But I'm having trouble figuring out what virtual function to override or am I supposed to do one of those message mapping things? I would guess that whatever it is, it would be common to all controls? Anyway, what would be the appropriate function?
TIA!!
If control is derived from CWnd a WM_CREATE is issued which can be directed to the control via a message map of:
ON_WM_CREATE()
And member function:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
If on a dialog resource the WM_CREATE won't occur. Some say you can use PreSubClassWindow but on a case of testing Create(), that call comes BEFORE the CreateWindowEx call so won't work for setting the TVS_CHECKBOX style. I didn't try a CDialog with a tree control and check the call stack.

MFC: Didn't get EN_CHANGE message within derived CEdit

I see very strange behavior of CEdit. In my derived class from CEdit control I need to catch text change event. I do it via ON_CONTROL_REFLECT_EX
class CSomeDerivedEdit : public CEdit
{
DECLARE_DYNAMIC(CSomeDerivedEdit )
public:
CSearchEditCtrl();
protected:
DECLARE_MESSAGE_MAP()
afx_msg BOOL OnEnChange();
...
};
in cpp
IMPLEMENT_DYNAMIC(CSomeDerivedEdit , CEdit)
BEGIN_MESSAGE_MAP(CSomeDerivedEdit , CEdit)
ON_CONTROL_REFLECT_EX(EN_CHANGE, &CSomeDerivedEdit::OnEnChange)
END_MESSAGE_MAP()
This control I create within some list control as child window, when I create it and pass as parent window pointer to list control everything working fine and I get EN_CHANGE events but when I pass as parent window header control of list control events didn't resive.
m_someEdit.Create( WS_CHILDWINDOW|WS_VISIBLE, rcRect, this, IDC_EDIT); here everything fine
m_someEdit.Create( WS_CHILDWINDOW|WS_VISIBLE, rcRect, GetHeaderCtrl(), IDC_EDIT); here also control create fine and I see it , but on_control_reflect didn't called when I type in edit control.
ON_CONTROL_REFLECT_EX can only work, if the receiver of the WM_COMMAND message is also subclassed in the MFC. If it is a pure windows control and not subclassed with the MFC the WM_COMMAND message is never reflected to any child.
Remember: The reflection works, because the MFC first handles a WM_COMMAND message from a child in the parent first and offers it back to the child control. And if not handled the WM_COMMAND message is handled inside the parent. Standard window controls doesn't know reflection and always handle WM_COMMAND messages by itself....
So if the header control is not subclassed by the MFC the reflection will not work.
I think you should read the MSDN. When you derive the class CEdit, you should put the ON_EN_CHANGE message handler between BEGIN_MESSAGE_MAP() and END_MESSAGE_MAP() macros. On the other hand, the OnEnChange function, whose declaration is:
afx_msg void OnEnChange();
does not return a BOOL value. The ON_EN_CHANGE message handler should be like this (from MSDN):
BEGIN_MESSAGE_MAP()
ON_EN_CHANGE(ID_OF_THIS_CONTROL,OnEnChange)
END_MESSAGE_MAP()
Hope this will help you.

ON_NOTIFY not working in my dialog when I have ON_NOTIFY_REFLECT defined by the control

In my CTreeCtrl derived class, I am acting on TVN_ITEMEXPANDED:
ON_NOTIFY_REFLECT(TVN_ITEMEXPANDED, &OnTVNItemExpanded)
In the control's parent dialog, I also want to act upon the same notification, TVN_ITEMEXPANDED,
ON_NOTIFY(TVN_ITEMEXPANDED, IDC_ELEMENT_TREE, &OnTVNItemExpanded)
However, only the control class's OnTVNItemExpanded method is getting called, never my dialog's. I am using both breakpoints and seeing the desired behavior (or lack of desired behavior) in both methods to verify that only the control class's method is being called, not my dialog's method.
BUT, if I comment out the ON_NOTIFY_REFLECT from my CTreeCtrl-derived BEGIN_MESSAGE_MAP, then my dialog's method gets called!?!
Why can't the notification go both to my control and to my dialog?!?
ON_NOTIFY_REFLECT overrides ON_NOTIFY, but you can use ON_NOTIFY_REFLECT_EX instead which lets your callback decide if the message should go through to the parent or not.
See Message Reflection for Windows Controls for a more detailed explanation:
If, in your parent window class, you supply a handler for a specific
WM_NOTIFY message or a range of WM_NOTIFY messages, your handler will
be called only if the child control sending those messages does not
have a reflected message handler through ON_NOTIFY_REFLECT(). If you
use ON_NOTIFY_REFLECT_EX() in your message map, your message handler
may or may not allow the parent window to handle the message. If the
handler returns FALSE, the message will be handled by the parent as
well, while a call that returns TRUE does not allow the parent to
handle it. Note that the reflected message is handled before the
notification message.

CListCtrl class override and OnTimer

I'm not sure if I'm doing something undocumented. I created my own class derived from CListCtrl and then overrode the OnTimer handler in it:
void CListCtrl2::OnTimer(UINT_PTR nIDEvent)
{
// TODO: Add your message handler code here and/or call default
switch(nIDEvent)
{
case MY_TIMER_ID:
{
//Do my processing
doMyProcessing();
}
break;
default:
{
//Default
CListCtrl::OnTimer(nIDEvent);
}
break;
}
}
But what seems strange to me is that this OnTimer() routine is called with timer IDs that are not mine. For instance, just from a quick debugger checkpoint research it turns out that my default handler is called with nIDEvent set to 45 and 43.
Are there some timer IDs that are reserved that I should avoid using myself?
From the CListCtrl documentation we see this text:
Also see:
Knowledge Base article Q200054: PRB: OnTimer() Is Not Called Repeatedly for a List Control
And from that article, some pertinent excerpts:
If you call the SetTimer function to send periodic WM_TIMER messages
to a list control, you may find that the WM_TIMER message handler (the
OnTimer function) for a list control is called only twice.
....
The list control uses the timer for editing labels, and for scrolling.
When you handle the timer message, if the timer ID is your own timer,
don't call the default handler (CListCtrl::OnTimer).
So, this confirms what you observe. The list control uses the timer. I can find no documentation for the specific IDs that are used. I guess that Microsoft would not wish to commit to documenting the specific IDs that were used. They would regard the control's implementation as private and would wish to retain the option of using more timer IDs in future versions. But as IInspectable points out, they could have done that by reserving a range of IDs.
My recommendation is to regard the list control's timer as out of bounds, and reserved for use by the control. For your derived class, use a different timer. Create a message only window and use it to receive timer events. You can subclass CWnd to achieve this.

Is there any function called after the OnInitDialog function in MFC?

I want to create a thread after the creation of a dialog box in MFC. Is there any function that Windows has provided and is automatically called after OnInitDialog so that I can create my thread inside it?
You can simply create your thread in the OnInitDialog function. There's no reason to overcomplicate things by going and searching for a different function, or splitting your initialization code up in two pieces. (There also isn't any such function, because there's no corresponding Windows message that is sent.)
If you want to get your dialog box on the screen before you create the thread, you can just show it manually using the ShowWindow function. For example:
ShowWindow(SW_SHOW);
RedrawWindow();
Also see this post by Raymond Chen: Waiting until the dialog box is displayed before doing something
OnInitDialog() is the main function called upon initialization (in reaction to WM_CREATE).
Why can't you create your thread in there?
I have changed the thread priority to below normal and when the thread executes for the first time I set the thread to normal priory. This works fine. Thanks for your response.
After many years of feeling unsatisifed with the OnTimer solution to draw first view graphics in an MFC dialog app (a favorite playground), this seemed like a nice simple solution:-
Add a WM_HSCROLL handler with class wizard.
At the end of OnInitDialog post a hscroll message with a NULL LPARAM
In handler detect the NULL, draw graphics.
a timer meant that the app was alive for some time before graphics happened, and apparently hscroll is prioritized to happen just after the WM_PAINT message which would erase a picture element to its blank state, deleting anything that was drawn during initdialog.
BOOL CSpecDlg::OnInitDialog()
{
...
PostMessage(WM_HSCROLL,0, (LPARAM)NULL);
return TRUE; // return TRUE unless you set the focus to a control
}
void CSpecDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
if (pScrollBar==NULL)
{
plot();
}
CDialogEx::OnHScroll(nSBCode, nPos, pScrollBar);
}