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

我找到了ResizeMode、WindowState和WindowStyle,但这些属性都不允许我隐藏关闭按钮,而是显示标题栏,就像在模态对话框中一样。


当前回答

我只是使用交互性行为(你需要引用System.Windows.Interactivity)添加了我对Joe White的答案的实现。

代码:

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>

其他回答

我非常喜欢这个答案,它使用附加属性来调节行为。然而,我发现答案的实现过于复杂,它也没有解决防止窗口关闭的次要目标,即使使用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)值,标题栏中的所有按钮都被删除了,包括关闭按钮。

You may run into the need to toggle the window state depending on what page or user control is currently being displayed. (Yes, this is an unusual scenario, but it can happen. In our case we have a user control in our application that is displayed on an external touch screen for customers to interact with. We don't want our customers to have touch access to close the screen. But the rest of the application uses standard windows frames.) To do this from the code behind of the page or user control. Note: You must run it from the Loaded event not the constructor because the control hasn't been populated in the constructor and it will throw an exception.

// To toggle it off
Window.GetWindow(this).WindowStyle = WindowStyle.None;

// To turn it back on toggle it off
Window.GetWindow(this).WindowStyle = WindowStyle.SingleBorderWindow;

我尝试了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 );
}

要设置的属性是=> 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">