代码之家  ›  专栏  ›  技术社区  ›  DotNetSpartan

当菜单打开时,单击“最小化”、“最大化”和“关闭”按钮,Windows标题栏不起作用。

  •  0
  • DotNetSpartan  · 技术社区  · 8 年前

    当我的菜单打开时,第一个单击最小化、最大化或关闭按钮不起作用。第一次单击这些标题栏按钮将关闭菜单并移动焦点,然后第二次单击最小化/最大化/关闭窗口。

    我提到过这个 article 这说明了上下文菜单和弹出窗口的一个类似问题。 但解决方案不适用于菜单类。

    这就是我所尝试的:

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

    主窗口.xaml

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

    菜单增强.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 回复  |  直到 8 年前
        1
  •  1
  •   dymanoid    8 年前

    因为我在参考问题中提出了解决方案,所以我可以很容易地回答这个问题。

    正如我在评论中提到的,你应该使用 MenuItem.SubMenuOpened 路由事件。

    因此,为了使此代码正常工作,需要进行以下修改:

    1. 更改 WindowHelper 类来处理 Menu ,而不是 MenuItem 我们要把我们的附属财产 菜单 ,看看你的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
        // ...
    }
    
    1. 更改 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);
            }
        }
    }
    
    1. 最后,更新初始事件订阅代码-我们现在有路由事件!

    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
    
    推荐文章