开发者

Force close all open popups from code

I want to cause all open popups (with StaysOpen == false) to close from code. Basically I want to simulate the user clicking the mouse (which would close the popups) from code.

I don't need to actually simulate the click, I just need the resulting behavior. I've thought about just go开发者_运维百科ing through the visual tree looking for popups and closing each one, but that doesn't seem like the cleanest approach.

Thanks in advance for any help or opinions.


A WPF popup actually creates a new window (a Win32 window, not a WPF Window instance). So you can't find it in the Application.Windows collection, but you can probably find it using a Win32 API like EnumChildWindows.

Once you have the handle, you can retrieve the associated HwndSource. I think the RootVisual of the HwndSource is the Popup (didn't check, you might have to look deeper in the visual tree).

So the code should be similar to this (completely untested):

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);

}


Going through the visual tree is the way to do it that isn't dependent in any way on how you've created them in the first place. There may be much cleaner ways of doing it, but they all really depend on your implementation.

For instance, all of the Popups in my application are bound to view model objects that expose some kind of IsOpen property that the Popup binds to. If I needed to implement this functionality in my project, I'd just maintain a collection of these objects (or a generator method) that I could iterate over, and set IsOpen to false on each. This obviously won't work if you aren't building your UI the way I am, though.


The accepted answer (https://stackoverflow.com/a/3886139/12885902) wasn't doing the trick for me, because source.RootVisual was never of type Popup but of the internal type PopupRoot. The following tweak got the code to work:

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);

Please also keep in mind that the CloseAllPopups() method has to be called by the main UI thread (e.g. Application.Current.Dispatcher.Invoke())!


My previous answer was also not working all the time. It only worked when the Visual Studio debugger was attached to the process. What ended up working in any case is the following:

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.SetCurrentValue(Popup.IsOpenProperty, false));
});


  1. Declare somewhere static array of opened popups:

    static List<Popup> openedPopups = new List<Popup>();

  2. Before open popup close all previsiously opened popups:

    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 }

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜