我正在WPF中写一个模态对话框。我如何设置一个WPF窗口没有关闭按钮?我仍然希望它的WindowState有一个正常的标题栏。
我找到了ResizeMode、WindowState和WindowStyle,但这些属性都不允许我隐藏关闭按钮,而是显示标题栏,就像在模态对话框中一样。
我正在WPF中写一个模态对话框。我如何设置一个WPF窗口没有关闭按钮?我仍然希望它的WindowState有一个正常的标题栏。
我找到了ResizeMode、WindowState和WindowStyle,但这些属性都不允许我隐藏关闭按钮,而是显示标题栏,就像在模态对话框中一样。
当前回答
我非常喜欢这个答案,它使用附加属性来调节行为。然而,我发现答案的实现过于复杂,它也没有解决防止窗口关闭的次要目标,即使使用Alt+F4。所以我提供了另一种选择:
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);
}
}
}
这提供了三种状态可供选择:
可见 隐藏,但用户仍然可以使用Alt+F4关闭 隐藏,关闭是完全禁用的
请注意,默认情况下,永不关闭的窗口将防止WPF程序的进程终止。如果你选择使用CloseButtonVisibility。值CloseDisabled时,您将需要定制Application.Run()行为,或者在退出前重新启用关闭窗口。例如,在你的主窗口中,你可能会有这样的东西:
protected override void OnClosed(EventArgs e)
{
WindowEx.SetCloseButtonVisibility(this.toolWindow.Value, CloseButtonVisibility.Hidden);
this.toolWindow.Value.Close();
base.OnClosed(e);
}
其中toolWindow是关闭按钮被禁用的窗口的窗口引用。
上面假设在正常的UI活动期间,窗口通常只是隐藏并根据需要显示。当然,您也可以选择在任何时候显式地关闭窗口,但是同样的技术—将选项设置为不禁用关闭,然后显式地关闭窗口—仍然适用。
其他回答
在寻找了很多答案之后,我想出了这个简单的解决方法,我将在这里分享,希望它能帮助到其他人。
我设置WindowStyle=0x10000000。
这设置了窗口样式的WS_VISIBLE (0x10000000)和WS_OVERLAPPED (0x0)值。"Overlapped"是显示标题栏和窗口边框的必要值。通过从我的样式值中删除WS_MINIMIZEBOX (0x20000)、WS_MAXIMIZEBOX (0x10000)和WS_SYSMENU (0x80000)值,标题栏中的所有按钮都被删除了,包括关闭按钮。
我刚遇到类似的问题,乔怀特的解决方案似乎对我来说简单而干净。我重用了它,并将其定义为Window的附加属性
public class WindowBehavior
{
private static readonly Type OwnerType = typeof (WindowBehavior);
#region HideCloseButton (attached property)
public static readonly DependencyProperty HideCloseButtonProperty =
DependencyProperty.RegisterAttached(
"HideCloseButton",
typeof (bool),
OwnerType,
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(HideCloseButtonChangedCallback)));
[AttachedPropertyBrowsableForType(typeof(Window))]
public static bool GetHideCloseButton(Window obj) {
return (bool)obj.GetValue(HideCloseButtonProperty);
}
[AttachedPropertyBrowsableForType(typeof(Window))]
public static void SetHideCloseButton(Window obj, bool value) {
obj.SetValue(HideCloseButtonProperty, value);
}
private static void HideCloseButtonChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window == null) return;
var hideCloseButton = (bool)e.NewValue;
if (hideCloseButton && !GetIsHiddenCloseButton(window)) {
if (!window.IsLoaded) {
window.Loaded += HideWhenLoadedDelegate;
}
else {
HideCloseButton(window);
}
SetIsHiddenCloseButton(window, true);
}
else if (!hideCloseButton && GetIsHiddenCloseButton(window)) {
if (!window.IsLoaded) {
window.Loaded -= ShowWhenLoadedDelegate;
}
else {
ShowCloseButton(window);
}
SetIsHiddenCloseButton(window, false);
}
}
#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 readonly RoutedEventHandler HideWhenLoadedDelegate = (sender, args) => {
if (sender is Window == false) return;
var w = (Window)sender;
HideCloseButton(w);
w.Loaded -= HideWhenLoadedDelegate;
};
private static readonly RoutedEventHandler ShowWhenLoadedDelegate = (sender, args) => {
if (sender is Window == false) return;
var w = (Window)sender;
ShowCloseButton(w);
w.Loaded -= ShowWhenLoadedDelegate;
};
private static void HideCloseButton(Window w) {
var hwnd = new WindowInteropHelper(w).Handle;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
}
private static void ShowCloseButton(Window w) {
var hwnd = new WindowInteropHelper(w).Handle;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_SYSMENU);
}
#endregion
#region IsHiddenCloseButton (readonly attached property)
private static readonly DependencyPropertyKey IsHiddenCloseButtonKey =
DependencyProperty.RegisterAttachedReadOnly(
"IsHiddenCloseButton",
typeof (bool),
OwnerType,
new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsHiddenCloseButtonProperty =
IsHiddenCloseButtonKey.DependencyProperty;
[AttachedPropertyBrowsableForType(typeof(Window))]
public static bool GetIsHiddenCloseButton(Window obj) {
return (bool)obj.GetValue(IsHiddenCloseButtonProperty);
}
private static void SetIsHiddenCloseButton(Window obj, bool value) {
obj.SetValue(IsHiddenCloseButtonKey, value);
}
#endregion
}
然后在XAML中,你只需要这样设置:
<Window
x:Class="WafClient.Presentation.Views.SampleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:WafClient.Presentation.Behaviors"
ResizeMode="NoResize"
u:WindowBehavior.HideCloseButton="True">
...
</Window>
下面是关于禁用关闭和最大化/最小化按钮,它实际上并没有删除按钮(但它确实删除了菜单项!)标题栏上的按钮以禁用/灰色状态绘制。(我还没准备好自己接管所有的功能^^)
这与Virgoss解决方案略有不同,因为它删除了菜单项(如果需要的话,还有后面的分隔符),而不是仅仅禁用它们。它不同于Joe Whites的解决方案,因为它没有禁用整个系统菜单,所以,在我的情况下,我可以保留最小化按钮和图标。
下面的代码还支持禁用最大化/最小化按钮,因为与关闭按钮不同,从菜单中删除条目不会导致系统呈现按钮“禁用”,即使删除菜单项会禁用按钮的功能。
这对我很管用。YMMV。
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using Window = System.Windows.Window;
using WindowInteropHelper = System.Windows.Interop.WindowInteropHelper;
using Win32Exception = System.ComponentModel.Win32Exception;
namespace Channelmatter.Guppy
{
public class WindowUtil
{
const int MF_BYCOMMAND = 0x0000;
const int MF_BYPOSITION = 0x0400;
const uint MFT_SEPARATOR = 0x0800;
const uint MIIM_FTYPE = 0x0100;
[DllImport("user32", SetLastError=true)]
private static extern uint RemoveMenu(IntPtr hMenu, uint nPosition, uint wFlags);
[DllImport("user32", SetLastError=true)]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32", SetLastError=true)]
private static extern int GetMenuItemCount(IntPtr hWnd);
[StructLayout(LayoutKind.Sequential)]
public struct MenuItemInfo {
public uint cbSize;
public uint fMask;
public uint fType;
public uint fState;
public uint wID;
public IntPtr hSubMenu;
public IntPtr hbmpChecked;
public IntPtr hbmpUnchecked;
public IntPtr dwItemData; // ULONG_PTR
public IntPtr dwTypeData;
public uint cch;
public IntPtr hbmpItem;
};
[DllImport("user32", SetLastError=true)]
private static extern int GetMenuItemInfo(
IntPtr hMenu, uint uItem,
bool fByPosition, ref MenuItemInfo itemInfo);
public enum MenuCommand : uint
{
SC_CLOSE = 0xF060,
SC_MAXIMIZE = 0xF030,
}
public static void WithSystemMenu (Window win, Action<IntPtr> action) {
var interop = new WindowInteropHelper(win);
IntPtr hMenu = GetSystemMenu(interop.Handle, false);
if (hMenu == IntPtr.Zero) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to get system menu");
} else {
action(hMenu);
}
}
// Removes the menu item for the specific command.
// This will disable and gray the Close button and disable the
// functionality behind the Maximize/Minimuze buttons, but it won't
// gray out the Maximize/Minimize buttons. It will also not stop
// the default Alt+F4 behavior.
public static void RemoveMenuItem (Window win, MenuCommand command) {
WithSystemMenu(win, (hMenu) => {
if (RemoveMenu(hMenu, (uint)command, MF_BYCOMMAND) == 0) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to remove menu item");
}
});
}
public static bool RemoveTrailingSeparator (Window win) {
bool result = false; // Func<...> not in .NET3 :-/
WithSystemMenu(win, (hMenu) => {
result = RemoveTrailingSeparator(hMenu);
});
return result;
}
// Removes the final trailing separator of a menu if it exists.
// Returns true if a separator is removed.
public static bool RemoveTrailingSeparator (IntPtr hMenu) {
int menuItemCount = GetMenuItemCount(hMenu);
if (menuItemCount < 0) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to get menu item count");
}
if (menuItemCount == 0) {
return false;
} else {
uint index = (uint)(menuItemCount - 1);
MenuItemInfo itemInfo = new MenuItemInfo {
cbSize = (uint)Marshal.SizeOf(typeof(MenuItemInfo)),
fMask = MIIM_FTYPE,
};
if (GetMenuItemInfo(hMenu, index, true, ref itemInfo) == 0) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to get menu item info");
}
if (itemInfo.fType == MFT_SEPARATOR) {
if (RemoveMenu(hMenu, index, MF_BYPOSITION) == 0) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to remove menu item");
}
return true;
} else {
return false;
}
}
}
private const int GWL_STYLE = -16;
[Flags]
public enum WindowStyle : int
{
WS_MINIMIZEBOX = 0x00020000,
WS_MAXIMIZEBOX = 0x00010000,
}
// Don't use this version for dealing with pointers
[DllImport("user32", SetLastError=true)]
private static extern int SetWindowLong (IntPtr hWnd, int nIndex, int dwNewLong);
// Don't use this version for dealing with pointers
[DllImport("user32", SetLastError=true)]
private static extern int GetWindowLong (IntPtr hWnd, int nIndex);
public static int AlterWindowStyle (Window win,
WindowStyle orFlags, WindowStyle andNotFlags)
{
var interop = new WindowInteropHelper(win);
int prevStyle = GetWindowLong(interop.Handle, GWL_STYLE);
if (prevStyle == 0) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to get window style");
}
int newStyle = (prevStyle | (int)orFlags) & ~((int)andNotFlags);
if (SetWindowLong(interop.Handle, GWL_STYLE, newStyle) == 0) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to set window style");
}
return prevStyle;
}
public static int DisableMaximizeButton (Window win) {
return AlterWindowStyle(win, 0, WindowStyle.WS_MAXIMIZEBOX);
}
}
}
用法:这必须在初始化源代码后执行。一个好地方是使用窗口的SourceInitialized事件:
Window win = ...; /* the Window :-) */
WindowUtil.DisableMaximizeButton(win);
WindowUtil.RemoveMenuItem(win, WindowUtil.MenuCommand.SC_MAXIMIZE);
WindowUtil.RemoveMenuItem(win, WindowUtil.MenuCommand.SC_CLOSE);
while (WindowUtil.RemoveTrailingSeparator(win))
{
//do it here
}
要禁用Alt+F4功能,简单的方法是连接取消事件,并使用设置一个标志,当你真的想关闭窗口。
将WindowStyle属性设置为None,这将隐藏控制框和标题栏。不需要内核调用。
使用WindowStyle="SingleBorderWindow",这将隐藏WPF窗口的最大和最小按钮。