暂时禁用关闭按钮

9

我需要暂时禁用关闭按钮(最小化和最大化应该允许)。

我尝试的每一个解决方案都会禁用所有按钮或永久禁用关闭按钮。有没有办法暂时禁用它?


3
请处理Closing事件,但请记住您无法停止任务管理器。 - Sayse
1
除了确实处理“Closing”事件之外,您还应考虑处理用户的决策 - 如果他想关闭窗口,则关闭它并处理后果。 - Random Dev
我也同意Carsten的观点,尽量不要强迫用户做任何事情。鼓励他们“做正确的事情”。 - Sayse
7个回答

12

永久禁用关闭按钮的方法是为窗体类设置CS_NOCLOSE样式。要从WinForms应用程序中执行此操作,可以重写窗体的CreateParams属性并使用|运算符添加SC_NOCLOSE标志,例如:

protected override CreateParams CreateParams
{
    get
    {
        const int CS_NOCLOSE = 0x200;
        CreateParams cp = base.CreateParams;
        cp.ClassStyle = cp.ClassStyle | CS_NOCLOSE;
        return cp;
    }
}

这是一种永久性的解决方案,因为您无法实时更新窗口类样式。您必须销毁并重新创建窗口类。

但是,您可以禁用系统菜单中的“关闭”命令,这也会自动禁用标题栏中的关闭按钮。

internal static class NativeMethods
{
    public const int SC_CLOSE     = 0xF060;
    public const int MF_BYCOMMAND = 0;
    public const int MF_ENABLED   = 0;
    public const int MF_GRAYED    = 1;

    [DllImport("user32.dll")]
    public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool revert);

    [DllImport("user32.dll")]
    public static extern int EnableMenuItem(IntPtr hMenu, int IDEnableItem, int enable);
}

public class MyForm : Form
{

    // ...

    // If "enable" is true, the close button will be enabled (the default state).
    // If "enable" is false, the Close button will be disabled.
    bool SetCloseButton(bool enable)
    {
        IntPtr hMenu = NativeMethods.GetSystemMenu(this.Handle, false);
        if (hMenu != IntPtr.Zero)
        {
            NativeMethods.EnableMenuItem(hMenu,
                                         NativeMethods.SC_CLOSE,
                                         NativeMethods.MF_BYCOMMAND | (enable ? NativeMethods.MF_ENABLED : NativeMethods.MF_GRAYED));                                
        }
    }   
}

请注意,这确实是一个短暂的操作。如果您执行了任何导致系统菜单被框架修改的操作(例如最大化或最小化窗体),则您的修改将被擦除。更多细节请参见我的相关答案。通常情况下,这是个问题,这也是为什么您更倾向于使用第一种解决方案的原因。但在这种情况下,由于您想要动态禁用和重新启用它,所以没关系。

最后,请注意您提出的功能与Windows对话框UI指南相反。它们基本上表示,用户期望看到关闭按钮,并且其存在给他们一种安全感,即他们始终可以安全地“退出”屏幕弹出的任何内容。因此,您不应该禁用它。它确实将进度对话框作为一个例外,但它继续说,进度对话框应始终具有“取消”按钮,允许中止操作。在这种情况下,您只需使标题栏中的关闭按钮调用此“取消”按钮即可,无需禁用它。


1
尽管可能有可能,但我从未见过。这不是程序的常规做法,因此您的程序应遵循已知的模式,以便用户知道如何使用它。
如果暂时无法关闭程序,请在用户尝试关闭时显示一条消息解释原因。这样,您可以提供一个解决方案(“您必须首先执行...”),而不仅仅是呈现问题(“无法关闭”)。
此外,关闭窗体有多种方法。您只看了其中一种。禁用其中一种仍将留下其他选项,因此最好适当处理“关闭”事件,以防止所有导致关闭窗口的选项。

1
isprocessing = true;
form.FormClosing += new FormClosingEventHandler(form_cancel); 
private void form_cancel(object sender, FormClosingEventArgs e)
{
   if (isprocessing)
   {
      e.Cancel = true;  //disables the form close when processing is true
   }
   else
   {
      e.Cancel = false; //enables it later when processing is set to false at some point 
    }
}

这对我有用。

1
以下情况下关闭按钮被禁用: 如果我们添加:MessageBoxButtons.YesNo
DialogResult Dr = MessageBox.Show(this, "", "", MessageBoxButtons.YesNo, MessageBoxIcon.Information);

0

根据其他答案,您正在违反指南和框架,但是,如果您真的必须这样做,一个解决方法是将所有表单内容放入Usercontrol中,然后在启用或禁用负载的表单实例之间传递它。

因此,当您触发关闭按钮的禁用或启用时,您会创建一个新的表单实例,在其中切换关闭按钮,然后传递对用户控件的引用,然后取消引用当前表单中的用户控件并转移到新表单。

这样做可能会导致一些闪烁。在我看来,这是一个可怕的想法,但这是“一个”选项。


我决定选择最用户友好的方法来解决这个问题。我选择处理FormClosing事件并在必要时取消该事件,同时通过消息框提供反馈,以便用户知道发生了什么。 - Horius

0

你不能隐藏它,但你可以禁用它:

private const int CP_NOCLOSE_BUTTON = 0x200;
protected override CreateParams CreateParams
{
    get
    {
       CreateParams myCp = base.CreateParams;
       myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON ;
       return myCp;
    }
}

来源:http://www.codeproject.com/Articles/20379/Disabling-Close-Button-on-Forms

如果您绝对需要隐藏它,唯一的方法是创建一个无边框窗体,然后绘制自己的最小化和最大化按钮。这里有一篇文章介绍如何实现:http://www.codeproject.com/Articles/42223/Easy-Customize-Title-Bar

在考虑这些解决方案之前,您应该重新思考为什么需要这样做。根据您尝试完成的任务,可能有更好的方式向用户呈现UI,而不是去掉熟悉的“X”关闭按钮。


绝对不要隐藏它!关闭它已经够糟糕了,但在某些情况下,有理由支持它。另一方面,完全删除它对用户来说是极其令人迷惑的。通常,您希望禁用不适用的命令,而不是删除它们。 - Cody Gray

0

使用Win32 API,您可以按照以下方式完成:

[DllImport("User32.dll")]
private static extern uint GetClassLong(IntPtr hwnd, int nIndex);

[DllImport("User32.dll")]
private static extern uint SetClassLong(IntPtr hwnd, int nIndex, uint dwNewLong);

private const int GCL_STYLE = -26;
private const uint CS_NOCLOSE = 0x0200;

private void Form1_Load(object sender, EventArgs e)
{
    var style = GetClassLong(Handle, GCL_STYLE);
    SetClassLong(Handle, GCL_STYLE, style | CS_NOCLOSE);
}

您将需要使用 GetClassLong / SetClassLong 以启用 CS_NOCLOSE 样式。然后,您可以使用相同的操作删除它,只需在 SetClassLongPtr 中使用 (style & ~CS_NOCLOSE)。

实际上,在 WPF 应用程序中也可以这样做(是的,我知道,问题是关于 WinForms 的,但也许有人将来会需要这个):

private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
    var hwnd = new WindowInteropHelper(this).Handle;
    var style = GetClassLong(hwnd, GCL_STYLE);
    SetClassLong(hwnd, GCL_STYLE, style | CS_NOCLOSE);
}

不过,您应该考虑其他人建议的:只需显示一个MessageBox或另一种消息,以指示用户现在不应关闭窗口。


编辑: 由于窗口类只是一个UINT,因此您可以使用GetClassLong和SetClassLong函数而不是GetClassLongPtr和SetClassLongPtr(如MSDN所述):

如果要检索指针或句柄,则此函数已被GetClassLongPtr函数取代。 (32位Windows上的指针和句柄为32位,在64位Windows上为64位。)

这解决了Cody Gray描述的32位操作系统中缺少* Ptr函数的问题。


这段代码存在几个问题。首先,32位操作系统不导出GetClassLongPtrSetClassLongPtr函数。 这些仅是SDK头文件中的宏。您必须根据目标体系结构P / Invoke适当的函数。其次,在动态切换此样式时,必须使窗体的非客户区无效,以确保关闭按钮的视觉状态发生更改。而且使非客户区失效比较棘手 - 窗口管理器会进行许多优化假设。 - Cody Gray
1
最后值得注意的是,设置CS_NOCLOSE样式并不会禁用系统菜单上的关闭项。因此,关闭窗口仍然很容易,可以使用系统菜单或双击标题栏图标。这可能是您想要的,但很可能不是。 - Cody Gray
@CodyGray,看起来你可以简单地使用 GetClassLongSetClassLong,因为窗口的样式只是一个 UINT。我已经相应地更新了答案。 - Alovchin
嗯,不对,窗口类样式不仅仅是“UINT”。它是一个“UINT_PTR”,这意味着它的大小会根据该平台上指针的宽度而变化。因此,您需要注意编译的体系结构的位数,这就是为什么存在“Ptr-suffix”宏的原因。 - Cody Gray
我很惊讶听到你说你看到了关闭按钮被更新。当我在Windows XP和Windows 7上尝试了你的代码(使用按钮来切换而不是计时器),我直到手动强制窗口重新绘制才看到更新。 - Cody Gray
显示剩余2条评论

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