I very much like this answer which uses attached properties to mediate the behavior. However, I found that answer's implementation overly complicated, and it also doesn't address the secondary goal of preventing the window from being closed even with Alt+F4. So I offer this alternative:
enum CloseButtonVisibility
{
Visible,
Hidden,
CloseDisabled,
}
static class WindowEx
{
private static readonly CancelEventHandler _cancelCloseHandler = (sender, e) => e.Cancel = true;
public static readonly DependencyProperty CloseButtonVisibilityProperty =
DependencyProperty.RegisterAttached(
"CloseButtonVisibility",
typeof(CloseButtonVisibility),
typeof(WindowEx),
new FrameworkPropertyMetadata(CloseButtonVisibility.Visible, new PropertyChangedCallback(_OnCloseButtonChanged)));
[AttachedPropertyBrowsableForType(typeof(Window))]
public static CloseButtonVisibility GetCloseButtonVisibility(Window obj)
{
return (CloseButtonVisibility)obj.GetValue(CloseButtonVisibilityProperty);
}
[AttachedPropertyBrowsableForType(typeof(Window))]
public static void SetCloseButtonVisibility(Window obj, CloseButtonVisibility value)
{
obj.SetValue(CloseButtonVisibilityProperty, value);
}
private static void _OnCloseButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is Window window))
{
return;
}
if (e.OldValue is CloseButtonVisibility oldVisibility)
{
if (oldVisibility == CloseButtonVisibility.CloseDisabled)
{
window.Closing -= _cancelCloseHandler;
}
}
if (e.NewValue is CloseButtonVisibility newVisibility)
{
if (newVisibility == CloseButtonVisibility.CloseDisabled)
{
window.Closing += _cancelCloseHandler;
}
if (!window.IsLoaded)
{
// NOTE: if the property is set multiple times before the window is loaded,
// the window will wind up with multiple event handlers. But they will all
// set the same value, so this is fine from a functionality point of view.
//
// The handler is never unsubscribed, so there is some nominal overhead there.
// But it would be incredibly unusual for this to be set more than once
// before the window is loaded, and even a handful of delegate instances
// being around that are no longer needed is not really a big deal.
window.Loaded += _ApplyCloseButtonVisibility;
}
else
{
_SetVisibility(window, newVisibility);
}
}
}
#region Win32 imports
private const int GWL_STYLE = -16;
private const int WS_SYSMENU = 0x80000;
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
#endregion
private static void _ApplyCloseButtonVisibility(object sender, RoutedEventArgs e)
{
Window window = (Window)sender;
CloseButtonVisibility visibility = GetCloseButtonVisibility(window);
_SetVisibility(window, visibility);
}
private static void _SetVisibility(Window window, CloseButtonVisibility visibility)
{
var hwnd = new WindowInteropHelper(window).Handle;
if (visibility == CloseButtonVisibility.Visible)
{
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_SYSMENU);
}
else
{
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
}
}
}
This provides three states to choose from:
- Visible
- Hidden, but user can still close using Alt+F4
- Hidden, and closing is disabled completely
Note that by default, a window that is never closed will prevent a WPF program's process from terminating. So if you choose to use the CloseButtonVisibility.CloseDisabled value, you will need to either customize the Application.Run() behavior, or re-enable closing of the window before exiting. E.g. in your main window, you might have something like this:
protected override void OnClosed(EventArgs e)
{
WindowEx.SetCloseButtonVisibility(this.toolWindow.Value, CloseButtonVisibility.Hidden);
this.toolWindow.Value.Close();
base.OnClosed(e);
}
where toolWindow is the Window reference for the window with the close button disabled.
The above assumes that the window is normally just hidden and shown as needed during the normal UI activity. Of course, you can also choose to close the window explicitly at any time, but the same technique — setting the option to not disable closing, and then explicitly closing the window — would still apply.