dotnet 讀 WPF 源代碼 Popup 的 StaysOpen 爲 false 將會吃掉其餘窗口的首次激活

在 WPF 中,使用 Popup 控件,能夠設置 StaysOpen 屬性來控制是否在 Popup 失去焦點時,也就是點擊界面空白處,自動收起 Popup 控件。但若是有兩個窗口,在設置 Popup 控件的 StaysOpen 屬性爲 false 那麼將會吃掉在點擊其餘窗口的第一次交互,如鼠標點擊或觸摸點擊時將不會讓本進程的其餘窗口 Activate 激活php

在 WPF 中,經過 Popup 控件能夠方便設置浮出的窗口,本質上 Popup 控件也是一個窗口,只是這是一個特殊的窗口。可是在使用 Popup 控件時,若是經過設置 Popup 控件的 StaysOpen 屬性爲 false 的方式讓 Popup 在點擊非 Popup 範圍內,包括點擊窗口其餘空白部分,或者點擊其餘應用程序或桌面等,自動收起。那麼 Popup 將會在點擊本進程內的其餘窗口時,點擊的交互被 Popup 吃掉,而讓其餘窗口收不到一次交互git

行爲以下:github

假定有兩個窗口,其中一個是 MainWindows 主窗口,另外一個是用來承載 Popup 的 Window1 窗口。 其中 Windows1 窗口有一個按鈕,點擊按鈕時將會彈出一個 Popup 控件,代碼過於簡單,我就不將全部代碼所有寫在博客。全部代碼放在 github 和 gitee 歡迎小夥伴訪問less

如下是 Windows1 的界面,有一個按鈕,和一個 Popup 控件,點擊按鈕自動彈出 Popup 控件ide

<Grid>
        <Button x:Name="OpenPopupButton" HorizontalAlignment="Center" VerticalAlignment="Center"
                Content="Open Popup" Click="OpenPopupButton_OnClick"></Button>
        <Popup x:Name="Popup" StaysOpen="False" PlacementTarget="{x:Reference OpenPopupButton}">
            <Grid Background="Gray" Width="100" Height="100">
                <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">The popup</TextBlock>
            </Grid>
        </Popup>
    </Grid>

如下是 Windows1 點擊按鈕的代碼工具

private void OpenPopupButton_OnClick(object sender, RoutedEventArgs e)
        {
            Popup.IsOpen = true;
        }

在 MainWindow 裏,在 Loaded 事件裏面彈出 Windows1 請看代碼ui

public MainWindow()
        {
            InitializeComponent();

            Loaded += MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var window1 = new Window1();
            window1.Show();
        }

請運行代碼,能夠看到打開兩個窗口,此時若是點擊 MainWindows 那麼可讓 MainWindows 獲取焦點。接下來請點擊 Window1 的空白,而後點擊 Open Popup 按鈕,此時將會彈出 Popup 控件。再點擊 MainWindows 的空白,能夠看到 MainWindows 只是獲取到鼠標按下和擡起事件,可是沒有被激活沒有獲取到焦點,依然焦點是 Windows1 窗口this

在 MainWindows 上添加一些代碼,這樣能夠方便在 VisualStudio 的輸出窗口裏面,看到窗口的各個事件rest

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Loaded += MainWindow_Loaded;
            MouseDown += MainWindow_MouseDown;
            MouseUp += MainWindow_MouseUp;
            Activated += MainWindow_Activated;
            Deactivated += MainWindow_Deactivated;
            LostFocus += MainWindow_LostFocus;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var window1 = new Window1();
            window1.Show();
        }

        private void MainWindow_MouseUp(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine($"MainWindow_MouseUp");
        }

        private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine($"MainWindow_MouseDown");
        }

        private void MainWindow_Activated(object sender, EventArgs e)
        {
            Debug.WriteLine($"MainWindow_Activated");
        }

        private void MainWindow_Deactivated(object sender, EventArgs e)
        {
            Debug.WriteLine($"MainWindow_Deactivated");
        }

        private void MainWindow_LostFocus(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine($"MainWindow_LostFocus");
        }
    }

下面來執行如下兩個不一樣的動做,瞭解一下彈出 Popup 對進程內的其餘窗口的行爲code

動做1的步驟:

  • 運行代碼,默認焦點是在 Window1 上
  • 點擊 MainWindow 的空白

此時能夠看到 VisualStudio 輸出的內容以下

MainWindow_Activated
MainWindow_Deactivated

MainWindow_Activated
MainWindow_MouseDown
MainWindow_MouseUp

第一次 MainWindow_Activated 和 MainWindow_Deactivated 是在 MainWindows 的 Loaded 彈出 Window1 而激活和失去焦點的

第二次的 MainWindow_Activated 和鼠標按下和擡起是在點擊 MainWindow 的空白,這是符合預期的

動做2的步驟:

  • 運行代碼,默認焦點是在 Window1 上
  • 點擊 Window1 的 Open Popup 按鈕
  • 點擊 MainWindow 的空白

此時能夠看到 VisualStudio 輸出的內容以下

MainWindow_Activated
MainWindow_Deactivated

MainWindow_MouseDown
MainWindow_MouseUp

對比能夠了解,在點擊 Window1 的 Open Popup 按鈕彈出 Popup 控件以後,下一次點擊 MainWindow 是不會激活 MainWindow 只是收到鼠標的按下和擡起

那爲何 Popup 會影響進程的其餘窗口的行爲?下面來閱讀 Popup 的源代碼

在 Popup 的 OnLostMouseCapture 方法裏面,觸發的定義以下

static Popup()
        {
            EventManager.RegisterClassHandler(typeof(Popup), Mouse.LostMouseCaptureEvent, new MouseEventHandler(OnLostMouseCapture));

            // 忽略其餘代碼
        }

        private static void OnLostMouseCapture(object sender, MouseEventArgs e)
        {
            Popup popup = sender as Popup;

            if (!popup.StaysOpen)
            {
                PopupRoot root = popup._popupRoot.Value;

                // Reestablish capture if an element within us lost capture
                // (hence we receive the LostCapture routed event) and capture
                // is not being acquired anywhere else.
                //
                // Note we do not reestablish capture if we are losing capture
                // ourselves.
                bool reestablishCapture = e.OriginalSource != root && Mouse.Captured == null && MS.Win32.SafeNativeMethods.GetCapture() == IntPtr.Zero;

                if(reestablishCapture)
                {
                    popup.EstablishPopupCapture();
                    e.Handled = true;
                }
                else
                {
                    // 忽略其餘代碼
                }
            }
        }

在點擊 MainWindow 的空白,將會觸發到 Popup 的 OnLostMouseCapture 方法,接着進入 EstablishPopupCapture 方法

private void EstablishPopupCapture(bool isRestoringCapture=false)
        {
            if (!_cacheValid[(int)CacheBits.CaptureEngaged] && (_popupRoot.Value != null) &&
                (!StaysOpen))
            {
                IInputElement capturedElement = Mouse.Captured;
                PopupRoot parentPopupRoot = capturedElement as PopupRoot;
                if (parentPopupRoot != null)
                {
                    if (isRestoringCapture)
                    {
                        // if the other PopupRoot is restoring capture back to this
                        // popup, ignore mouse button events until both buttons have been
                        // released.  Otherwise a mouse click outside a chain of
                        // "nested" popups would dismiss two of them - one on MouseDown
                        // and another on MouseUp.
                        if (Mouse.LeftButton != MouseButtonState.Released ||
                            Mouse.RightButton != MouseButtonState.Released)
                        {
                            _cacheValid[(int)CacheBits.IsIgnoringMouseEvents] = true;
                        }
                    }
                    else
                    {
                        // this is a "nested" popup, invoked while another popup is open.
                        // We need to restore capture to the previous popup root when
                        // we're done
                        ParentPopupRootField.SetValue(this, parentPopupRoot);
                    }

                    // in either case, taking capture away from the other PopupRoot is OK.
                    capturedElement = null;
                }

                if (capturedElement == null)
                {
                    // When the mouse is not already captured, we will consider the following:
                    // In all cases but Modeless, we want the popup and subtree to receive
                    // mouse events and prevent other elements from receiving those messages.
                    Mouse.Capture(_popupRoot.Value, CaptureMode.SubTree);
                    _cacheValid[(int)CacheBits.CaptureEngaged] = true;
                }
            }
        }

在 EstablishPopupCapture 方法裏面從新調用了 Mouse.Capture 將會讓本進程內的其餘窗口沒有被激活

以上是大琛告訴個人,我只是記錄的工具人

我搭建了本身的博客 https://blog.lindexi.com/ 歡迎你們訪問,裏面有不少新的博客。只有在我看到博客寫成熟以後纔會放在csdn或博客園,可是一旦發佈了就再也不更新

若是在博客看到有任何不懂的,歡迎交流,我搭建了 dotnet 職業技術學院 歡迎你們加入

本做品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、從新發布,但務必保留文章署名林德熙,不得用於商業目的,基於本文修改後的做品務必以相同的許可發佈。若有任何疑問,請與我聯繫。

相關文章
相關標籤/搜索