Кнопки «Свернуть», «Развернуть» и «Закрыть» в строке заголовка Windows не работают одним щелчком мыши при открытом меню - PullRequest
0 голосов
/ 30 мая 2018

Когда мое меню открыто, первый единственный щелчок по кнопке сворачивания, разворачивания или закрытия не работает.Первый щелчок по этим кнопкам строки заголовка закрывает меню и смещает фокус, а затем по второму щелчку минимизирует / максимизирует / закрывает оконные работы.

Я ссылался на эту статью , в которой говоритсяаналогичная проблема для контекстного меню и всплывающих окон.Но решение не работает для класса Menu.

Вот что я попробовал:

xmlns:controls="clr-namespace:TestProject.Controls"

MainWindow.xaml

<Menu Style="{StaticResource CustomMenuStyle}" controls:MenuMouseEnhance.Enabled="True">
                            <MenuItem Header="List of Items" >
                                <MenuItem Header="MenuOne" />
                                <MenuItem Header="MenuTwo" />
                            </MenuItem>  
                         </Menu>

MenuMouseEnhance.cs

  public static class MenuMouseEnhance
    {
        public static bool GetEnabled(UIElement element)
        {
            return (bool)element.GetValue(EnabledProperty);
        }

        public static void SetEnabled(UIElement element, bool value)
        {
            element.SetValue(EnabledProperty, value);
        }


        public static readonly DependencyProperty EnabledProperty =
            DependencyProperty.RegisterAttached(
                "Enabled",
                 typeof(bool),
                 typeof(MenuMouseEnhance),
                 new PropertyMetadata(false, EnabledChanged));



        private static void EnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

            MenuItem Menu = d as MenuItem;

            if ((bool)e.NewValue)
            {
                Menu.SubmenuOpened += Menu_Opened;
            }
            else
            {
                Menu.SubmenuOpened -= Menu_Opened;
            }
        }



        private static void Menu_Opened(object sender, EventArgs e)
        {
            MenuItem p = (MenuItem)sender;

            // First, we determine the window we will monitor:
            Window w = Window.GetWindow(p);
            if (w != null)
            {
                // Then, we need a HwndSource instance of that window
                // to be able to insert our custom Message Hook
                HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(w).Handle);
                if (source != null)
                {
                    //Enable the custom window helper!
                    WindowHelper.Enable(source, w, p);
                }
            }
        }

        /// <summary>
        /// This is a custom helper class
        /// This initialized the HwndSource and Window classes through constructor injection
        /// </summary>
        private class WindowHelper
        {
            private readonly HwndSource mHwndSource;
            private readonly Window mWindow;

            /// <summary>
            /// Set the members of this class in constructor
            /// </summary>
            /// <param name="hwndSource"></param>
            /// <param name="window"></param>
            private WindowHelper(HwndSource hwndSource, Window window)
            {
                mHwndSource = hwndSource;
                mWindow = window;
            }

        public static void Enable(HwndSource hwndSource, Window window, MenuItem menu)
        {
            WindowHelper helper = new WindowHelper(hwndSource, window);
            hwndSource.AddHook(helper.WndProc);
            menu.SubmenuClosed += helper.Menu_Closed;
        }


        private void Menu_Closed(object sender, EventArgs e)
        {
            // The ContextMenu is closed now - disable all!
            MenuItem p = (MenuItem)sender;
            p.SubmenuClosed -= Menu_Closed;
            mHwndSource.RemoveHook(WndProc);
        }


        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            // WM_SETCURSOR will be sent to our window when the user moves the mouse
            // Cursor around and clicks the mouse buttons.
            if (msg != NativeConstants.WM_SETCURSOR)
            {
                return IntPtr.Zero;
            }

            // Determine the necessary parameters.    
            //The low-order word of lParam specifies the hit-test code.
            //The high-order word of lParam specifies the identifier of the mouse message.
            var mouseMessage = ((int)lParam & 0xFFFF0000) >> 16;
            var hitTest = (int)lParam & 0xFFFF;

            switch (hitTest)
            {
                // Only continue if the mouse is over
                // The 'minimize', 'maximize', 'close'
                case NativeConstants.HTMINBUTTON:
                case NativeConstants.HTMAXBUTTON:
                case NativeConstants.HTCLOSE:
                    break;

                default:
                    // Otherwise, do nothing.
                    return IntPtr.Zero;
            }

            // If the user clicks outside the Menu,
            // a WM_MOUSEMOVE message will be transmitted via WM_SETCURSOR.
            // So if we've received something other - ignore that.
            if (mouseMessage != NativeConstants.WM_MOUSEMOVE)
            {
                return IntPtr.Zero;
            }

            // We need to perform these actions manually,
            // because the window will not receive the corresponding messages
            // on first mouse click (when the ContextMenu is still open).
            switch (hitTest)
            {
                case NativeConstants.HTMINBUTTON:
                    mWindow.WindowState = WindowState.Minimized;
                    break;

                case NativeConstants.HTMAXBUTTON:
                    if (mWindow.WindowState.ToString() == "Maximized")
                    {
                        //When Window is maximized
                        //Assign the Normal state to the window when Maximize is pressed
                        mWindow.WindowState = WindowState.Normal;
                    }
                    else
                    {
                        //When Window is in normal state
                        //Assign the maximized state to the window when Maximize is pressed
                        mWindow.WindowState = WindowState.Maximized;
                    }
                    break;

                case NativeConstants.HTCLOSE:
                    mWindow.Close();
                    break;
            }

            // We always return 0, because we don't want any side-effects
            // in the message processing.
            return IntPtr.Zero;
        }
    }


    private static class NativeConstants
    {
        public const int WM_SETCURSOR = 0x020;
        public const int WM_MOUSEMOVE = 0x200;
        public const int HTMINBUTTON = 8;
        public const int HTMAXBUTTON = 9;
        public const int HTCLOSE = 20;
    }
}

1 Ответ

0 голосов
/ 30 мая 2018

Поскольку я предложил решение в указанном вопросе, я легко могу ответить на него.

Как я уже упоминал в комментариях, вам следует использовать перенаправленное событие MenuItem.SubMenuOpened.

Итак, вот изменения, которые необходимо внести, чтобы этот код работал:

  1. Измените класс WindowHelper для обработки Menu, а не MenuItem s.Мы собираемся установить наше присоединенное свойство на Menu, смотрите ваш XAML!

private class WindowHelper
{
    // Third parameter is a Menu, not a MenuItem
    public static void Enable(HwndSource hwndSource, Window window, Menu menu)
    {
        WindowHelper helper = new WindowHelper(hwndSource, window);
        hwndSource.AddHook(helper.WndProc);

        // Subscribe to the routed event MenuItem.SubmenuClosed
        menu.AddHandler(MenuItem.SubmenuClosedEvent, (RoutedEventHandler)helper.Menu_Closed);
    }

    // The method signature has to be changed - this is a routed event handler now
    private void Menu_Closed(object sender, RoutedEventArgs e)
    {
        Menu menu = (Menu)sender;
        MenuItem menuItem = (MenuItem)e.Source;

        if (menuItem.Parent != menu)
        {
            // If it's not the first level menu, ignore it.
            // We only disable our helper when the whole menu closes.
            return;
        }

        // Unsubscribe from the routed event
        menu.RemoveHandler(MenuItem.SubmenuClosedEvent, (RoutedEventHandler)Menu_Closed);
        mHwndSource.RemoveHook(WndProc);
    }

    // Rest is unchanged
    // ...
}
Измените подпись обработчика событий Menu_Opened, чтобы она соответствовала делегату RoutedEventHandler.Обновите код подписки на событие.
private static void Menu_Opened(object sender, RoutedEventArgs e)
{           
    Menu menu = (Menu)sender;
    MenuItem menuItem = (MenuItem)e.Source;

    if (menuItem.Parent != menu)
    {
        // We don't want to process any sub-menus in the deeper levels,
        // because the helper will already be enabled when
        // a first level menu opens
        return;
    }

    Window w = Window.GetWindow(menu);
    if (w != null)
    {
        HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(w).Handle);
        if (source != null)
        {
            WindowHelper.Enable(source, w, menu);
        }
    }
}
И, наконец, обновите исходный код подписки на события - теперь мы перенаправили события!
private static void EnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    Menu menu = (Menu)d;

    if ((bool)e.NewValue)
    {
        menu.AddHandler(MenuItem.SubmenuOpenedEvent, (RoutedEventHandler)Menu_Opened);
    }
    else
    {
        menu.RemoveHandler(MenuItem.SubmenuOpenedEvent, (RoutedEventHandler)Menu_Opened);
    }
}

Теперь это будет работать для меню.

ПоКстати, вместо

w.WindowState.ToString() == "Maximized"

следует использовать

w.WindowState == WindowState.Maximized
...