I have the following C# code in a WPF project:
private static void RunConfig(string owner)
{
long ownerHandle;
var settingsWindow = new SettingsWindow();
if (long.TryParse(owner, out ownerHandle))
{
WindowInteropHelper helper = new WindowInteropHelper(settingsWindow);
helper.Owner = new IntPtr(ownerHandle);
}
settingsWindow.ShowDialog();
}
The SettingsWindow isn't properly modal to the owner window (i.e. I can focus on, interact with, and even close the owner window while the SettingsWindow is still open). What am I doing wrong?
For context, this code is part of a screen saver program, and the owner window is the Control Panel screen saver selection window (which passes in the handle to use as owner via command line parameter). I know the IF statement is evaluating true and correctly parsing the handle.
I have also tried using the SetWindowLongPtr method from user32.dll (compiling for x64, hence not using SetWindowLong) which is briefly described here and shown in use here. This method works in WinForms, but doesn't seem to work here in WPF. Help me Obi-Wan Kenobi, you're my only hope.
It turns out that using WindowInteropHelper to set the native window as owner of the WPF Window does work, it just doesn't do the whole job. When set this way, the WPF Window will remain visible on top of the native window, even if the native window has focus. However, that is the only effect obtained. The WPF Window does not prevent interaction with the native Window, and the native window can even be closed, without the WPF Window closing or being affected.
In order to get the rest of the desired behaviour, we need to use the EnableWindow function in user32.dll to disable the native Window before calling ShowDialog on the WPF Window, and again to re-enable it once the WPF Window closes.
The modified code looks like this:
private static void RunConfig(string owner)
{
long ownerHandle;
var settingsForm = new SettingsWindow();
if (long.TryParse(owner, out ownerHandle))
{
WindowInteropHelper helper = new WindowInteropHelper(settingsForm);
helper.Owner = new IntPtr(ownerHandle);
NativeMethods.EnableWindow(helper.Owner, false);
settingsForm.ShowDialog();
NativeMethods.EnableWindow(helper.Owner, true);
}
else
{
settingsForm.ShowDialog();
}
}
(Note: The above code is correct in general, but incomplete in the case of screen savers, which is what this code is actually being used for. In the case that this code is being used for the config window of a screen saver, the string passed in for the owner handle is not the handle of the Control Panel window to be used as owner, but rather a handle for a control that is a child of the Control Panel window. The extra step in this case is to get the handle of the parent of that control. We can do this by calling GetParent, also in user32.dll, on the passed-in handle. This will return the real handle we want to use for the owner and EnableWindow calls.)
If anyone from Microsoft ever finds this, maybe consider modifying WindowInteropHelper to properly set all of this up when Owner is assigned and ShowDialog used, since this is the proper complete behavior for modal windows.
Related
We have an intermittent problem where an InvalidOperationException is being thrown in the ShowDialog method of a dialog window. The suspect cause is because the dialog is setting its Owner to a window which hasn't been displayed yet. This conclusion is based on the following:
The main application window (intended Owner of the dialog) instantiates and invokes ShowDialog on the dialog during its Loaded handler.
The dialog sets its Owner to the application window during its constructor
The documentation for Window.Owner says it will throw an InvalidOperationException if set to a window that hasn't been displayed yet.
We assume the (main application window) Loaded event may be fired before the window is displayed.
The exception is usually not thrown because the main application window is shown by the time the ShowDialog call is made. When the host system is under stress, the application window "Show" may be delayed, so that when ShowDialog is called, it isn't yet ready to be used as an Owner.
The question is: is this true? If so, what window event or override can be used to reliably fire after it has been displayed, so that the window may then be reliably used as the dialog's Owner, regardless of system conditions?
<Window x:Class="MyApplication.MyMainWindow"
... etc...
Loaded="OnLoaded">
... etc...
</Window>
class MyMainWindow : Window
{
private void OnLoaded(object sender, RoutedEventArgs e)
{
var dialog = new MyDialog(Application.Current.MainWindow);
dialog.ShowWindow();
}
}
class MyDialog: Window
{
public MyDialog(Window window)
{
Owner = window;
}
}
The question is: is this true?
Looks that way from inspecting the code. Programming WPF by Chris Sells & Ian Griffiths also states that the Loaded event is raised just before the window is shown.
What window event or override can be used to reliably fire after it has been displayed, so that the window may then be reliably used as the dialog's Owner, regardless of system conditions?
The ContentRendered event will be fired once, upon showing the window. I think it would be the best choice for your situation.
You can also force a Window's hWnd to be created at any time, though I'm not sure that would be enough to avoid the exception, as a created window isn't necessarily 'shown'. Still, it's a useful thing to know if you ever end up calling Win32 methods related to window management:
new WindowInteropHelper(window).EnsureHandle()
I want to display a MessageBox that is modal to just a single form, and allows other top level windows on the thread to remain active. WinForms disables all windows on the current thread, whether displaying a custom modal form or using the MessageBox class.
I've tried a number of different approaches to solving the problem, including re-enabling any other top level windows (using the Win32 EnableWindow function) and p/invoking the native MessageBox function. Both of these approaches work, but leave keyboard shortcuts inoperable, including tab to move between controls, escape to cancel an open menu and most importantly menu shortcut keys.
I can make menu shortcut keys work by installing a keyboard hook when I display the MessageBox and then directly calling ProcessCmdKey on the active form, but other keyboard shortcuts still don't work. I guess I could press on and add more hacks to make these other cases work, but I was wondering if there was a better way.
Note, this is not about being non-blocking, but about not disabling all windows on the current thread. It happens that the solution may be similar, however.
The basic problem you are battling here is that MessageBox.Show() pumps its own message loop to make itself modal. This message loop is built into Windows and is thoroughly unaware of what the Winforms message loop looks like. So anything special that Winforms does in its message loop just won't happen when you use MessageBox. Which is keyboard handling: detecting mnemonics, implementing navigation and calling methods like ProcessCmdKey() so that a form can implement its own shortcut keystrokes. Not normally a problem since it is supposed to be modal and ignore user input.
The only practical way to revive this is to display the message box on its own thread. This is formally allowed in the winapi, the owner of a window can be a window owned by another thread. But that's a rule that Microsoft did not implement when they added the code to .NET 2.0 that detects threading mistakes. Working around that code requires an IWin32Window as the message box owner that is not also a Control.
Add a new class to your project and paste this code:
using System;
using System.Threading;
using System.Windows.Forms;
public class NonModalMessageBox : IWin32Window {
public NonModalMessageBox(Form owner, Action<IWin32Window> display) {
this.handle = owner.Handle;
var t = new Thread(new ThreadStart(() => display(this)));
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
public IntPtr Handle {
get { return handle; }
}
private IntPtr handle;
}
And use it like this:
new NonModalMessageBox(this, (owner) => {
MessageBox.Show(owner, "Testing", "Non-modal");
});
Where this the Form object that should be disabled while the message box is displayed. There's little I can do to make you feel better about this hack, if the FUD is too overpowering then you really do need to re-implement MessageBox with your own Form class so you can use Show() instead of ShowDialog(). It has been done.
You could pass a reference to the Form, set Enabled property to false when you open the dialog, and set it back to true when the dialog closes.
In my winforms application I am trying to get a main window handle, so I can set it as parent to my wpf modal window. I am not too experienced with winforms, so after a bit of googling I found two ways to get it.
System.Windows.Forms.Application.OpenForms[0].Handle
System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle
(1) seems to always return the same value which appears to be correct (at least my modal window behaves as expected), while (2) sometimes returns the same value as (1), but sometimes - an entirely different pointer, which does not seem to work (my modal window appears on top of every other window, not just the parent window).
Can someone explain the difference between the two methods? Is it normal that sometimes they return different results?
Edit:
In case anyone else is wondering: once you get the handle, you can use it by creating WindowInteropHelper class:
public static void SetInteropParent(this Window wpfDialogWindow, IntPtr winformsParentHandle)
{
new WindowInteropHelper(wpdDialogWindow) { Owner = winformsParentHandle };
}
It is certainly not unusual for Process.MainWindowHandle to return the wrong handle. The Process class has to make a guess at which window is the "main" one. There is no mechanism in the native winapi to designate a window as such. So Process makes a guess that the first window is the main one. This has a knack for going wrong in apps that use a splash screen or a login dialog, etc, or create a window on another thread.
Application.OpenForms doesn't have this problem, but has a failure mode, it will lose track of a window when it is recreated. Which happens when the program changes certain properties of the Form that can only be specified when the window is created. The ShowInTaskbar, TransparencyKey and Opacity properties are the most common troublemakers.
The most reliable way is to override the OnHandleCreated() method of the form you want to be the parent. Which is called whenever the Handle property changes. Do note that you want to make sure that this doesn't happen while your WPF window is active, that will kill the WPF window as well. Otherwise easy to observe of course :)
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
SetWpfInteropParentHandle(this.Handle);
}
I have a c++ windows app calling into a c++/cli library, which is calling into a managed library. The managed library is calling OpenFileDialog.Show with a WPF window parent which is owned by the Win32 window. I haven't found a better way to do this, and all the resources I've read here and searching google and social.msdn recommend doing what I'm doing.
The dialog opens just fine, but when I hit the cancel button, for instance, the app loses focus completely. I'm not sure why it's happening, but I can't seem to make it stop. I've tried a number of different things to no avail.
If I just launch the OpenFileDialog without creating a WPF Window, I don't see the problem.
If I don't set the owner of the WPF Window, I don't see the problem. If I call OpenFileDialog.Show and don't pass the parent, but still create the WPF Window and set its owner, I still see the problem.
I am able to hack it to set the parent app window to foreground after it loses focus, but I would like to not have to.
I have uploaded a small example solution for my scenario that illustrates the problem:
http://dl.dropbox.com/u/26054523/MixedExample.zip
Any help would be appreciated.
Have you tried inverting the hosting scenario? Right now it sounds like you're going unmanaged->bridge->managed->WPF->Winforms. Maybe you could go ...managed->WinForms->WPF using ElementHost http://msdn.microsoft.com/en-us/library/ms742215.aspx
In that way, the WPF window would just be a child control of the WinForms app and that might work out better for focus switches. WinForms controls are not really meant to work directly with WPF apps so well, two different UI threading setups are being used as you've noted.
I know that this is an old post but I think that this is a common problem and I have a good answer. If you have a Win32 window parent window called ParentWindow and a WPF child window called WPFChild you can do this:
using System.Windows.Interop;
void OpenWindow()
{
WPFChildWindow WPFChild = new WPFChildWindow();
WindowInteropHelper helper = new WindowInteropHelper(WPFChild)
{
Owner = new NativeWindowWrapper(ParentWindow.Hwnd).Handle
};
bool? ret = _stepsForm.ShowDialog();
}
This will cause the child window to remain on top of the parent and function as a dialog. Keep in mind that the WPF window does not return a DialogResult but rather a nullable bool.
NativeWindow wrapper is a simple class that takes casts an int as an IntPtr. It's actually from a .net Excel ref edit project located here: How to code a .NET RefEdit ControlT
I have a form which instances a new form that I've already created using
statuswindow statwin = new statuswindow();
statwin.ShowDialog();
return statwin;
This code is in a function. The problem is when I run the function in other code the next line of code doesn't run until the newly instanced window is closed by the user, I'm guessing this is the side-effect of using ShowDialog(), is there a way to have it use the same behaver (being topmost, not being to select the other main window) while still letting the main form's code run?
Update: Although Show(); will work, I need to be able to make the window like a dialog by having it always on top of the other window and it always being the active selected window.
The ShowDialog method of showing a window shows it as a dialog, which is a blocking mechanism within C#/.net. If you want to simply display the window and not cause the running code to block until it is closed, you can use the window.Show() function.
In your code:
startwin.Show();
This will cause the startwin form to become visable to the user, and will fire the startwin.VisibleChanged event as well as the startwin.Load event to fire.
To make the new window always on top, you could set the Topmost window flag in the form properties.
To make the window run in a separate thread, first spawn the thread, then create the window from that thread.
There are also "application modal" and "system modal" Win32 window flags, but I don't know if those are exposed to WinForms or not -- go have a look on the form properties!