Создание настраиваемого элемента управления с помощью XAML и C#

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

В последней записи, "Создание настраиваемого элемента управления с помощью библиотеки Windows для JavaScript", Джордан Маттьесен (Jordan Matthiesen) показал, как создать настраиваемый элемент управления HelloWorld. В этой записи я опишу создание такого же элемента на XAML. Я познакомлю вас со способами и понятиями, позволяющими разрабатывать настраиваемые элементы управления с возможностью многократного использования, и покажу, как создать шаблон для определения стиля этих элементов. Я опишу такие понятия, как свойства зависимостей и использование настраиваемого файла Generic.xaml для создания неявного стиля, определяющего шаблон элемента управления по умолчанию.

Простой элемент управления XAML

Сначала мы создадим простейший элемент управления (что-то вроде "Здравствуй, мир!" среди элементов управления): класс, производный от Windows.UI.XAML.Controls.Control. Создайте новый проект в Visual Studio с помощью шаблона "Пустой проект". Назовите ваш проект CustomControls. Добавьте настраиваемый элемент управления с помощью мастера создания шаблона элемента.

Visual Studio содержит шаблон элемента для элемента управления на основе шаблона. Щелкните проект правой кнопкой мыши и выберите команду "Добавить -> Новый элемент"

Visual Studio содержит шаблон элемента для элемента управления на основе шаблона. Щелкните проект правой кнопкой мыши и выберите команду "Добавить -> Новый элемент"

Шаблон элемента управления создает файлы и базовый код в качестве основы вашего элемента управления

Выберите элемент Элемент управления на основе шаблона и назовите свой элемент управления "HelloWorld.cs"

Этот шаблон создает следующий класс:

 public class HelloWorld : Control
{
    public HelloWorld()    {
        this.DefaultStyleKey = typeof(HelloWorld);
    }
}

В этом коротком блоке кода мы указали две важных детали. Во-первых, то, что класс HelloWorld является производным от Windows.UI.XAML.Controls.Control. Во-вторых, параметр DefaultStyleKey указывает платформе XAML, что этот класс использует неявный стиль. Шаблон элемента управления также добавляет папку Themes и создает в ней файл Generic.xaml. Дополнительную информацию об этом шаблоне можно прочитать в статье Шаблоны проекта.

Используемый по умолчанию стиль элемента управления определен в настраиваемом файле Generic.xaml, который платформа загружает автоматически. В этом файле вы определяете неявный стиль. Это означает, что мы можем определить один стиль, который будет по умолчанию применяться ко всем элементам управления определенного типа. Добавьте выделенный XAML-фрагмент в новый файл Generic.xaml в папке Themes:

 <ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/XAML/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/XAML"
    xmlns:local="using:CustomControls">

    <Style TargetType="local:HelloWorld">
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:HelloWorld">
                    <Border
                       Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <TextBlock Text="HelloWorld" 
                               FontFamily="Segoe UI Light"
                               FontSize="36"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style> 
</ResourceDictionary>

Эти несколько строк кода XAML определяют стиль, который будет по умолчанию применяться ко всем экземплярам элемента управления HelloWorld. Мы определяем шаблон элемента управления, который указывает, что это просто текстовый блок с текстом "HelloWorld".

Теперь перейдите в файл MainPage.xaml и добавьте следующую разметку. Необходимо добавить обозначение "local:", чтобы "сообщить" платформе XAML, в каком пространстве имен искать класс HelloWorld. Обозначение "local:" определяется в начале XAML-файла.

 <local:HelloWorld />

После запуска приложения вы увидите, как загрузится элемент управления и появится текст "Hello, World!" (Здравствуй, мир!).

Определение параметров элемента управления

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

Для этого мы добавим свойство зависимости (DP) в наш элемент управления. Дополнительную информацию о свойствах зависимостей можно прочитать в статье Обзор свойств зависимостей. Существует фрагмент кода Visual Studio, позволяющий очень просто добавлять свойства зависимостей. Расположите указатель под конструктором, введите "propdp" и дважды нажмите клавишу TAB. Вы можете задать значения, переходя к каждому параметру во фрагменте с помощью клавиши TAB.

 public class HelloWorld : Control
{
    public HelloWorld()    {
        this.DefaultStyleKey = typeof(HelloWorld);
    }

    public bool Blink
    {
        get { return (bool)GetValue(BlinkProperty); }
        set { SetValue(BlinkProperty, value); }
    }

    // Using a DependencyProperty enables animation, styling, binding, etc.
    public static readonly DependencyProperty BlinkProperty =
        DependencyProperty.Register(
            "Blink",                  // The name of the DependencyProperty
            typeof(bool),             // The type of the DependencyProperty
            typeof(HelloWorld),       // The type of the owner of the DependencyProperty
            new PropertyMetadata(     // OnBlinkChanged will be called when Blink changes
                false,                // The default value of the DependencyProperty
                new PropertyChangedCallback(OnBlinkChanged)
            )
        );

    private DispatcherTimer __timer = null;
    private DispatcherTimer _timer
    {
        get
        {
            if (__timer == null)
            {
                __timer = new DispatcherTimer();
                __timer.Interval = new TimeSpan(0,0,0,0,500); // 500 ms interval
                __timer.Tick += __timer_Tick;
            }

            return __timer;
        }
    }

    private static void OnBlinkChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e
    )
    {
        var instance = d as HelloWorld;
        if (instance != null)
        {
            if (instance._timer.IsEnabled != instance.Blink)
            {
                if (instance.Blink)
                {
                    instance._timer.Start();
                }
                else
                {
                    instance._timer.Stop();
                }
            }
        }
    }

    private void __timer_Tick(object sender, object e)
    {
        DoBlink();
    }

    private void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
    }
}

Вернитесь в файл MainPage.xaml и добавьте параметр конфигурации в элемент управления.

 <local:HelloWorld Blink="True" />

Запустите приложение и вы увидите, как элемент управления мигает!

Добавление поддержки событий

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

 public class HelloWorld : Control
{
    
    ...
...
    private void __timer_Tick(object sender, object e)
    {
        DoBlink();
    }

    private void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
        OnBlinked();
    }

    public event EventHandler Blinked;

    private void OnBlinked()
    {
        EventHandler eh = Blinked;
        if (eh != null)
        {
            eh(this, new EventArgs());
        }
    }
}

В файле MainPage.xaml добавьте свойство x:Name в элемент, чтобы мы могли получить экземпляр элемента управления далее в коде:

 <local:HelloWorld x:Name="HelloWorldWithEvents" Blink="True" />

В файле MainPage.xaml.cs добавьте делегат функции прослушивателя событий для печати результатов отладки при инициировании события.

 HelloWorldWithEvents.Blinked += (object sender, EventArgs args) =>
{
    System.Diagnostics.Debug.WriteLine("HelloWorldWithEvents Blinked");
};

При выполнении этого кода вы увидите сообщение, которое каждые 500 мс записывается в окно консоли вывода в Visual Studio.

Предоставление доступа к общим методам

Мы уже написали частный метод DoBlink, чтобы вызывать мигание элемента управления, а теперь сделаем его общим, чтобы вы могли включать мигание в любой момент. Для этого достаточно изменить видимость метода DoBlink на "public":

 public class HelloWorld : Control
{
    ...

    public void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
        OnBlinked();
    }

    ...
}

Теперь можно вызвать общий метод DoBlink в фоновом коде C#, например в обработчике события нажатия кнопки.

 private void Button_Click_1(object sender, RoutedEventArgs e)
{
    HelloWorldWithEvents.DoBlink();
}    

Многократное использование элементов управления

Если вы хотите использовать элемент управления только в одном проекте или только для себя, не требуется много думать об упаковке и распространении. Но если вы хотите создать элемент управления для многократного использования и предоставить его другим разработчикам, у вас есть несколько вариантов. Один из них — создать SDK для расширений Visual Studio, который другие разработчики смогут устанавливать на свои компьютеры и добавлять в свои проекты. Подробнее об этом можно прочитать в статье Создание пакета SDK с помощью C# или Visual Basic.

Заключение

Мы проработали основные аспекты создания элемента управления:

  1. добавление элемента управления на страницу;
  2. передача параметров конфигурации;
  3. передача событий и реагирование на них;
  4. предоставление функциональной возможности через общие методы.

Итак, вы узнали основы создания настраиваемых элементов управления XAML. Далее нужно найти функциональные возможности, которые бы вы хотели добавить в существующий элемент управления XAML, и соответствующим образом изменить стиль или подкласс элемента управления. Когда я ближе познакомился с настраиваемыми элементами управления, то стал чувствовать себя гораздо увереннее при написании приложений на XAML. Я надеюсь, что вы поэкспериментируете с собственными настраиваемыми элементами управления и поделитесь своим опытом в Интернете.

Надеюсь, эта запись была для вас полезной. Если у вас есть вопросы по работе с собственными элементами управления, посетите Центр разработки для Windows и задайте вопросы на форумах.

-- Аарон Вроблевски (Aaron Wroblewski)

Руководитель программы, Windows