Почему события «Развернуть / Свернуть» приводят к повторному включению кнопки «Закрыть» после ее отключения? - PullRequest
2 голосов
/ 29 мая 2010

Я использовал P / Invoke для вызова GetSystemMenu и EnableMenuItem (win32api), чтобы отключить функцию закрытия. Однако после сворачивания или разворачивания моего приложения Windows Forms кнопка снова включается.

Очевидно, что минимизация или максимизация является причиной поведения, но как? Я не уверен, где искать, чтобы предотвратить такое поведение.

Должен ли я предотвращать максимизацию и минимизацию поведения или есть что-то особенно неправильное в способе, которым я P / вызывал вызовы? Как только приложение (основная форма) загружено, я вызываю статический метод нажатием кнопки.

class PInvoke
{
    // P/Invoke signatures
    [DllImport("user32.dll")]
    static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
    [DllImport("user32.dll")]
    static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);

    // SysCommand (WM_SYSCOMMAND) constant
    internal const UInt32 SC_CLOSE = 0xF060;

    // Constants used with Add/Check/EnableMenuItem
    internal const UInt32 MF_BYCOMMAND = 0x00000000;
    internal const UInt32 MF_ENABLED = 0x00000000;
    internal const UInt32 MF_GRAYED = 0x00000001;
    internal const UInt32 MF_DISABLED = 0x00000002;

    /// <summary>
    /// Sets the state of the Close (X) button and the System Menu close functionality.
    /// </summary>
    /// <param name="window">Window or Form</param>
    /// <param name="bEnabled">Enabled state</param>
    public static void EnableCloseButton(IWin32Window window, bool bEnabled)
    {
        IntPtr hSystemMenu = GetSystemMenu(window.Handle, false);

        EnableMenuItem(hSystemMenu, SC_CLOSE, MF_BYCOMMAND | (bEnabled ? MF_ENABLED : MF_GRAYED));
    }
}

Ответы [ 3 ]

5 голосов
/ 29 мая 2010

Каждое окно имеет класс окна, который определяет стили для всех окон этого класса. Вы можете использовать стиль класса CS_NOCLOSE, чтобы удалить кнопку закрытия для окон этого класса. Смотрите здесь и здесь , чтобы узнать, как установить этот флаг класса.

Если это не даст вам того, что вы хотите, я бы не отключил свернуть / развернуть ради удобства использования, но вы могли бы прослушать события минимизации / развертки и повторно запустить код, чтобы отключить кнопку закрытия. Наконец, можно обрабатывать событие закрытия и просто не закрывать. Тогда вы знаете, что ваше окно точно не будет закрыто, даже если кнопка закрытия невольно станет активной.

3 голосов
/ 04 января 2011

Принятый ответ действительно предлагает возможный обход проблемы (и тот, который я использовал много раз), но он просто не отвечает на вопрос, который был первоначально задан:

Как / почему при максимизации или минимизации формы кнопка закрытия закрывается после ее отключения с помощью функций API GetSystemMenu и EnableMenuItem?

Я пришел к этому вопросу в ходе совершенно бесполезного поиска в Google, обнаружив это, казалось бы, необъяснимое поведение для себя. Не найдя ответа, который на самом деле объяснял бы поведение,
я был вынужден прибегнуть к некоторым собственным копаниям.

Для справки: обратите внимание, что тот же код, который показан в исходном вопросе, прекрасно работает в родном приложении Win32. Повторное включение пункта меню «Закрыть» ограничено приложениями WinForms.

Изучение исходного кода для класса System.Windows.Forms.Form раскрывает интересную деталь реализации: Разработчики .NET Framework, по-видимому, решили настраивать системное меню формы каждый раз, когда WindowState формы изменяет , включая максимизировать и минимизировать события, отправленные системой.

В частности, есть два метода с именем AdjustSystemMenu, которые отвечают за изменение системного меню в ответ на эти события (и путаницы в любой настройке, которую вы, возможно, сделали самостоятельно). Если вы заинтересованы в изучении кода (который я воздержался от публикации здесь в интересах тех, кто связан с такими проектами, как Mono), возьмите бесплатную копию .NET Reflector .

Я не совсем уверен, почему было принято это решение, но, по крайней мере, теперь у меня есть объяснение.

0 голосов
/ 30 января 2013

У меня было такое же требование. После нескольких попыток отключить опцию закрытия меню, а затем удалить и попытаться воссоздать ее (правильно), я нашел этот взлом от Microsoft http://support.microsoft.com/kb/184686.

Работает как шарм. Это все еще хак, но это работает.

Вот мое (свободное) преобразование C # оригинала VB

        [System.Runtime.InteropServices.DllImport("user32.dll")]
    static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    static extern int GetMenuItemCount(IntPtr hMenu);

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    static extern bool DrawMenuBar(IntPtr hWnd);


    public static void EnableCloseButton(Form frm, bool enabled) {
        IntPtr hMenu;
        int n;
        hMenu = GetSystemMenu(frm.Handle, false);
        if (hMenu != IntPtr.Zero) {
            n = GetMenuItemCount(hMenu);
            if (n > 0) {
                if (enabled) {
                    EnableClose(frm);
                }
                else {
                    DisableClose(frm);
                }
                SendMessage(frm.Handle, WM_NCACTIVATE, (IntPtr)1, (IntPtr)0);
                DrawMenuBar(frm.Handle);
                Application.DoEvents();
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MENUITEMINFO {
        public uint cbSize;
        public uint fMask;
        public uint fType;
        public uint fState;
        public int wID;
        public int hSubMenu;
        public int hbmpChecked;
        public int hbmpUnchecked;
        public int dwItemData;
        public string dwTypeData;
        public uint cch;
        //    public int hbmpItem;
    }

    internal const UInt32 SC_CLOSE = 0xF060;

    //SetMenuItemInfo fMask constants.
    const UInt32 MIIM_STATE = 0x1;
    const UInt32 MIIM_ID = 0x2;

    //'SetMenuItemInfo fState constants.
    const UInt32 MFS_ENABLED = 0x0;
    const UInt32 MFS_GRAYED = 0x3;
    const UInt32 MFS_CHECKED = 0x8;

    internal const int MFS_DEFAULT = 0x1000;

    [DllImport("user32.dll")]
    static extern bool SetMenuItemInfo(IntPtr hMenu, int uItem, bool fByPosition, [In] ref MENUITEMINFO lpmii);

    [DllImport("user32.dll")]
    static extern bool GetMenuItemInfo(IntPtr hMenu, int uItem, bool fByPosition, ref MENUITEMINFO lpmii);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

    private const UInt32 WM_NCACTIVATE = 0x0086;

    private static void DisableClose(Form frm) {
        IntPtr hMenu;
        int n;
        hMenu = GetSystemMenu(frm.Handle, false);
        if (hMenu != IntPtr.Zero) {
            MENUITEMINFO mif = new MENUITEMINFO();
            mif.cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO));
            mif.fMask = MIIM_ID | MIIM_STATE;
            mif.fType = 0;
            mif.dwTypeData = null;
            bool a = GetMenuItemInfo(hMenu, (int)SC_CLOSE, false, ref mif);
            mif.fState = MFS_GRAYED;
            SetMenuItemInfo(hMenu, (int)SC_CLOSE, false, ref mif);
            SendMessage(frm.Handle, WM_NCACTIVATE, (IntPtr)1, (IntPtr)0);

            mif.wID = -10;
            mif.fState = MFS_GRAYED;
            SetMenuItemInfo(hMenu, (int)SC_CLOSE, false, ref mif);
        }
    }

    private static void EnableClose(Form frm) {
        IntPtr hMenu;
        int n;
        hMenu = GetSystemMenu(frm.Handle, false);
        if (hMenu != IntPtr.Zero) {
            MENUITEMINFO mif = new MENUITEMINFO();
            mif.cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO));
            mif.fMask = MIIM_ID | MIIM_STATE;
            mif.fType = 0;
            mif.dwTypeData = null;
            bool a = GetMenuItemInfo(hMenu, -10, false, ref mif);
            mif.wID = (int)SC_CLOSE;
            SetMenuItemInfo(hMenu, -10, false, ref mif);
            SendMessage(frm.Handle, WM_NCACTIVATE, (IntPtr)1, (IntPtr)0);

            mif.fState = MFS_ENABLED;
            SetMenuItemInfo(hMenu, (int)SC_CLOSE, false, ref mif);
            SendMessage(frm.Handle, WM_NCACTIVATE, (IntPtr)1, (IntPtr)0);
        }
    }
...