A WPF Dialog Window
I’m continuing to work through learning more about WPF and XAML. I was trying a simple dialog and noticed that it didn’t look like typical Windows dialogs… for example, all WPF windows have an icon, but if you look at dialogs in most apps (and what WinForms produces), they don’t have a visible icon and system menu.
At first, I thought I’d missed something pretty obvious but after a several web searches and questions to some folks, it turns that’s the default window behavior in WPF. So, I decided to put together a class to expose what a typical dialog looks like.
First, the DialogWindow class derives from Window class. And, it exposes a couple of new dependency properties to control a couple of dialog behaviors:
· IsSystemMenuVisible – to show/hide the system menu.
· IsHelpButtonVisible – for context help button on dialog.
Then, we override OnSourceInitialized to set additional window styles when this window is being initialized.
/// <summary>
/// Sets the appropriate window style for the dialog window based on the
/// IsSystemMenuVisible and IsHelpButtonVisible properties.
///
/// Note: this makes several calls into native Windows methods to deliver
/// this functionality. NativeMethods is a wrapper for native Windows
/// calls.
/// </summary>
protected virtual void SetWindowStyle()
{
// Gets a window handle for this dialog window.
WindowInteropHelper wih = new WindowInteropHelper(this);
IntPtr hwnd = wih.Handle;
// Gets the current windows StyleEx value.
int windowStyle = NativeMethods.GetWindowLongPtr(hwnd, NativeMethods.GWL_EXSTYLE).ToInt32();
// Turns modal dialog frame on/off depending on whether we want to show
// the system menu.
if (IsSystemMenuVisible)
{
windowStyle &= ~NativeMethods.WS_EX_DLGMODALFRAME;
}
else
{
windowStyle |= NativeMethods.WS_EX_DLGMODALFRAME;
}
// Turns context help on/off for the dialog depending is we want it shown.
if (IsHelpButtonVisible)
{
windowStyle |= NativeMethods.WS_EX_CONTEXTHELP;
}
else
{
windowStyle &= ~NativeMethods.WS_EX_CONTEXTHELP;
}
// Now, sets the new windows StyleEx value.
NativeMethods.SetWindowLongPtr(hwnd, NativeMethods.GWL_EXSTYLE, new IntPtr(windowStyle));
if (!IsSystemMenuVisible && this.ResizeMode == ResizeMode.NoResize)
{
// Note: this is a workaround for a WPF bug. When NoResize is chosen,
// the system menu doesn't get set up correctly. The code below disables
// the appropriate system menu items in this case.
IntPtr hmenu = NativeMethods.GetSystemMenu(hwnd, false);
NativeMethods.EnableMenuItem(hmenu, NativeMethods.SysMenuPos_Maximize,
NativeMethods.MF_DISABLE | NativeMethods.MF_BYPOSITION);
NativeMethods.EnableMenuItem(hmenu, NativeMethods.SysMenuPos_Minimize,
NativeMethods.MF_DISABLE | NativeMethods.MF_BYPOSITION);
NativeMethods.EnableMenuItem(hmenu, NativeMethods.SysMenuPos_Size,
NativeMethods.MF_DISABLE | NativeMethods.MF_BYPOSITION);
NativeMethods.EnableMenuItem(hmenu, NativeMethods.SysMenuPos_Restore,
NativeMethods.MF_DISABLE | NativeMethods.MF_BYPOSITION);
NativeMethods.DrawMenuBar(hwnd);
}
}
The bulk of the interesting code is in the SetWindowStyle method. This method makes extensive use of calls to unmanaged code to expose the functionality we want. If we don’t want to show the system menu, we turn on the WS_EX_DLGMODALFRAME (this gets rid of the icon at the top left of the dialog window). I also tried turning off the WS_SYSMENU style, but this not only got rid of the system menu icon, but also gets rid of the dialog’s Close button.
Then, if we want the help button to show up, we turn on the WS_EX_CONTEXTHELP flag.
Even though the system menu icon isn’t shown, the system menu itself will still come up – when a user right clicks on the dialog title bar, or press ALT+SPACE. This is typical dialog behavior. However, in this case in WPF, there appears to be an issue, because that system menu comes up but the menu items aren’t always in the right state. Specifically when the ResizeMode is set to NoResize, the sizing system menu items are still enabled… this isn’t right. So the remainder of the code in SetWindowStyle, disables the appropriate system menu items.
Since I use quite a few native Windows APIs to make this work, I implemented a NativeMethods class that wraps the entire managed-native interop. And, I got most of this code from pinvoke.net (it’s very helpful for this kind of thing, in case you aren’t already familiar with the website).
There you have it… A dialog window that behaves like most Windows dialogs. It’s easy enough to create one of these dialogs:
<my:DialogWindow x:Class="WpfHelpersTestApp.TestDialog"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:WpfHelpers;assembly=WpfHelpers"
Title="TestDialog" Height="200" Width="200" ResizeMode="NoResize" ShowInTaskbar="False">
<Grid>
</Grid>
</my:DialogWindow>
TestDialog test = new TestDialog();
test.IsHelpButtonVisible = true;
test.ShowDialog();
Notice that you can use XAML to create an instance of the new DialogWindow class, rather than the default Window that is usually generated. And, this generates the appropriate code for you. This is definitely cooler than what you would have to do in WinForms.
Comments
- Anonymous
April 27, 2009
PingBack from http://asp-net-hosting.simplynetdev.com/a-wpf-dialog-window/