I'm creating a simple win32 program with one main window and a modeless dialog.
I know that using IsDialogMessage() the program will dispatches messages to modeless window (like keyboard events).
// step 3: The Message Loop
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
if(!IsDialogMessage(g_hToolbar, &Msg))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
But without using IsDialogMessage(), the modeless window still get events like click and some other events dispatched by the mouse.
Why? How can this modeless get these messages if the main loop isn't dispatching messages to it?
I just want to know how it works internally.
IsDialogMessage filters out some of the messages, but allows most messages to hit the TranslateMessage / DispatchMessage part of the message loop and be dispatched normally.
The reason that IsDialogMessage has to process some messages is that the messages were going to be delivered to the wrong window.
Consider - normally - keypress messages are delivered to the control with focus. However, the tab keystroke is meant to move focus to the next control on the dialog.
Rather than making every control have to process tabbing, IsDialogMessage catches tab keystrokes before they are delivered to the actual currently focused control, and ensures that the dialog box code handles the tab logic.
Most other messages - mouse overs and painting and so on - are going to be delivered to the dialog boxes window proc anyway - and so are handled the normal way. Its really just the subset of messages that were destined to be sent to controls, but need to be handled by the dialog box, that IsDialogMessage filters out and processes.
A modal window will disable its parent window, the fact that your HWND is called g_hToolbar tells me that this is not a modal dialog...
Modal dialogs (DialogBox*) create their own message loop (And work like MessageBox etc), you must be talking about modeless dialogs (CreateDialog*) You will get all messages from the window manager without IsDialogMessage, IsDialogMessage performs dialog manager tasks like handling TAB and default button focus.
See this blog post for info about using IsDialogMessage on non dialog windows. See this post series for a great overview about the dialog manager and how to write your own.
Without IsDialogMessage some dialog features, like changing focus when Tab key is pressed, are not working. IsDialogMessage implements these dialog-specific features. If it returns TRUE, current message is already handled, and there is no need to call TranslateMessage and DispatchMessage.
Related
One of the window classes in my cross-platform library is a generic drawing area, accessible through an interface like
struct AreaHandler {
void (*Draw)(AreaHandler *, Area *, DrawParams *);
void (*MouseEvent)(AreaHandler *, Area *, MouseEventParams *);
BOOL (*KeyEvent)(AreaHandler *, Area *, KeyEventParams *);
};
If the KeyEvent() method returns TRUE, the key is considered "handled" and not passed to the system.
For instance, if the user presses the Tab key and KeyEvent() returns TRUE, tabbing between controls should be inhibited and the Area will eat the tab key. But if KeyEvent() returns FALSE, the Tab key should be given to the system, and the system will tab to the next control. Same for F10: if KeyEvent() returns TRUE, the system won't see the F10 and won't activate the menubar.
My current design works like this:
In the message pump, verify that the current focused window is an Area
If it isn't, IsDialogMessage(), TranslateMessage(), and DispatchMessage().
If it is, and the message is not a keyboard message, IsDialogMessage() and DispatchMessage().
Send a special message to the Area to handle the keyboard event, returning whether or not it was handled.
If it was handled, discard the original message.
Otherwise, IsDialogMessage() and DispatchMessage(). The Area window procedure will then simply send the messages to DefWindowProc().
This design works, and handles all cases cleanly, but it means I can't use the Area control in anything that doesn't use my standard message pump. In this case, I refer to dialog boxes.
For the common dialogs, I have to disable every top-level window myself to make sure no messages on my Area can come in. This is both inelegant and doesn't let the dialog manager handle the owner window properly.
For custom dialogs, this means if I want modality, I have to implement modality myself, again disabling every window manually and not allowing an owner/owned relationship.
This is unattractive. I could get rid of a lot of code and be able to do things "the right way" by being able to use the standard dialog modal message pump. But I'm not sure how to do that while keeping the input behavior I want (handle messages as they are retrieved from the pump, choosing whether to send them to IsDialogMessage(), TranslateMessage(), and DefWindowProc() as needed).
I know about WM_GETDLGCODE. That won't work in two ways. If I process the message in WM_GETDLGCODE and handle the event there, I'm going to get an extra window message later I won't know what to do with. Unless there is a way to know tha the message was the one I got before? If I ask for all messages and then process the messages, it'll be too late to give the message back to WM_GETDLGCODE. This means that if the Tab key is pressed, I won't be able to do tab navigation anymore, even if the key event wasn't handled.
And then what about WM_CHAR messages? The standard dialog message pump likeliy has a TranslateMessage() in it, so I'm bound to get those. Is there a reliable way that I can map a WM_CHAR to its source WM_KEYDOWN, assuming the relationship is 1:1? That way I can either discard the message or pass it to DefWindowProc() like I did earlier. But this 1:1 relationship doesn't sound right to me; what about IME?
Would message filters be a solution? I read this post but I'm not sure if the valid use of a message filter stated here is the same as mine.
This is not MFC; I do not have PreTranslateMessage().
Thanks.
For cross-platform parity reasons, my GetOpenFileName() specifies no owner and I explicitly disable all toplevel windows myself. The problem is re-enabling. In order to re-enable these windows correctly, I need to re-enable them before the dialog closes.
In the case of the user choosing a file, this is no issue: I just check for CDN_FILEOK in the hook procedure. No issues, no messed-up focus.
How can I do the same, but for cancelling the dialog box?
I have tried WM_DESTROY in the hook procedure, but that runs after the dialog box has been hidden (too late). I have tried WM_COMMAND, but that doesn't seem to cover all cases. I'm not sure what other options I have.
I need to target Windows XP and newer for now; that also means no Common Item Dialogs. Thanks!
Alternative: if there was a way to do a callback-based GetOpenFileName() that returned control to my message loop, like on Mac OS X with beginSheetModalForWindow:, I could be able to specify a parent window and avoid this hack.
There is no CDN notification when the dialog is canceled. If the user presses the Cancel button, you could try intercepting the BN_CLICKED notification that it sends to the dialog, or even subclass the button itself. But if the user cancels the dialog through other means (clicking the red X, pressing ESC, etc), you will likely have to catch the WM_CLOSE message instead.
I have a WPF window myWindow, which I open using myWindow.ShowDialog() ?? true and listen to the DialogResult (DialogResult = true) to execute some code.
When I set it to either true or false, the window is disposed, is there a way I can prevent this window from closing while also getting the DialogResult? Also, is there a another way I can approach this problem?
What do you want to happen? For example:
You might want a modal dialog (so users can't interact with the rest of the UI while it is visible) but you want code to run in the main program in response to some user action in the dialog. In this case, add events to your dialog that the main program can respond to.
Or you might actually want a modeless dialog, which lets users interact with the rest of the program without completing the dialog. In this case, don't use ShowWindow, just show an owned window.
Surprisingly one can show more than one dialog at a time by putting the ShowDialog() call on the Dispatcher:
uiDispatcher.BeginInvoke(new Func<bool?>(myWindow.ShowDialog));
How come this works and still the UI remains running responding to user interaction once the dialog is shown (I would have thought not since ShowDialog() blocks the thread it is on which has to be the UI thread), one can even go on showing new dialogs:
Window myWindow;
for(int i = 0; i < 5; i ++)
{
myWindow = new Window();
uiDispatcher.BeginInvoke(new Func<bool?>(myWindow.ShowDialog));
}
And the UI is still responsive.
Is there something I should beware of relying on this behaviour? (I want to show one dialog on top of another when some background thread wants to - this works - the only unwanted behaviour seems to be when switching apps sometimes WPF does not know which dialog should be on top - but still allows you to bring one of the dialogs to the front by clicking on it which is unusual for a dialog as clicking outside a dialog is usually not allowed).
UPDATE: One issue I have come across is if you hide one of your dialogs the user can interact with all other Windows again! (not just the other dialogs). See: WPF Dialog not modal?
Showing a dialog does not block the UI thread -- otherwise you won't be able to interact with the dialog.
It merely marks the fact that there is a modal dialog outstanding, and that it should disable inputs to all other non-dialog windows.
If you shuff a ShowDialog call into the dispatcher, the dispatcher will allow an additional dialog to be created because you are not doing something which is prohibited when a modal dialog is outstanding -- which is to input into other non-dialog windows.
Your new dialog is fully functional, because it is a dialog, and you are not trying to input into non-dialog windows.
Switching applications should bring any modal dialog out to the front, but since you have more than one modal dialogs, the system will get confused as to which one should be top-most. I'd suggest you trap the activation event and just manually bring the necessary dialog top-most.
My WinForms application has a button. This button has accelerator key (e.g. Alt+L). When button is pressed I handle the Click event and disable UI to prevent further button clicks until processing is finished. However, when accelerator key is pressed using keyboard those keystrokes are queued and get processed as soon as UI is enabled again. I don't want this. My question is how to clear/flush keyboard buffer?
If I use KeyPress or KeyDown to eat those characters I don't know when they have been received. I only want to suppress old/stale messages that arrived when I was still processing first Click event.
Yes, indeed your theory of the problem is consistent with that proposed by both myself and madmik3 in the comment exchange above. The amount of work your application is doing on the UI thread is effectively blocking it from processing other events, including keystrokes by the user. Those are getting queued for later execution whenever your application finishes its time-consuming foreground task. Those are the perils of a modern-day, pre-emptive multitasking OS. Of course, without posting your actual code, the best I or anyone else can do is speculate about what the problem is, given our experience.
The quick check to confirm that this is actually the case is to toss Application.DoEvents into your processing loop. That will allow the OS to handle the keystrokes immediately, which will all fail because the button has been disabled. (Click events, whether initiated by the mouse or keyboard shortcuts, are not raised for a Button control that has its Enabled property set to "False".) This is the closest you'll get to anything like "flushing the buffers". I doubt you're receiving KeyDown or KeyPress events anyway until after whatever long-running task has completed.
If that fixes the problem, the long-term solution is to spawn a new thread and perform whatever processing you need to do there, instead of on your UI thread. This will prevent you from blocking your UI thread, and, assuming the Button control is correctly disabled, cause the keystrokes to get thrown away because the button they "click" is in a non-clickable state. The simplest way to create a new thread is using the BackgroundWorker component. The documentation contains a pretty good example.