在我的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();
}
答案 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。正如您所看到的,使用非常简单 - 它是您在弹出窗口中添加的附加属性。你没有必要添加任何包装,如果你有或没有背景颜色,你不必小心......我的代码将负责所有。