I’m listening to global win events for cases such as object destroyed, hidden, shown, created etc…
However I’d like to do certain things based on what that handle (HWND) is. For example did a button just get hidden or destroyed or did a window? Was it a child window that got hidden or parent? The last question regards to seemingly getting an event raised for every object in the parent window when parent window closes which is too much noise. Trying to make sense of what raised the event.
https://learn.microsoft.com/en-us/windows/win32/winauto/what-are-winevents
Windows offers a rich set of functions for interrogating an HWND. For example (error checking omitted for brevity):
did a button just get hidden or destroyed or did a window?
TCHAR classname [32];
GetClassName (hWnd, classname, 32);
if (_tcscmp (classname, __T ("BUTTON")) == 0)
...
Was it a child window that got hidden or parent?
LONG dwStyle = GetWindowLong (hWnd, GWL_STYLE);
if (dwStyle & WS_CHILD)
...
an event [is] raised for every object in the parent window when parent window closes which is too much noise.
I think you are stuck with this. When a parent window is destroyed, it automatically destroys all its children, and each of those will send the corresponding event(s) as a result.
Related
What's the point of having the WM_CREATE message when you can create windows without it.
Calling void CreateWindowA outside of WM_CREATE works so what's the deal?
WM_CREATE message is received by window procedure when window is created and not shown yet. You can prepare initial state. For example, you can create controls (child windows), set default values for controls, etc. If something is wrong, you can cancel creation of window and it will not be shown. In other words, in WM_CREATE you can add custom extension to CreateWindow API.
There are many reasons why an application could want/need to intercept the WM_CREATE message. For example, you may want to check a for a particular condition, and prevent the actual creation if that condition is wrong: you can do this by returning -1 from the WndProc that handles the message (see the documentation):
If an application processes this message, it should return zero to
continue creation of the window. If the application returns –1, the
window is destroyed and the CreateWindowEx or CreateWindow function
returns a NULL handle.
What's the point of having the WM_CREATE message when you can create
windows without it.
If you don't handle WM_CREATE message explicitly, it is actually handled by system via DefWindowProc(hwnd, uMsg, wParam, lParam); implicitly. So you see the window visible.
WM_CREATE message is sent before the CreateWindowEx or CreateWindow function returns, that give you an opportunity to intervene the result of mentioned function. You can allow (return zero to continue creation of the window) or prevent (returns –1, the window is destroyed) the window to be visible, and how it will looks like.
The WM_NCCREATE and WM_CREATE message are sent before the window
becomes visible. That makes them a good place to initialize your
UI—for example, to determine the initial layout of the window.
Refer to "Managing Application State", "WM_CREATE message".
Can I create a button without a parent in WINAPI?
I tried doing:
CreateWindowEx(0, "Button", "BTN", WS_POPUP | BS_PUSHBUTTON, 0, 0, 15, 15, nullptr, nullptr, nullptr, nullptr);
then setting the parent to a specified window later on and also showing the button using ShowWindow. This indeed created a fine looking button.
However, the button has no ID and cannot be Identified in WM_COMMAND because the ID is 0.. If two buttons were parentless, there'd be no way to tell them apart. Now if I give it an ID through the HMENU parameter:
CreateWindowEx(0, "Button", "BTN", WS_POPUP | BS_PUSHBUTTON, 0, 0, 15, 15, nullptr, 15, nullptr, nullptr);
GetLastError() prints "Invalid Menu Handle" and the button will not be created.
If I give it no parent and WS_CHILD, it will say cannot create a top level child window which is understandable.
So what I did was I set the Parent to GetDesktopWindow() and give the button an ID. That works but the button isn't parentless..
So is there a way to give a button an ID (So as to identify it in WM_COMMAND) and at the same time, have its parent NULL so that I can set the parent later? How does Windows Forms do it? The buttons can be parentless until you do Form.add(ButtonName);
Can the same effect be achieved in WINAPI?
This is appears on its face to be a very silly sort of question.
Button controls are by definition child controls, so the call to the CreateWindowEx function you use to create the button should also be specifying the WS_CHILD style.
Of course, as you mention, you cannot create a child control with no parent; you'll get an error. There is no such thing as a top-level child window.
So then, the answer to the initial question
Can I create a button without a parent in WINAPI?
is clearly no. Buttons are child controls, and all child controls must have a parent.
Just because Windows let you get away with specifying the WS_POPUP flag when you create a button control doesn't mean that it's a valid combination.
I strongly recommend re-reading the documentation for the CreateWindowEx function. In particular, note that the hMenu parameter is overloaded with respect to its meaning. If you are creating an overlapped or pop-up window (WS_OVERLAPPED or WS_POPUP), it specifies a handle to a menu. If you're creating a child window (WS_CHILD), it specifies the identifier of the child window. The fact that the same parameter is used for both things, depending on the style of the window, should tell you something.
How does Windows Forms do it? The buttons can be parentless until you do Form.add(ButtonName);
They most certainly cannot. The button controls are not created until you add them to a form or other parent control. The System.Windows.Forms.Button class constructor does not create a Win32 window. It just holds a collection of necessary styles used to create the underlying Win32 window when appropriate.
You could, of course, do the same thing by writing a C++ Button class. A simple implementation would just have member variables corresponding to the parameters of CreateWindowEx and a Create member function that would actually call CreateWindowEx to create the Win32 window once all of the members had been set. The Create method could throw an exception if one of the necessary members had not yet been set to a valid value.
I solved it. I had to pass HWND_MESSAGE as the Parent Parameter. When you call SetParent, that parameter gets changed to the Parent's handle and all is well.
No, this really is not a "solution" to the problem. As kero points out, you've simply set the button control's parent to the message-only window. Again, this might appear to work, but it's a rather strange thing to do and I hardly recommend it as a solution.
If you really want to hack it, I recommend creating your own hidden top-level window to use as a parent for your "unparented" child controls. Then you could use the same trick of calling SetParent to reparent them.
I solved it. I had to pass HWND_MESSAGE as the Parent Parameter. When
you call SetParent, that parameter gets changed to the Parent's handle
and all is well.
No, you didn't get "Parent-Less Button": the parent window of your message-only button is the "main" message-only window (class "Message").
HWND button = CreateWindowEx(0, "BUTTON", ...);
SetFocus(button); // Button no get focus! :(
Also, I have other controls on my form that I am able to SetFocus() to.
Thanks, Martin
It has been FOREVER since I've had to do this, but...
Were this a dialog, I would tell you to send a WM_NEXTDLGCTL via PostMessage(). The default dialog item message handler would take care of the rest for you setting keyboard focus and selection activation. However, this is a different case if I read this correctly. You're creating both parent and child windows raw on the fly. If this is the case, SetFocus() to the parent window, and handle WM_SETFOCUS on the parent window by bringing it to top, then setting focus on the child window. WM_SETFOCUS, and WM_KILLFOCUS were designed to allow you to switch the 'activated' state of your controls, and most handle it for you (unless your window is an owner draw control or some such). But in a raw window, when your base parent window is sent the focus, you need to appropriately ensure the proper child has it if you're hosting any (think of it as managing your own 'dialog'). Again, normally this is done by the default dialog procedure for you if this were a dialog, but being raw windows you're kind of stuck managing it all yourself.
Though I can't imagine how, I hope that helped somewhat.
SetFocus is a function, not a procedure. Call it as a function and check its returned value. Either the retuned value is null because you made an error in the CreateWindowEx() call and "button" isn't a valid handle or it's a window not associated with your thread's message queue, or the return value is not null (it's now the prior focused window's handle) and you do have the focus (but are somehow failing to detect it).
Try setting the WS_TABSTOP style on the button.
If you create that button in respond of the WM_INITDIALOG message you should return FALSE to prevent dialog box procedure to change the focus.
(title updated)
Following on from this question, now I have a clearer picture what's going on...
I have a MFC application with no main window, which exposes an API to create dialogs. When I call some of these methods repeatedly, the dialogs created are parented to each other instead of all being parented to the desktop... I have no idea why.
But anyway even after creation, I am unable to change the parent back to NULL or CWnd::GetDesktopWindow()... if I call SetParent followed by GetParent, nothing has changed.
So apart from the really weird question of why Windows is magically parenting each dialog to the last one created, is there anything I'm missing to be able to set these windows as children of the desktop?
UPDATED: I have found the reason for all this, but not the solution. From my dialog constructor, we end up in:
BOOL CDialog::CreateIndirect(LPCDLGTEMPLATE lpDialogTemplate, CWnd* pParentWnd,
void* lpDialogInit, HINSTANCE hInst)
{
ASSERT(lpDialogTemplate != NULL);
if (pParentWnd == NULL)
pParentWnd = AfxGetMainWnd();
m_lpDialogInit = lpDialogInit;
return CreateDlgIndirect(lpDialogTemplate, pParentWnd, hInst);
}
Note: if (pParentWnd == NULL)pParentWnd = AfxGetMainWnd();
The call-stack from my dialog constructor looks like this:
mfc80d.dll!CDialog::CreateIndirect(const DLGTEMPLATE * lpDialogTemplate=0x005931a8, CWnd * pParentWnd=0x00000000, void * lpDialogInit=0x00000000, HINSTANCE__ * hInst=0x00400000)
mfc80d.dll!CDialog::CreateIndirect(void * hDialogTemplate=0x005931a8, CWnd * pParentWnd=0x00000000, HINSTANCE__ * hInst=0x00400000)
mfc80d.dll!CDialog::Create(const char * lpszTemplateName=0x0000009d, CWnd * pParentWnd=0x00000000)
mfc80d.dll!CDialog::Create(unsigned int nIDTemplate=157, CWnd * pParentWnd=0x00000000)
MyApp.exe!CMyDlg::CMyDlg(CWnd * pParent=0x00000000)
Running in the debugger, if I manually change pParentWnd back to 0 in CDialog::CreateIndirect, everything works fine... but how do I stop it happening in the first place?
Some thoughts:
First, you are passing NULL for the parent window the whole way through the chain. Its becomming non NULL when MFC tries to find your applications main window.
As I see it you have two mitigations:
Create a CWnd from the desktop window. CWnd::GetDesktopWindow will give you a non NULL window to use as a parent window that will inhibit the AfxGetMainWnd call.
Or trace into AfxGetMainWnd, find out where it is reading the main window from, and see if there is some setting to prevent it finding your dialog window.
On a final note. The MFC terminology is unfortunate :- On Windows, only child windows have parent windows. Popup or desktop windows have owner windows. CreateWindow takes a single parameter that accepts the owner, or parent of the window being created. The distinction is important because while a parent window can be changed, an owner cannot. SetParent will NOT change the owner window for a popup or overlapped window.
OK, found it!
There were actually two problems. I was passing NULL as the parent/owner... but trying to pass CWnd::GetDesktopWindow() was not helping so I'd given up on the idea until finding the behaviour of CDialog::CreateIndirect. That made me take a closer look at my code, and I finally spotted that MyDialog::MyDialog(CWnd *pParent) was calling super::Create(NULL), not super::Create(pParent)... because we'd always passed it NULL before anyway the bug had never been apparent.
So yet again, the hard problem turned out to be only one step away from a typo!
MFC can only create one window at a time IIRC. In the dark and distant past at least, when MFC creates a Win32 Window it needs to associate the MFC CWnd instance with the window. Because the first message a window receives is NOT the WM_CREATE message with the LPVUSERDATA parameter MFC stored the CWnd instance in a thread local variable - in the not unreasonable expectation that the next call to CWnd::WindowProc would be for the window it started trying to create.
I have no idea how one could actually write code to subvert this process. It all hinges on how you could be creating windows at differing velocities.
In my native windows mobile app I've got a window that creates a dialog. Lets say my window handle is hMainWnd.
I create the dialog using DialogBoxParam() and passing in hMainWnd as the dialog's parent:
DialogBoxParam(_,_,hMainWnd,_,_);
Let's say the dialog's handle is hDlgWnd. From within the dialog, GetParent() returns hMainWnd as expected:
//We're inside the dialog created above
HWND hParent = GetParent(hDlgWnd); //hParent == hMainWnd
Here's the odd thing, calling GetWindow() to find the children of hMainWnd returns NULL, signifying that it has no children. I would expect the function to return hDlgWnd
//We're inside the main window
HWND hChild = GetWindow(hMainWnd, GW_CHILD); //hChild == NULL
How can a child know its parent when the parent doesn't know its child?
GetWindow with GW_CHILD apparently does not retrieve descendant windows, only child windows. From MSDN:
The retrieved handle identifies the
child window at the top of the Z
order, if the specified window is a
parent window; otherwise, the
retrieved handle is NULL. The function
examines only child windows of the
specified window. It does not examine
descendant windows
What is the difference between a child and a descendant? I don't know but EnumChildWindows might get you what you need.
A Window is either a child window (has WS_CHILD style) or is a top-level window.
Top-level windows have no parent window, but they might have an owner window. Child windows have a parent, but no owner.
The GetParent( ) function return the parent of a child window or the owner of a top-level window. Hence, it is misnamed -- it should be called GetParentOrOwner().
So what is happening is that you are getting the owner of your top-level window from GetParent(), but since it isn't actually the parent, hDlgWnd isn't hMainWnd's child.