【问题标题】:Force close all open popups from code从代码中强制关闭所有打开的弹出窗口
【发布时间】:2020-05-29 14:44:19
【问题描述】:

我想让所有打开的弹出窗口(StaysOpen == false)从代码中关闭。基本上我想从代码中模拟用户点击鼠标(这将关闭弹出窗口)。

我不需要实际模拟点击,我只需要结果行为。我曾想过只是通过可视化树来寻找弹出窗口并关闭每个弹出窗口,但这似乎不是最干净的方法。

提前感谢任何帮助或意见。

【问题讨论】:

    标签: c# wpf xaml


    【解决方案1】:

    WPF 弹出窗口实际上创建了一个新窗口(Win32 窗口,而不是 WPF Window 实例)。所以你在Application.Windows 集合中找不到它,但你可以使用像EnumChildWindows 这样的Win32 API 找到它。

    获得句柄后,您可以检索关联的HwndSource。我认为HwndSource 中的RootVisualPopup(没有检查,您可能需要深入查看可视化树)。

    所以代码应该是这样的(完全未经测试):

    public static class PopupCloser
    {
        public static void CloseAllPopups()
        {
            foreach(Window window in Application.Current.Windows)
            {
                CloseAllPopups(window);
            }
        }
    
        public static void CloseAllPopups(Window window)
        {
            IntPtr handle = new WindowInteropHelper(window).Handle;
            EnumChildWindows(handle, ClosePopup, IntPtr.Zero);
        }
    
        private static bool ClosePopup(IntPtr hwnd, IntPtr lParam)
        {
            HwndSource source = HwndSource.FromHwnd(hwnd);
            if (source != null)
            {
                Popup popup = source.RootVisual as Popup;
                if (popup != null)
                {
                    popup.IsOpen = false;
                }
            }
            return true; // to continue enumeration
        }
    
        private delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
    
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
    
    }
    

    【讨论】:

    • ClosePopup 签名有 void() 但你返回 bool。
    • 我遇到过一些特殊情况,其中 popup.IsOpen=false 不会做任何事情。如果有人面临类似的问题,我建议您使用 winapi @(0,0) 生成鼠标点击。这将关闭 StaysOpen=false 弹出窗口。
    • 我无法让 EnumChildWindows 工作,最终改用 EnumThreadWindows。
    【解决方案2】:

    遍历可视化树是一种完全不依赖于您最初创建它们的方式的方法。可能有更简洁的方法,但它们都取决于您的实现。

    例如,我的应用程序中的所有 Popup 都绑定到视图模型对象,这些对象公开了 Popup 绑定到的某种 IsOpen 属性。如果我需要在我的项目中实现这个功能,我只需维护一个可以迭代的这些对象(或生成器方法)的集合,并将每个对象上的 IsOpen 设置为 false。但是,如果您不按照我的方式构建 UI,这显然是行不通的。

    【讨论】:

      【解决方案3】:

      接受的答案 (https://stackoverflow.com/a/3886139/12885902) 对我没有用,因为source.RootVisual 从来不是Popup 类型,而是内部类型PopupRoot。以下调整使代码工作:

      private void CloseAllPopups()
      {
          foreach (Window window in Application.Current.Windows)
          {
              IntPtr handle = new WindowInteropHelper(window).Handle;
              EnumThreadWindows(handle, ClosePopup, IntPtr.Zero);
          }
      }
      
      private static bool ClosePopup(IntPtr hwnd, IntPtr lParam)
      {
          HwndSource source = HwndSource.FromHwnd(hwnd);
          if (source?.RootVisual?.GetType().Name == "PopupRoot")
          {
              if (LogicalTreeHelper.GetParent(source.RootVisual) is Popup popup)
              {
                  popup.IsOpen = false;
              }
          }
          return true; // to continue enumeration
      }
      
      private delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
      
      [DllImport("user32.dll")]
      [return: MarshalAs(UnmanagedType.Bool)]
      private static extern bool EnumThreadWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
      

      还请记住,CloseAllPopups() 方法必须由主 UI 线程调用(例如 Application.Current.Dispatcher.Invoke())!

      【讨论】:

      • 我发现在我的情况下,即使是这个解决方案也只有在附加了 Visual Studio 的调试器的情况下才有效。请参阅我的其他答案以获得真正的解决方案
      【解决方案4】:

      我之前的回答也不是一直有效。它仅在将 Visual Studio 调试器附加到进程时才有效。无论如何,最终的结果如下:

      Application.Current.Dispatcher.Invoke(() =>
      {
          PresentationSource.CurrentSources.OfType<HwndSource>()
              .Select(h => h.RootVisual)
              .OfType<FrameworkElement>()
              .Select(f => f.Parent)
              .OfType<Popup>()
              .Where(popup => popup.IsOpen)
              .ToList()
              .ForEach(popup => popup.IsOpen = false);
      });
      

      【讨论】:

      • 不错的解决方案。但是,要保留任何IsOpen 绑定,您应该改用popup =&gt; popup.SetCurrentValue(Popup.IsOpenProperty, false)。另外,不需要.Where(popup =&gt; popup.IsOpen)
      【解决方案5】:
      1. 在某处声明打开的弹出窗口的静态数组:

        static List&lt;Popup&gt; openedPopups = new List&lt;Popup&gt;();

      2. 在打开弹出窗口之前关闭所有以前打开的弹出窗口:

        private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // close all before opene popup openedPopups.ForEach(p => p.IsOpen = false); openedPopups.Clear(); // clear opened popus's collection, because they were closed Popup1.IsOpen = true; // open popup I need open now openedPopups.Add(Popup1); // remember it for future close }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-12
        • 1970-01-01
        • 1970-01-01
        • 2023-04-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多