How to dismiss a popup in Silverlight when clicking outside of the control?

点点圈 提交于 2019-12-18 11:45:54

问题


In my Silverlight UI, I have a button that when clicked pops up a control with some filtering parameters. I would like this control to hide itself when you click outside of it. In other words, it should function in a manner similar to a combo box, but it's not a combo box (you don't select an item in it). Here's how I'm trying to capture a click outside of the control to dismiss it:

public partial class MyPanel : UserControl
{
    public MyPanel()
    {
        InitializeComponent();
    }

    private void FilterButton_Click(object sender, RoutedEventArgs e)
    {
        // Toggle the open state of the filter popup
        FilterPopup.IsOpen = !FilterPopup.IsOpen;
    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        // Capture all clicks and close the popup
        App.Current.RootVisual.MouseLeftButtonDown += delegate {
            FilterPopup.IsOpen = false; };
    }
}

Unfortunately, the event handler for MouseLeftButtonDown is never getting fired. Is there a well-established way of making a popup control that auto-dismisses when you click outside of it? If not, why isn't my MouseLeftButtonDown handler firing?

Solution:

I thought I'd post my entire solution in case others find it helpful. In my top-level visual, I declare a "shield" for the popups, like this:

<UserControl xmlns:my="clr-namespace:Namespace"
    x:Class="Namespace.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" 
    xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
>
  <Grid Background="Black" HorizontalAlignment="Stretch" 
          VerticalAlignment="Stretch">
    <my:MyStuff/>
    <Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
            x:Name="PopupShield" Background="Transparent" Width="Auto" 
            Height="Auto" Visibility="Collapsed"/>
  </Grid>
</UserControl>

Then, I added an extension method for the Popup class, like this:

public static class PopupUtils
{
    public static void MakeAutoDismissing(this Popup popup)
    {
        var shield = (App.Current.RootVisual as MainPage).PopupShield;

        // Whenever the popup opens, deploy the shield
        popup.HandlePropertyChanges(
            "IsOpen",
            (s, e) =>
            {
                shield.Visibility = (bool)e.NewValue 
                    ? Visibility.Visible : Visibility.Collapsed;
            }
        );

        // Whenever the shield is clicked, dismiss the popup
        shield.MouseLeftButtonDown += (s, e) => popup.IsOpen = false;
    }
}

public static class FrameworkUtils
{
    public static void HandlePropertyChanges(
        this FrameworkElement element, string propertyName, 
        PropertyChangedCallback callback)
    {
        //Bind to a depedency property
        Binding b = new Binding(propertyName) { Source = element };
        var prop = System.Windows.DependencyProperty.RegisterAttached(
            "ListenAttached" + propertyName,
            typeof(object),
            typeof(UserControl),
            new System.Windows.PropertyMetadata(callback));

        element.SetBinding(prop, b);
    }
}

The extension method is used like this:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    FilterPopup.MakeAutoDismissing();
}

回答1:


Did you set a background color on your RootVisual?




回答2:


One way is to put your control on a transparent canvas that fills the entire Silverlight surface. When the canvas is clicked close the canvas and control. It is important to ensure that the Background brush of the canvas is set to "Transparent" if you want to receive mouse events.

An alternate method that I have not had success with is using mouse capture in Silverlight and detecting when the mouse is clicked outside of the popup.




回答3:


I just created something similar and hopefully this could be of any help. :)

In my PopUp control, I have a Border which has a Grid that contains a number of textboxes. I named the Border 'PopUpBorder'.

In my UserControl's constructor, I have,

        this.PopUpBorder.MouseLeave += (s, e) =>
        {
            Application.Current.RootVisual.MouseLeftButtonDown += (s1, e1) =>
            {
                this.PopUp.IsOpen = false;
            };
        };

And looks like it is working as expected. Please let me know if this doesn't work in your case.




回答4:


On the first click, call the CaptureMouse() method on the control. Then call ReleaseMouseCapture() on the second click.




回答5:


The proposed solution uses a special shield canvas to block input to other controls. This is fine, but I am trying implement a generic control and cannot depend on the existence of such a shield canvas. The binding of the MouseLeftButtonDown event on the root visual didn't work for me either, because existing buttons on my canvas would still fire their own click event and not the MouseLeftButtonDown on the root visual. I found the solution in this ice article: Popup.StaysOpen in Silverlight from Kent Boograart. The main methods for this question are:

        private void OnPopupOpened(object sender, EventArgs e)
        {
            var popupAncestor = FindHighestAncestor(this.popup);

            if (popupAncestor == null)
            {
                return;
            }

            popupAncestor.AddHandler(Windows.Popup.MouseLeftButtonDownEvent, (MouseButtonEventHandler)OnMouseLeftButtonDown, true);
        }

        private void OnPopupClosed(object sender, EventArgs e)
        {
            var popupAncestor = FindHighestAncestor(this.popup);

            if (popupAncestor == null)
            {
                return;
            }

            popupAncestor.RemoveHandler(Windows.Popup.MouseLeftButtonDownEvent, (MouseButtonEventHandler)OnMouseLeftButtonDown);
        }

        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // in lieu of DependencyObject.SetCurrentValue, this is the easiest way to enact a change on the value of the Popup's IsOpen
            // property without overwriting any binding that may exist on it
            var storyboard = new Storyboard() { Duration = TimeSpan.Zero };
            var objectAnimation = new ObjectAnimationUsingKeyFrames() { Duration = TimeSpan.Zero };
            objectAnimation.KeyFrames.Add(new DiscreteObjectKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero), Value = false });
            Storyboard.SetTarget(objectAnimation, this.popup);
            Storyboard.SetTargetProperty(objectAnimation, new PropertyPath("IsOpen"));
            storyboard.Children.Add(objectAnimation);
            storyboard.Begin();
        }
    private static FrameworkElement FindHighestAncestor(Popup popup)
    {
        var ancestor = (FrameworkElement)popup;

        while (true) {
            var parent = VisualTreeHelper.GetParent(ancestor) as FrameworkElement;

            if (parent == null) {
                return ancestor;
            }

            ancestor = parent;
        }
    }

I'm still not sure, why this solution works for me and this not:

    Application.Current.RootVisual.MouseLeftButtonDown += (s1, e1) =>
    {
        this.PopUp.IsOpen = false;
    };

It looks like the FindHighestAncestor method will just return the root visual? But then the difference must be the event handler? I guess the main difference is the last parameter of the AddHandler method which is true in the case:

AddHandler(Windows.Popup.MouseLeftButtonDownEvent, (MouseButtonEventHandler)OnMouseLeftButtonDown, true);

The MSDN docs say:

handledEventsToo

Type: System.Boolean

true to register the handler such that it is invoked even when the routed event is marked handled in its event data; false to register the handler with the default condition that it will not be invoked if the routed event is already marked handled. The default is false. Do not routinely ask to rehandle a routed event. For more information, see Remarks.




回答6:


A simpler alternative (although not exactly what you asked for) would be to close the popup on MouseLeave. MouseLeave on the Popup object itself wont work, but MouseLeave on the highest level container within the Popup does.




回答7:


i have posted a solution for this on my blog, you can see the post here - Silverlight: close Popup on click outside. as you can see the use is very simple - it is an attached property you add on your popup. you do not have to add any wrappers, you do not have to take care if you do or do not have some background color... my code will take care of it all.



来源:https://stackoverflow.com/questions/2322231/how-to-dismiss-a-popup-in-silverlight-when-clicking-outside-of-the-control

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!