Как стереть все «вокруг» данного элемента управления?

#wpf #user-interface

#wpf #пользовательский интерфейс

Вопрос:

В моем приложении мне нужно некоторое модальное поведение в определенных ситуациях, т. Е. пользователю должно быть разрешено взаимодействовать только с определенным элементом пользовательского интерфейса (например, со всеми элементами управления внутри группового поля). Я не хочу использовать модальные диалоговые окна, поэтому я пытаюсь найти способ «отключить» все, кроме элемента управления, который должен оставаться активным, предпочтительно затемнив все остальное (тем самым придав визуальный фокус рассматриваемому элементу управления).

Как можно добиться такого поведения? Пожалуйста, обратите внимание, что элемент, который должен стать модальным, всегда является частью пользовательского интерфейса, поэтому я не могу просто поместить его на оверлей или что-то подобное.

Я наткнулся на декораторов и оформителей, но информации о них довольно мало…

Ответ №1:

Вы могли бы применить наложение ко всему окну и установить OpacityMask этого наложения таким образом, чтобы оно было прозрачным над элементом, который должен быть модальным. Я постараюсь опубликовать пример через несколько минут.


РЕДАКТИРОВАТЬ: Хорошо, это немного сложнее, чем я ожидал… Вот несколько рабочее, но уродливое решение:

     private Grid _modalOverlay;

    private void btnShowOverlay_Click(object sender, RoutedEventArgs e)
    {
        if (_modalOverlay != null)
            root.Children.Remove(_modalOverlay);
        _modalOverlay = MakeModalOverlay(groupBox1, root, 0.5);
        root.Children.Add(_modalOverlay);
    }

    private static Grid MakeModalOverlay(FrameworkElement element, FrameworkElement root, double opacity)
    {
        var offset = GetRelativeOffset(element, root);

        Grid g = new Grid();

        var c0 = new ColumnDefinition();
        c0.Width = new GridLength(offset.X);
        var c1 = new ColumnDefinition();
        c1.Width = new GridLength(element.ActualWidth);
        var c2 = new ColumnDefinition();
        c2.Width = new GridLength(root.ActualWidth - element.ActualWidth - offset.X);

        var r0 = new RowDefinition();
        r0.Height = new GridLength(offset.Y);
        var r1 = new RowDefinition();
        r1.Height = new GridLength(element.ActualHeight);
        var r2 = new RowDefinition();
        r2.Height = new GridLength(root.ActualHeight - element.ActualHeight - offset.Y);

        g.ColumnDefinitions.Add(c0);
        g.ColumnDefinitions.Add(c1);
        g.ColumnDefinitions.Add(c2);
        g.RowDefinitions.Add(r0);
        g.RowDefinitions.Add(r1);
        g.RowDefinitions.Add(r2);

        Brush b = new SolidColorBrush(Colors.Black) { Opacity = opacity };
        for (int i = 0; i < 3; i  )
        for (int j = 0; j < 3; j  )
        {
            if (i == 1 amp;amp; j == 1)
                continue;

            Rectangle r = new Rectangle();
            r.Fill = b;
            Grid.SetColumn(r, i);
            Grid.SetRow(r, j);
            g.Children.Add(r);
        }

        Panel.SetZIndex(g, int.MaxValue);

        return g;
    }

    private static Vector GetRelativeOffset(Visual visual, Visual ancestor)
    {
        Visual tmp = visual;
        Vector offset = default(Vector);
        while (tmp != ancestor)
        {
            offset  = VisualTreeHelper.GetOffset(tmp);

            tmp = (Visual) VisualTreeHelper.GetParent(tmp);
            if (tmp == null)
                throw new ArgumentException("ancestor is not an visual ancestor of visual");
        }
        return offset;
    }

    private void btnHideOverlay_Click(object sender, RoutedEventArgs e)
    {
        if (_modalOverlay != null)
            root.Children.Remove(_modalOverlay);
    }
  

В приведенном выше коде root является корневой панелью окна.

Это решение вроде как работает, но у него есть две основные проблемы:

  • Он не поддерживает изменение размера; вероятно, это можно было бы решить, привязав ширину столбца и высоту строки к сетке наложения с помощью конвертеров, но это не очень просто
  • Это не позволяет вам нажимать на другие элементы управления, но вы все равно можете взаимодействовать с ними с помощью клавиатуры. Я думаю, что единственный способ предотвратить это — фактически отключить их…

Комментарии:

1. Обработка события изменения размера окна и воссоздание наложения в нем, похоже, довольно прилично с точки зрения производительности, и это без какой-либо оптимизации… теперь, чтобы найти способ легко отключить все элементы управления…

Ответ №2:

Я бы выбрал Adorner, потому что это элемент WPF, для которого указано msdn, который можно использовать для «визуальной маскировки или переопределения части или всего пользовательского элемента» (смотрите здесь).

Хорошую отправную точку можно найти в этом сообщении в блоге, где вы, вероятно, захотите исключить из отображаемой области область пользовательского элемента, которую хотите подчеркнуть. (На этом этапе вы даже можете создать визуальную кисть для всего окна и нарисовать ею декоратор, если хотите получить классный эффект, но в противном случае для этого подойдет сплошная кисть с непрозрачностью около 0,5).

Томас очень хорошо заметил, что когда включено это «модальное» поведение, вы должны отключить все элементы управления, с которыми пользователь не должен взаимодействовать, потому что только их отключение гарантирует, что эти элементы управления никак не реагируют на пользовательский ввод (или, по крайней мере, делают их не сфокусированными, но я думаю, что это может повлиять на их предыдущее состояние, когда вы вернетесь из «модального» состояния).