【问题标题】:Minimize, Maximize and close buttons of Windows title bar does not work on single click when pop up window is open当弹出窗口打开时,Windows 标题栏的最小化、最大化和关闭按钮在单击时不起作用
【发布时间】:2018-10-06 19:15:53
【问题描述】:

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

有什么方法——我们可以在第一次点击时激活这些标题栏按钮吗?

MainWindow.xaml

<Button Height="54" Width="50" Margin="100,0,0,0" x:Name="btnNotification"  FontFamily="Segoe UI Symbol" FontSize="20" Content="&#x1f514;" Command="{Binding LoadNotification}" Click="btnNotification_Click"/>

<Popup Name="NotificationPopup" IsOpen="False" Closed="PopupClosed" StaysOpen="False" PlacementTarget="{Binding ElementName=btnNotification}" Placement="Bottom" VerticalOffset="20">
    <Grid x:Name="PopUpGrid" Height="560" Width="360" Background="White">
        <StackPanel Orientation="Vertical" HorizontalAlignment="Right">
            <Button  BorderBrush="Transparent" BorderThickness="0" Background="White" >
                <StackPanel Width="{Binding ActualWidth, ElementName=PopUpGrid}" Orientation="Vertical">
                    <WrapPanel>
                        <Rectangle Width="20"/>
                        <TextBlock  Text="Notifications" Width="300" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="24" FontWeight="Light" />
                        <Button Click="btnNotification_Click" >
                            <StackPanel>
                                <TextBlock Text="&#x2715;" Foreground="Black" FontWeight="ExtraLight"/>
                            </StackPanel>
                        </Button>
                    </WrapPanel>
                    <Grid>
                            <!--Datagrid-->
                    </Grid>
                </StackPanel>
            </Button>
        </StackPanel>
    </Grid>
</Popup>

MainWindow.xaml.cs

public void PopupClosed(object sender, EventArgs e)
{
    NotificationPopup.IsOpen = false;               
}

【问题讨论】:

    标签: c# wpf xaml popup titlebar


    【解决方案1】:

    您试图实现的目标违反了“正常”的 Windows 行为。实际上,这是由操作系统管理的。

    但如果您仍想覆盖默认操作系统的行为,您需要做一些低级 Windows 消息魔术。

    我有一个解决方案给你,这是一个简单的附加属性,你可以将它用于你所有的Popups。就这样:

    <Popup local:PopupMouseEnhance.Enabled="True">
        <!-- your content here -->
    </Popup>
    

    但是,幕后发生了一些事情,所以我会尝试解释一下。这是我为您创建的附加属性的完整来源。阅读 cmets 以了解发生了什么。

    static class PopupMouseEnhance
    {
        // This is the usual attached property stuff...
        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(PopupMouseEnhance),
                 new PropertyMetadata(false, EnabledChanged));
    
        // This method is called when you set the attached property value
        private static void EnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Popup popup = d as Popup;
            if (popup == null)
            {
                // We don't support anything but Popups
                throw new InvalidOperationException("This attached property can only be set on a Popup object.");
            }
    
            if ((bool)e.NewValue)
            {
                // if the attached property value is 'true', then enable our trick...
                popup.Opened += Popup_Opened;
    
                // This is to prevent memory leaks when Popups get removed from the visual tree
                popup.Unloaded += Popup_Unloaded;
            }
            else
            {
                // ... otherwise, disable
                popup.Unloaded -= Popup_Unloaded;
                popup.Opened -= Popup_Opened;
            }      
        }
    
        private static void Popup_Unloaded(object sender, RoutedEventArgs e)
        {
            // When a Popup is completely unloaded, unsubscribe from everything!
            // This event won't be raised on app's shutdown, but in that case
            // we don't bother with memory leaks anyway.
            Popup p = (Popup)sender;
            p.Unloaded -= Popup_Unloaded;
            p.Opened -= Popup_Opened;      
        }
    
        private static void Popup_Opened(object sender, EventArgs e)
        {
            Popup p = (Popup)sender;
    
            // Okay, the Popup is shown. Enable our tricks!
            // 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)
                {
                    // All set, enable our custom window helper!
                    WindowHelper.Enable(source, w, p);
                }
            }
        }
    
        // Our custom helper class. The magic occurs here.
        private class WindowHelper
        {
            private readonly HwndSource mHwndSource;
            private readonly Window mWindow;
    
            private WindowHelper(HwndSource hwndSource, Window window)
            {
                mHwndSource = hwndSource;
                mWindow = window;
            }
    
            public static void Enable(HwndSource hwndSource, Window window, Popup popup)
            {
                // Create an instance of our helper class...
                WindowHelper helper = new WindowHelper(hwndSource, window);
    
                // ... and set a Message Hook to our custom method.
                hwndSource.AddHook(helper.WndProc);
    
                // Don't forget to disable the magic, when the popup is closed.
                popup.Closed += helper.Popup_Closed;
            }
    
            private void Popup_Closed(object sender, EventArgs e)
            {
                // The Popup is closed now - disable all!
                Popup p = (Popup)sender;
                p.Closed -= Popup_Closed;
                mHwndSource.RemoveHook(WndProc);
            }
    
            // This is our custom Windows messages hook.
            // This method will be called whenever our window receives a message.
            private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                // We're only interested in the WM_SETCURSOR message.
                // It 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.
                // See MSDN topic on WM_SETCURSOR for details.
                var mouseMessage = ((int)lParam & 0xFFFF0000) >> 16;
                var hitTest = (int)lParam & 0xFFFF;
    
                switch (hitTest)
                {
                    // Only continue if the mouse is over
                    // '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 Popup,
                // 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 Popup is still open).
                switch (hitTest)
                {
                    case NativeConstants.HTMINBUTTON:
                        mWindow.WindowState = WindowState.Minimized;
                        break;
    
                    case NativeConstants.HTMAXBUTTON:
                        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;
        }
    }
    

    希望这会有所帮助!

    【讨论】:

    • 非常感谢!这是一个很好的解决方案。现在,我观察到最小化和关闭按钮本身在单击时工作(如预期的那样),但最大化按钮仍然需要单击两次。
    • 那么,也许你有一些自定义窗口代码。在我的系统上一切正常。您可以调试消息挂钩方法并查看那里发生了什么。可能WM_SETCURSOR 消息未传递,因为其他代码处理了它,或者这条消息有一些意想不到的LParam 值。
    • @dynamoid,这个解决方案是否也适用于上下文菜单/菜单?我尝试用上下文菜单替换 Popup 类,但单击机制不起作用。请评论
    • 是的,它应该可以工作。我还没有尝试过。您可能应该删除 ContextMenuUnloaded 事件处理程序,因为它会在关闭菜单时引发,从而完全禁用该技巧。
    • 感谢 dymanoid,我也必须为 Menu 获得相同的行为。但我看到菜单没有打开/关闭事件。您能否提供一些线索-如何做到这一点?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-21
    • 2011-01-04
    • 2016-03-04
    • 1970-01-01
    • 1970-01-01
    • 2012-12-16
    相关资源
    最近更新 更多