Как отклонить всплывающее окно в Silverlight при нажатии вне элемента управления?

В моем пользовательском интерфейсе Silverlight у меня есть кнопка, которая при щелчке выдает элемент управления с некоторыми параметрами фильтрации. Я бы хотел, чтобы этот элемент управления спрятался, когда вы нажимаете на него. Другими словами, он должен функционировать так же, как и поле со списком, но это не поле со списком (вы не выбираете в нем элемент). Здесь, как я пытаюсь захватить клик за пределами элемента управления, чтобы отклонить его:

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">
 </canvas></my:mystuff></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 ответов

Установили ли вы цвет фона в RootVisual?


Одним из способов является управление прозрачным холстом, заполняющим всю поверхность Silverlight. Когда холст щелкнут, закройте холст и элемент управления. Важно обеспечить, чтобы для кисти фона была выбрана "Прозрачная", если вы хотите получать события мыши.

Альтернативный метод, с которым у меня не было успеха, - это захват мыши в Silverlight и обнаружение, когда мышь нажата за пределами всплывающего окна.


Я только что создал нечто похожее, и, надеюсь, это может помочь.:)

В моем элементе управления PopUp у меня есть Border, у которого есть Grid, который содержит несколько текстовых полей. Я назвал Border 'PopUpBorder'.

В моем конструкторе UserControl у меня есть

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

И похоже, что он работает так, как ожидалось. Пожалуйста, дайте мне знать, если это не сработает в вашем случае.


При первом нажатии вызовите метод CaptureMouse() в элементе управления. Затем вызовите ReleaseMouseCapture() во втором клике.


В предлагаемом решении используется специальный экранный экран для блокировки ввода других элементов управления. Это прекрасно, но я пытаюсь реализовать общий контроль и не могу зависеть от существования такого экрана. Связывание события MouseLeftButtonDown с корневым визуалом не сработало для меня, потому что существующие кнопки на моем холсте все равно запускают собственное событие click, а не MouseLeftButtonDown на корневом изображении. Я нашел решение в этой статье: Popup.StaysОткрыть в Silverlight от Kent Boograart. Основными методами для этого вопроса являются:

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 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 просто вернет корневую визуализацию? Но тогда разница должна быть обработчиком событий? Я думаю, основное отличие - последний параметр метода AddHandler, который истинен в случае:

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

В документах MSDN говорится:

handledEventsToo

Тип: System.Boolean

true для регистрации обработчика таким образом, что он вызывается, даже когда маршрутизируемое событие помечено обработанным в его данных о событиях; false, чтобы зарегистрировать обработчик с условием по умолчанию, чтобы он не был вызывается, если маршрутизируемое событие уже отмечено обработанным. По умолчанию используется значение false. Не регулярно спрашивайте для перенаправления маршрутизируемого события. Для получения дополнительной информации см. Примечания.


Я разместил решение для этого на моем блоге, вы можете увидеть сообщение здесь - Silverlight: закрыть всплывающее окно на экране. как вы можете видеть, использование очень просто - это добавленное свойство, которое вы добавляете во всплывающее окно. вам не нужно добавлять какие-либо обертки, вам не нужно заботиться, если у вас есть или нет цвета фона... мой код позаботится обо всем этом.


Более простая альтернатива (хотя и не совсем то, что вы просили) заключалась в том, чтобы закрыть всплывающее окно на MouseLeave. MouseLeave в самом всплывающем объекте не работает, но MouseLeave на контейнере самого высокого уровня внутри всплывающего окна делает.

licensed under cc by-sa 3.0 with attribution.