Přestože je technologie WPF asi ta nejlepší volba pro tvorbu desktopových aplikací, je zde horší podpora pokud děláme klasicky vypadající Windows aplikace. Pro modální dialogy zde nejsou všechny možnosti jako u Windows Forms. Proto jsem udělal pomocnou třídu DialogWindow sloužící jako base třída pro okna dialogů.
Podíváme se, jak třída funguje.
Spot the difference?
Ano jedná se o možnost ovládání zobrazení ovládacích tlačítek v titulku dialogu. Standardně ve WPF u okna, když je povoleno měnění velikosti (konkrétně ResizeMode="CanResizeWithGrip"), pak již není umožněno skrýt Maximize a Minimize tlačítka. Také u okna není vůbec umožněno například skrýt ikonu nalevo. Třída DialogWindow zavádí vlastnosti ControlBox, ShowIcon, tak jak jsme na ně zvyklí u Windows Forms. Tlačítka Maximize a Minimize jsou skryté vždy, ty u modálních dialogu nepotřebujeme.
Jak je to udělané? Ačkoliv vnitřek WPF okna a prvky na něm již nepoužívají standardní Win32 API funkce, to neplatí o okně samotném. To je stále tvořeno pomoci Win32 API, a proto můžeme získat jeho Handler, a s ním volat standartní API funkce pro ovládání oken. Handler okna lze ve WPF získat pomoci třídy WindowInteropHelper, na změnu vlastností okna pak použijeme API funkce GetWindowLong, SetWindowLong, SetWindowPos a SendMessage. Změny provedeme v události Loaded okna, kód vypadá takto:
private void DialogWindow_Loaded(object sender, RoutedEventArgs e)
{
const string cFakeIcon = @"AAABAAEAEBACAAAAAACwAAAAFgAAACgAAAAQAAAAIAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////";
IntPtr hWnd = new WindowInteropHelper(this).Handle;
...
UInt32 windowStyleex = GetWindowLong(hWnd, GWL_EXSTYLE);
windowStyleex |= WS_EX_DLGMODALFRAME; //Hide dialog icon if icon is not set (Fixed Dialog style)
SetWindowLong(hWnd, GWL_EXSTYLE, windowStyleex);
if (this.ResizeMode == ResizeMode.CanResize || this.ResizeMode == ResizeMode.CanResizeWithGrip || !this.ControlBox)
{
UInt32 windowStyle = GetWindowLong(hWnd, GWL_STYLE);
//Disable Minimize and Maximize buttons
if (this.ResizeMode == ResizeMode.CanResize || this.ResizeMode == ResizeMode.CanResizeWithGrip)
{
if (originalResizeMode == System.Windows.ResizeMode.CanMinimize)
{
windowStyle = windowStyle & ~WS_MAXIMIZEBOX;
}
else
{
windowStyle = windowStyle & ~WS_MINIMIZEBOX & ~WS_MAXIMIZEBOX;
}
}
//Set ControlBox (hides dialog Icon, Minimize, Maximize and Close buttons)
if (!this.ControlBox)
{
windowStyle = windowStyle & ~WS_SYSMENU;
}
SetWindowLong(hWnd, GWL_STYLE, windowStyle);
}
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
if (!this.ShowIcon)
{
if (this.Icon == null)
{
//Fix hide dialog icon - Set dummy icon and then clear icon
var fakeIconData = new System.IO.MemoryStream(Convert.FromBase64String(cFakeIcon));
this.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(fakeIconData);
this.Icon = null;
}
//Hide icon
SendMessage(hWnd, 0x80, 0, 0);
SendMessage(hWnd, 0x80, 1, 0);
}
}
Celou třídu DialogWindow si můžete prohlédnou nebo stáhnout zde.
Ve třídě je toho řešeno více, tak už jen stručněji:
- Třída mění typ vlastnosti DialogResult na enum System.Windows.MessageBoxResult.
- Ve změněných metodách ShowDialog je navíc automaticky nastavována vlastnost SizeToContent.
- Jsou změněny výchozí nastavení některých vlastností - WindowStartupLocation = WindowStartupLocation.CenterOwner, ShowInTaskbar = false a ResizeMode = ResizeMode.NoResize.
- Je automaticky nastaveno TextOptions.TextFormattingMode="Display" (viz popis zde).
- Poslední změna je o něco složitější, jedná se o změnu border dialogu. U dialogů, kde není povolen resize okna, není ve Windows 7 při vypnuté Aero standardní okraj okna vykreslen tlustým okrajem, ale jen jednoduchým.
Oprava je řešena tak, že border dialogu je zaměněn pomoci nastavení ResizeMode = ResizeMode.CanResize a dále je zaregistrována metoda DialogWindow_WndProc, ve které je potlačen resize dialogu. (Pokud by někdo věděl, jak to řešit jinak, tak mi napište, protože stejný problém mám i u klasických MessageBox dialogů.)
Pro Windows Forms jsem připravil podobnou třídu DialogFormTemplate, která mění výchozí nastavení některých vlastností pro dialog (FormBorderStyle = FormBorderStyle.FixedDialog, MaximizeBox = false, MinimizeBox = false, ShowIcon = false, ShowInTaskbar = false, StartPosition = CenterScreen) a řeší výše popsaný problém s okrajem formuláře. K dispozici je zde.