如何在WPF窗口中隐藏关闭按钮?

233

我正在WPF中编写一个模态对话框。如何设置WPF窗口没有关闭按钮?我仍希望它的WindowState具有正常的标题栏。

我找到了ResizeModeWindowStateWindowStyle,但这些属性都不能隐藏关闭按钮,同时显示标题栏,就像模态对话框一样。


9
这是一个运行于后台线程的进度对话框,不支持取消操作;我猜我只是想让它不必支持取消(至少现在不需要)。不过你可能是对的。 - Michael Hedgpeth
1
我也讨厌应用程序试图移除窗口装饰。如果我制作进度对话框,我总是使窗口关闭按钮执行与单击实际取消按钮相同的逻辑。 - Christian Hayter
13
针对 Chris:假设你的软件用于视频监控。夜间,一名安保人员(这是他的工作)需要保持窗户开着... 但有时候他们的工作很无聊,想上网冲浪或者因为任何原因关闭视频矩阵的窗口,去除窗口按钮是正确的做法。 - Jean-Marie
7
“为什么你想这样做?我认为这是非常糟糕的用户界面设计。” - 当程序显示一个对话框,只有“确定”,“取消”和“关闭”按钮时,称其为非常糟糕的用户界面设计。对于用户来说,“关闭”按钮可能不够明显。它是“取消”还是“提交”? 共识是在对话框中不包括关闭按钮 - user585968
2
@Jean-Marie 隐藏关闭按钮并不能防止这种情况发生,它只是愚弄了那些不知情和懒惰(去谷歌搜索)的人。隐藏关闭按钮只能防止点击该按钮。Win键和Alt键组合仍将像往常一样工作。“正确”的方法是为工人创建一个用户帐户,并使用组策略防止他们打开/安装除批准软件以外的任何软件。然后有一个管理员帐户,监管人员可以访问,以处理任何维护问题。 - Digital_Utopia
显示剩余2条评论
24个回答

296

WPF没有内置的属性来隐藏标题栏的关闭按钮,但是您可以使用几行P/Invoke代码实现。

首先,在您的窗口类中添加以下声明:

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);

然后将这段代码放在窗口的 Loaded 事件中:

var hwnd = new WindowInteropHelper(this).Handle;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);

这就是实现没有关闭按钮的方法。标题栏左侧也将不再有窗口图标,这意味着没有系统菜单,即使右键单击标题栏也没有。

重要提示:这只是隐藏了按钮。用户仍然可以关闭窗口!如果用户按下Alt+F4,或通过任务栏关闭应用程序,则窗口仍将关闭。

如果您不想在后台线程完成之前允许窗口关闭,那么您还可以覆盖 OnClosing 并将 Cancel 设置为 true,正如 Gabe 建议的那样。


5
根据文档,我们应该使用 SetWindowLongPtr 代替。 - Jonathan Allen
15
注意事项:主要是提醒自己... DllImport的命名空间为-> System.Runtime.InteropServices.DllImport。 WindowInteropHelper的命名空间为-> System.Windows.Interop.WindowInteropHelper。 - doobop
4
这种方法实际上会隐藏所有三个按钮(最小化、最大化和关闭)。是否可以只隐藏关闭按钮? - newman
4
@miliu,不行。你可以禁用它,但是如果同时隐藏最小化/最大化按钮,你将无法隐藏它。我猜想Windows开发人员认为,如果最大化按钮在通常关闭按钮的右边,这可能会让人感到困惑。 - Joe White
6
在XAML文件中的Window标签上添加WindowStyle="None"。 - diegodsp
显示剩余6条评论

102

我遇到了类似的问题,Joe White 的解决方案 看起来很简单清晰。我将其重用并将其定义为 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>

2
应该是 window.Loaded += ShowWhenLoadedDelegate; 而不是 -= 吗?否则我看不到任何地方会调用 ShowWhenLoadedDelegate。 - StayOnTarget

76

WindowStyle属性设置为None,这将隐藏控制框以及标题栏。无需内核调用。


23
这将完全隐藏窗口标题栏,这意味着您将无法看到窗口标题,也无法移动窗口。 - newman
11
可以通过在窗口的 MouseDown 事件中添加 this.DragMove(); 来使窗口可移动。 - paul
2
对于一个应该是纯信息性和强制性的模态对话框,比如正在升级已经打开的旧架构数据库的进度,这个解决方案是完美的。 - The Lonely Coder
1
我认为有些人可能想要一个边框。 - pjdupreez
2
绝对是最好的解决方案。给面板添加边框或实现移动都没有问题。 - buks
显示剩余4条评论

58

这不会消除关闭按钮,但它可以阻止某人关闭窗口。

将以下代码放入您的后端文件中:

protected override void OnClosing(CancelEventArgs e)
{
   base.OnClosing(e);
   e.Cancel = true;
}

8
请注意,在设置为模态对话框的“窗口”中执行此操作将干扰“窗口”设置其“DialogResult”属性并可能使其无法使用。https://dev59.com/tHNA5IYBdhLWcg3wmfEa - Sheridan
4
用这种方法时我遇到了溢出的问题,我去掉了 base.OnClosing(e) 这行代码,然后它就能正常工作了。 - jacobsgriffith
11
作为用户,我会讨厌将这个功能加入应用程序的程序员。 - user604613
2
@UrbanEsc 我倾向于同意这是一件烦人的事情,但当我这样做时 - 而且只有一次 - 这是一个强制性要求,也是必要的恶,因为有一些非常重要的过程正在进行中,不能被打断,应用程序在完成之前无法继续进行。还有其他的方法可以实现(使用后台线程,在准备就绪之前禁用UI),但老板和客户都喜欢这种方式,因为它强调了该过程的重要性。 - flurbius
这是在设置WindowStyle=None之后很好的效果,这样用户就不会从任务栏关闭窗口了。 - Hossein Ebrahimi
这是在设置WindowStyle=None之后很好,这样用户就不会从任务栏关闭窗口。 - undefined

18
要禁用关闭按钮,您应该将以下代码添加到窗口类中(代码取自此处,稍作编辑和格式化):
protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);

    HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;

    if (hwndSource != null)
    {
        hwndSource.AddHook(HwndSourceHook);
    }

}

private bool allowClosing = false;

[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);

private const uint MF_BYCOMMAND = 0x00000000;
private const uint MF_GRAYED = 0x00000001;

private const uint SC_CLOSE = 0xF060;

private const int WM_SHOWWINDOW = 0x00000018;
private const int WM_CLOSE = 0x10;

private IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case WM_SHOWWINDOW:
            {
                IntPtr hMenu = GetSystemMenu(hwnd, false);
                if (hMenu != IntPtr.Zero)
                {
                    EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
                }
            }
            break;
        case WM_CLOSE:
            if (!allowClosing)
            {
                handled = true;
            }
            break;
    }
    return IntPtr.Zero;
}

这段代码还禁用了系统菜单中的关闭选项,并禁止使用Alt + F4关闭对话框。

你可能想通过编程方式关闭窗口,仅仅调用Close()是无法起作用的。可以尝试如下方法:

allowClosing = true;
Close();

在Windows 7中:以上操作也会禁用(但不删除)下拉系统菜单中的“关闭”选项。关闭按钮本身被禁用(呈灰色),但未被删除。这个技巧对最小化/最大化项目/按钮无效 - 我怀疑WPF会重新启用它们。 - user166390
4
禁用按钮比仅仅删除它们更好,这可以保持一致的外观和让用户知道正在运行一个重要操作。 - Robert Baker

11
我刚刚添加了Joe White's answer的实现,使用了交互行为(需要引用System.Windows.Interactivity)。
代码:
public class HideCloseButtonOnWindow : Behavior<Window>
{
    #region bunch of native methods

    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

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += OnLoaded;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= OnLoaded;
        base.OnDetaching();
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var hwnd = new WindowInteropHelper(AssociatedObject).Handle;
        SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
    }
}

用法:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:w="clr-namespace:WpfApplication2">

    <i:Interaction.Behaviors>
        <w:HideCloseButtonOnWindow />
    </i:Interaction.Behaviors>

</Window>

如果无法引用 System.Windows.Interactivity,请查看此链接:https://dev59.com/Ymsy5IYBdhLWcg3wtwa9 - Ivan P.
另外,在“用法”中,命名空间的引用应为:xmlns:i="http://schemas.microsoft.com/xaml/behaviors"。它起作用了。 - Ivan P.
补充我的先前评论:我安装了 Microsoft.Xaml.Behaviors.Wpf Nuget 包。 - Ivan P.

10

我尝试了Viachaslau的答案,因为我喜欢不移除按钮而是禁用它的想法,但出于某种原因它并不总是起作用:关闭按钮仍然可用,但没有任何错误。

另一方面,这个始终有效(省略了错误检查):

[DllImport( "user32.dll" )]
private static extern IntPtr GetSystemMenu( IntPtr hWnd, bool bRevert );
[DllImport( "user32.dll" )]
private static extern bool EnableMenuItem( IntPtr hMenu, uint uIDEnableItem, uint uEnable );

private const uint MF_BYCOMMAND = 0x00000000;
private const uint MF_GRAYED = 0x00000001;
private const uint SC_CLOSE = 0xF060;
private const int WM_SHOWWINDOW = 0x00000018;

protected override void OnSourceInitialized( EventArgs e )
{
  base.OnSourceInitialized( e );
  var hWnd = new WindowInteropHelper( this );
  var sysMenu = GetSystemMenu( hWnd.Handle, false );
  EnableMenuItem( sysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED );
}

1
太棒了!已经作为我的项目中的“Window”扩展方法添加进去了。 - Matt Davis

10

需要设置的属性为 => WindowStyle="None"

<Window x:Class="mdaframework.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Start" Height="350" Width="525" ResizeMode="NoResize"  WindowStartupLocation="CenterScreen" WindowStyle="None">

4
这也隐藏了最大化/最小化按钮。 - VoteCoffee
3
它会去掉整个标题栏,使得方框变得丑陋而且没有描述。这是一种随意的方法,并且回答重复了。点踩。 - vapcguy
1
这是自助应用程序的最佳解决方案,它始终需要其应用程序最大化,并且不应允许客户关闭应用程序。所以点赞。 - Rajon Tanducar

2
以下是我使用自定义样式而不需要使用DllImports和P/Invoke调用来实现类似目标的方法。这将使用WindowStyle="none"来移除现有的标题栏,并显示一个具有相似背景颜色的'TextBlock'作为标题栏。

enter image description here

XAML代码

<Window x:Class="AddBook"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="http://wpftoolkit.my-libraries.com/v5" 
    WindowStartupLocation="CenterOwner"        
    ResizeMode="NoResize" 
    Style="{DynamicResource WindowStyleX}"
    ShowInTaskbar="False"
    ShowActivated="True"
    SizeToContent="Height"
    Title="Add New Book" 
    Width="450">
..............

</Window>

XAML

<Style x:Key="WindowStyleX" TargetType="{x:Type Window}">
<Setter Property="WindowStyle" Value="None" />
<Setter Property="AllowsTransparency" Value="False" />
<Setter Property="ResizeMode" Value="NoResize" />
<Setter Property="Background" Value="White" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type Window}">
            <Border BorderBrush="{DynamicResource BlackColor}" BorderThickness="1">
                <Grid Background="{TemplateBinding Background}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="30" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Border
                        Grid.Row="0"
                        Grid.ColumnSpan="2"
                        Background="{DynamicResource BlackColor}">
                        <Grid>
                            <TextBlock
                                Grid.Column="1"
                                Margin="10,0,0,0"
                                HorizontalAlignment="Left"
                                VerticalAlignment="Center"
                                FontSize="16"
                                Foreground="{DynamicResource WhiteTextForeground}"
                                Text="{TemplateBinding Title}" />
                        </Grid>
                    </Border>
                    <ContentPresenter Grid.Row="1" />
                </Grid>
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

2

让用户“关闭”窗口,但实际上只是隐藏它。

在窗口的OnClosing事件中,如果已经可见,则隐藏窗口:

    If Me.Visibility = Windows.Visibility.Visible Then
        Me.Visibility = Windows.Visibility.Hidden
        e.Cancel = True
    End If

每次执行后台线程时,重新显示背景UI窗口:

    w.Visibility = Windows.Visibility.Visible
    w.Show()

当终止程序执行时,请确保所有窗口都已关闭或可以关闭:

Private Sub CloseAll()
    If w IsNot Nothing Then
        w.Visibility = Windows.Visibility.Collapsed ' Tell OnClosing to really close
        w.Close()
    End If
End Sub

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接