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

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

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处理程序不会被触发?

解:

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

       

然后,我为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(); } 

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

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

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

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

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

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

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

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

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

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

  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。 不要经常要求重新处理路由事件。 有关更多信息,请参阅备注。

一个更简单的替代方法(虽然不是你要求的)将是关闭MouseLeave上的弹出窗口。 Popup对象上的MouseLeave本身不起作用,但是Popup中最高级别容器上的MouseLeave会起作用。

我已经在我的博客上发布了一个解决方案,你可以看到这里的post – Silverlight:点击外面点击Popup 。 正如您所看到的,使用非常简单 – 它是您在弹出窗口中添加的附加属性。 你没有必要添加任何包装,如果你有或没有背景颜色,你不必小心……我的代码会照顾好这一切。