Как закрыть всплывающее окно в 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"/>
  </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();
}

person Jacob    schedule 23.02.2010    source источник
comment
Ваше решение сработало для меня. Лучше создать интерфейс с одним свойством PopupShield и использовать его в методе MakeAutoDismissing. Ваш MainPage или другой UserControl должен будет реализовать свойство и вернуть прозрачный Canvas. (Почему бы не опубликовать свое решение в качестве ответа, чтобы люди могли проголосовать за него?)   -  person kgiannakakis    schedule 14.01.2011


Ответы (7)


Вы установили цвет фона на вашем RootVisual?

person Glenn Sandoval    schedule 24.02.2010
comment
Хорошая мысль. Я попробую завтра. - person Jacob; 24.02.2010
comment
Когда у меня был непрозрачный фон, я начал получать события нажатия мыши. - person Jacob; 24.02.2010
comment
Это также должно сработать, если в качестве цвета фона использовать «Прозрачный». - person Ashley Davis; 28.10.2011

Один из способов - поместить управление на прозрачный холст, который заполняет всю поверхность Silverlight. При щелчке по холсту закройте холст и выполните управление. Если вы хотите получать события мыши, важно убедиться, что кисть фона холста установлена ​​на «Прозрачный».

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

person Doug Ferguson    schedule 23.02.2010
comment
Хотел бы я принять и этот ответ. Проблема с простым захватом событий в корневом визуале заключается в том, что дочерние элементы управления, такие как кнопки, блокируют события нажатия мыши. Поэтому я создал холст верхнего уровня, который будет располагаться чуть ниже всплывающих элементов управления. При отображении всплывающего окна я делаю этот щит видимым и сворачиваю, когда всплывающее окно закрывается. - person Jacob; 24.02.2010
comment
Спасибо, что сообщили мне, что ответ вам помог. - person Doug Ferguson; 24.02.2010

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

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

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

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

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

person Justin XL    schedule 11.04.2011
comment
Разве он не подписывается на одно и то же событие каждый раз, когда мышь покидает границу всплывающего окна? Может, это приводит к ненужному использованию памяти? - person synergetic; 17.08.2011
comment
Мне очень нравится этот ответ за его простоту. - person David Faivre; 25.05.2012

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

person Alun Harford    schedule 23.02.2010
comment
Я хотел бы сделать что-нибудь настолько простое, но у меня это не работает. У вас есть более конкретные инструкции, как это сделать, или пример кода? - person Jacob; 24.02.2010

Предлагаемое решение использует специальное полотно щита для блокировки ввода в другие элементы управления. Это нормально, но я пытаюсь реализовать общий элемент управления и не могу зависеть от существования такого полотна щита. Привязка события MouseLeftButtonDown к корневому визуалу у меня тоже не сработала, потому что существующие кнопки на моем холсте по-прежнему будут запускать собственное событие щелчка, а не MouseLeftButtonDown на корневом визуале. Я нашел решение в этой ледяной статье: Popup.StaysOpen в 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 просто вернет корневой визуал? Но тогда разница должна быть в обработчике событий? Думаю, основное отличие - это последний параметр метода AddHandler, который верен в случае:

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

Документы MSDN говорят:

handledEventsToo

Тип: System.Boolean

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

person stefan.s    schedule 04.05.2011

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

person beameup    schedule 22.09.2011

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

person shemesh    schedule 09.12.2013