Элемент Popup в WinRT

70

Класс Popup (производный от FrameworkElement) — самый близкий аналог традиционных диалоговых окон в Windows Runtime. Класс Popup содержит свойство Child типа Element, которому обычно задается элемент Panel с набором элементов управления, и элемент Border с дочерним элементом Panel.

Следующий пример функционально эквивалентен примеру из предыдущей статьи, где мы использовали PopupMenu, а между их файлами XAML тоже существует много общего:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Name="textBlock"
                   FontSize="24"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   TextAlignment="Center"
                   RightTapped="OnTextBlockRightTapped">
            Простое диалоговое меню
            <LineBreak />
            <LineBreak />
            (щелкните правой кнопкой мыши или нажмите и удерживайте палец на сенсорном экране)
        </TextBlock>
</Grid>

Обработчик события RightTapped элемента TextBlock собирает два элемента управления Button и три элемента управления RadioButton на панели StackPanel, которая назначается потомком Border и задается свойству Child объекта Popup. Это основная часть кода; обработчик Click элементов управления Button и обработчик Checked элементов RadioButton получаются совсем короткими:

using System;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        private void OnTextBlockRightTapped(object sender, RightTappedRoutedEventArgs args)
        {
            StackPanel stackPanel = new StackPanel();

            // Создание двух элементов управления Button
            // и добавление их на панель StackPanel
            Button btn1 = new Button
            {
                Content = "Крупный шрифт",
                Tag = 1.2,
                HorizontalAlignment = HorizontalAlignment.Center,
                Margin = new Thickness(12)
            };

            btn1.Click += OnButtonClick;
            stackPanel.Children.Add(btn1);

            Button btn2 = new Button
            {
                Content = "Мелкий шрифт",
                Tag = 1 / 1.2,
                HorizontalAlignment = HorizontalAlignment.Center,
                Margin = new Thickness(12)
            };

            btn2.Click += OnButtonClick;
            stackPanel.Children.Add(btn2);

            // Создание трех элементов управления RadioButton 
            // и добавление их на панель  StackPanel
            string[] names = { "Красный", "Зеленый", "Синий" };
            Color[] colors = { Colors.Red, Colors.Green, Colors.Blue };

            for (int i = 0; i < names.Length; i++)
            {
                RadioButton radioButton = new RadioButton
                {
                    Content = names[i],
                    Foreground = new SolidColorBrush(colors[i]),
                    IsChecked = (textBlock.Foreground as SolidColorBrush).Color == colors[i],
                    Margin = new Thickness(12)
                };
                radioButton.Checked += OnRadioButtonChecked;
                stackPanel.Children.Add(radioButton);
            }

            // Создание элементы Border для StackPanel
            Border border = new Border
            {
                Child = stackPanel,
                Background = this.Resources["ApplicationPageBackgroundThemeBrush"] as SolidColorBrush,
                BorderBrush = this.Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush,
                BorderThickness = new Thickness(1),
                Padding = new Thickness(24),
            };

            // Создание объекта Popup
            Popup popup = new Popup
            {
                Child = border,
                IsLightDismissEnabled = true
            };

            // Изменение позиции по размеру содержимого
            border.Loaded += (loadedSender, loadedArgs) =>
            {
                // Изначально получить позицию сверху слева
                Point point = args.GetPosition(this);
                point.X -= border.ActualWidth / 2;
                point.Y -= border.ActualHeight;

                // Оставить поля не менее четверти дюйма
                popup.HorizontalOffset =
                    Math.Min(this.ActualWidth - border.ActualWidth - 24,
                        Math.Max(24, point.X));

                popup.VerticalOffset =
                    Math.Min(this.ActualHeight - border.ActualHeight - 24,
                        Math.Max(24, point.Y));

                // Фокус ввода передается первому элементу
                btn1.Focus(FocusState.Programmatic);
            };

            // Открыть всплывающее окно
            popup.IsOpen = true;
        }

        private void OnButtonClick(object sender, RoutedEventArgs args)
        {
            textBlock.FontSize *= (double)(sender as Button).Tag;
        }

        private void OnRadioButtonChecked(object sender, RoutedEventArgs args)
        {
            textBlock.Foreground = (sender as RadioButton).Foreground;
        }
    }
}

Чтобы позиционировать объект Popup на экране, необходимо задать значения свойств HorizontalOffset и VerticalOffset относительно окна программы. Но чтобы выбор значений этих свойств был осмысленным, необходимо знать размер содержимого Popup, а эта информация обычно остается недоступной до появления Popup на экране. По этой причине код назначает обработчик Loaded для элемента Border, являющегося элементом содержимого Popup. Далее объект Popup выравнивается по центру над точкой касания (по аналогии с тем, как это делалось для PopupMenu), но я также добавил поля размером не менее 24 пикселов между Popup и окном программы.

Обработчик RightTapped завершается заданием свойству IsOpen объекта Popup значения true, в результате чего объект Popup появляется на экране. Обычно при этом пользователь может взаимодействовать с остальными компонентами страницы программы. Однако обратите внимание на то, что свойству IsLightDismissEnabled объекта Popup задано значение true. Это позволяет закрыть объект Popup щелчком или касанием за его пределами или нажатием клавиши Esc. Без задания этого свойства на экране могут отображаться несколько экземпляров этого окна, а программа должна явно убирать их с экрана, задавая их свойству IsOpen значение false (вероятно, по событию одного из дочерних элементов управления). Класс Popup также определяет свойства Opened и Closed, если эта информация понадобится вам для инициализации или очистки.

Результат щелчка (размер шрифта был предварительно увеличен):

Вызов контекстного меню с помощью Popup

Для перехода между экранными элементами используется клавиша Tab. По умолчанию эти диалоговые окна используют ту же цветовую тему, что и приложение.

В диалоговом окне нет кнопки OK или Cancel. Вместо этого я реализовал его так, чтобы нажатие кнопок немедленно изменяло нижележащее изображение, а объект Popup закрывался щелчком или касанием за его пределами. В более сложное диалоговое окно можно включить кнопку восстановления настроек элементов по умолчанию.

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

Лучший чат для C# программистов