【发布时间】:2020-05-29 14:44:19
【问题描述】:
我想让所有打开的弹出窗口(StaysOpen == false)从代码中关闭。基本上我想从代码中模拟用户点击鼠标(这将关闭弹出窗口)。
我不需要实际模拟点击,我只需要结果行为。我曾想过只是通过可视化树来寻找弹出窗口并关闭每个弹出窗口,但这似乎不是最干净的方法。
提前感谢任何帮助或意见。
【问题讨论】:
我想让所有打开的弹出窗口(StaysOpen == false)从代码中关闭。基本上我想从代码中模拟用户点击鼠标(这将关闭弹出窗口)。
我不需要实际模拟点击,我只需要结果行为。我曾想过只是通过可视化树来寻找弹出窗口并关闭每个弹出窗口,但这似乎不是最干净的方法。
提前感谢任何帮助或意见。
【问题讨论】:
WPF 弹出窗口实际上创建了一个新窗口(Win32 窗口,而不是 WPF Window 实例)。所以你在Application.Windows 集合中找不到它,但你可以使用像EnumChildWindows 这样的Win32 API 找到它。
获得句柄后,您可以检索关联的HwndSource。我认为HwndSource 中的RootVisual 是Popup(没有检查,您可能需要深入查看可视化树)。
所以代码应该是这样的(完全未经测试):
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);
}
【讨论】:
遍历可视化树是一种完全不依赖于您最初创建它们的方式的方法。可能有更简洁的方法,但它们都取决于您的实现。
例如,我的应用程序中的所有 Popup 都绑定到视图模型对象,这些对象公开了 Popup 绑定到的某种 IsOpen 属性。如果我需要在我的项目中实现这个功能,我只需维护一个可以迭代的这些对象(或生成器方法)的集合,并将每个对象上的 IsOpen 设置为 false。但是,如果您不按照我的方式构建 UI,这显然是行不通的。
【讨论】:
接受的答案 (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 调试器附加到进程时才有效。无论如何,最终的结果如下:
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 => popup.SetCurrentValue(Popup.IsOpenProperty, false)。另外,不需要.Where(popup => popup.IsOpen)。
在某处声明打开的弹出窗口的静态数组:
static List<Popup> openedPopups = new List<Popup>();
在打开弹出窗口之前关闭所有以前打开的弹出窗口:
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
}
【讨论】: