如何在控件外部单击时关闭Silverlight中的弹出窗口?

时间:2010-02-23 22:25:02

标签: c# silverlight silverlight-3.0 event-handling popup

在我的Silverlight UI中,我有一个按钮,当点击时会弹出一个带有一些过滤参数的控件。当你在它外面点击时,我希望这个控件隐藏起来。换句话说,它应该以类似于组合框的方式运行,但它不是组合框(您不选择其中的项目)。这是我试图捕获控件之外的点击以解除它的方式:

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

不幸的是,MouseLeftButtonDown的事件处理程序永远不会被触发。是否有一种成熟的方法可以使弹出控件在您点击它之外时自动解除?如果没有,为什么我的MouseLeftButtonDown处理程序没有被解雇?

解决方案:

我以为我会发布我的整个解决方案以防其他人认为它有用。在我的顶级视觉中,我为弹出窗口声明了一个“盾牌”,如下所示:

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

然后,我为Popup类添加了一个扩展方法,如下所示:

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

扩展方法的用法如下:

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

7 个答案:

答案 0 :(得分:5)

一种方法是将控件放在填充整个Silverlight表面的透明画布上。单击画布时,关闭画布并控制。如果要接收鼠标事件,请确保将画布的背景画笔设置为“透明”。

我没有成功的另一种方法是在Silverlight中使用鼠标捕获并检测何时在弹出窗口外单击鼠标。

答案 1 :(得分:4)

您是否在RootVisual上设置了背景颜色?

答案 2 :(得分:3)

我刚刚创造了类似的东西,希望这可以提供任何帮助。 :)

在我的PopUp控件中,我有一个Border,其中包含一个包含许多文本框的Grid。我将Border命名为'PopUpBorder'。

在我的UserControl的构造函数中,我有,

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

看起来它按预期工作。如果您的情况不起作用,请告诉我。

答案 3 :(得分:1)

在第一次单击时,调用控件上的CaptureMouse()方法。然后在第二次单击时调用ReleaseMouseCapture()。

答案 4 :(得分:1)

建议的解决方案使用特殊的屏蔽画布来阻止对其他控件的输入。 这很好,但我正在尝试实现一个通用控件,而不能依赖于这样的盾牌画布的存在。 MouseLeftButtonDown事件在root visual上的绑定对我来说也不起作用,因为我画布上的现有按钮仍然会触发自己的click事件而不是根视觉上的MouseLeftButtonDown。 我在这篇冰文章中找到了解决方案:来自Kent Boograart的 Popup.StaysOpen in Silverlight 。 这个问题的主要方法是:

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

我仍然不确定,为什么这个解决方案适合我,而不是这个:

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

看起来FindHighestAncestor方法只返回root视觉效果?但那么差异必须是事件处理程序? 我猜主要区别在于AddHandler方法的最后一个参数,在这种情况下是正确的:

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

MSDN文档说:

  

handledEventsToo

     

键入: System.Boolean

     

如果注册处理程序,即使路由事件被标记为已处理,也会调用它   在其事件数据中; false以使用默认条件注册处理程序   如果路由事件已标记为已处理,则调用此方法。默认值为false。不要经常问   重新处理路由事件。有关更多信息,请参阅备注。

答案 5 :(得分:0)

更简单的替代方法(尽管不完全是你要求的)将关闭MouseLeave上的弹出窗口。 Popup对象上的MouseLeave本身不起作用,但Poplet中最高级容器上的MouseLeave确实有效。

答案 6 :(得分:0)

我在我的博客上发布了一个解决方案,你可以看到这里的帖子 - Silverlight: close Popup on click outside。正如您所看到的,使用非常简单 - 它是您在弹出窗口中添加的附加属性。你没有必要添加任何包装,如果你有或没有背景颜色,你不必小心......我的代码将负责所有。