【问题标题】:Disabling the Close-Button temporarily暂时禁用关闭按钮
【发布时间】:2014-10-08 22:21:28
【问题描述】:

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

我尝试过的每个解决方案都会禁用所有按钮,或者只是永久禁用关闭按钮。有什么办法可以暂时做吗?

【问题讨论】:

  • 改为处理Closing事件,记住不能停止任务管理器
  • 除了确实处理 Closing 事件之外,您还应该考虑处理用户的决定 - 如果他想要关闭窗口然后关闭它并处理后果
  • 这里我也同意 Carsten 的观点,尽量不要强迫用户做任何事情。鼓励他们“做正确的事”

标签: c# winforms


【解决方案1】:

永久禁用关闭按钮的方法是为窗体的窗口类设置CS_NOCLOSE style。要从 WinForms 应用程序执行此操作,您需要覆盖表单的 CreateParams property 并使用 | 运算符添加 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));                                
        }
    }   
}

请注意,这实际上是一个瞬态操作。如果您做了任何事情 导致系统菜单被框架修改(例如最大化或最小化表单),您的修改将被删除。更多细节在my related answer here。这通常是一个问题,也是您更喜欢使用第一个解决方案的原因。但在这种情况下,既然你想动态禁用和重新启用,那也没什么大不了的。

最后,请注意您的提议与Windows UI Guidelines for dialog boxes 背道而驰。他们说,从本质上讲,用户希望看到一个关闭按钮,并且它的存在给了他们一种安全感,他们总是可以安全地“退出”屏幕上弹出的任何内容。因此,您不应禁用它。它确实调用了一个进度对话框作为一个例外,但它继续说一个进度对话框应该总是有一个允许中止操作的“取消”按钮。在这种情况下,您可以简单地让标题栏中的关闭按钮调用这个“取消”按钮——无需禁用它。

【讨论】:

    【解决方案2】:

    虽然有可能,但我从未见过。程序不是这样做的,您的程序应该遵循已知的模式,以便用户知道如何使用它。

    如果暂时无法关闭程序,请在用户尝试时显示一条消息,说明原因。通过这种方式,您可以提出解决方案(“您必须首先做...”),而不是简单地提出问题(“无法关闭”)。

    此外,还有多种方法可以关闭表单。你只看其中之一。禁用一个仍然会留下其他的,并且您希望阻止导致关闭窗口的 所有 选项,因此最好适当地处理 Closing 事件。

    【讨论】:

      【解决方案3】:
      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 
          }
      }
      

      这对我有用

      【讨论】:

        【解决方案4】:

        关闭按钮在低于条件时被禁用: 如果我们添加:MessageBoxButtons.YesNo

        DialogResult Dr = MessageBox.Show(this, "", "", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
        

        【讨论】:

          【解决方案5】:

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

          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

          在考虑这些解决方案之前,您或许应该重新考虑一下为什么需要这样做。根据您要执行的操作,除了取消熟悉的“X”关闭按钮之外,可能还有更好的方式向用户展示 UI。

          【讨论】:

          • 绝对不要隐藏它!关闭它已经够糟糕的了,但在某些情况下需要为此提出论据。另一方面,完全删除它会让用户极度迷失方向。通常,您希望禁用不适用的命令,而不是删除它们。
          【解决方案6】:

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

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

          执行此操作时可能会出现一些闪烁。恕我直言,这是一个可怕的想法,但它是“一个”选项。

          【讨论】:

          • 我决定选择对用户最友好的方法来解决这个问题。我已选择处理 FormClosing-Event 并取消该事件,只要我必须这样做,同时它会提供一个 MessageBox 作为反馈,以便用户知道发生了什么
          【解决方案7】:

          使用 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 适当的功能。其次,在动态切换此样式时,您必须使表单的非客户区无效,以确保更改关闭按钮的视觉状态。并且使非客户区无效是很棘手的——窗口管理器为了优化目的做了很多假设。
          • 最后,值得注意的是,设置CS_NOCLOSE 样式不会禁用系统菜单上的关闭项。因此,通过使用系统菜单或双击标题栏图标来关闭窗口仍然是微不足道的。这可能是你想要的,但它可能不是。
          • @CodyGray,这与设置CreateParams.ClassStyle 的解决方案大致相同。非客户区的失效似乎是由操作系统本身完成的,因为它在尝试使用计时器进行多次禁用/启用操作时成功工作(实际上,我认为这与设置通常的 Button 的 Enabled 属性相同)。但是对于 32 位操作系统,您绝对是正确的,在实施此解决方案时必须考虑这一点。
          • @CodyGray,看来您可以简单地使用GetClassLongSetClassLong,因为窗口的样式只是一个UINT。我已经相应地更新了答案。
          • 嗯,不,窗口类样式不是“只是一个 UINT”。它是一个 UINT_PTR,这意味着它会根据该平台上指针的宽度改变大小。所以你确实需要注意你正在编译的架构的位数,这就是 Ptr-suffix 宏存在的原因。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多