Как отключить всплывающее окно в Silverlight при нажатии за пределами элемента управления? - PullRequest
22 голосов
/ 24 февраля 2010

В моем интерфейсе 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();
}

Ответы [ 7 ]

5 голосов
/ 24 февраля 2010

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

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

4 голосов
/ 24 февраля 2010

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

3 голосов
/ 11 апреля 2011

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

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

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

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

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

1 голос
/ 04 мая 2011

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

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

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

handledEventsToo

Введите: System.Boolean

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

1 голос
/ 24 февраля 2010

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

0 голосов
/ 09 декабря 2013

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

0 голосов
/ 22 сентября 2011

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

...