Опубликован: 13.12.2011 | Доступ: свободный | Студентов: 1021 / 34 | Оценка: 4.29 / 4.57 | Длительность: 13:56:00
Лекция 3:

Стили и шаблоны элементов управления WPF

Основы шаблонов

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

В Silverlight имеется куда более радикальный инструмент для настройки подобных вещей, называемый шаблонами (templates). Тогда как стили можно применять к любому Silverlight элементу, использование шаблонов ограничено Silverlight Control’ами, т.е. элементами, которые наследуются от класса Control, принадлежащего пространству имен System.Windows.Controls. У таких элементов имеется свойство Template, через которое вы можете задавать свой шаблон, и тем самым, перекрыть стандартное визуальное представление Control’а.

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

Шаблоны – это одна из наиболее сложных деталей в WPF, поэтому не удивительно, что в Silverlight все еще отсутствуют некоторые возможности. Более неожиданно то, что факт отсутствия этих возможностей создает условия, в которых Silverlight Control’ы вынуждены опираться на новый комплекс стандартов и использовать более продуманные правила проектирования шаблонов.

Создание шаблона

Каждый Control содержит встроенный набор правил, определяющий его отрисовку (в виде набора более простых элементов). Этот набор правил называется шаблоном Control’а (control template). Описывается он как блок XAML-разметки и применяется к Control’у через свойство "Template". Для примера давайте рассмотрим простую кнопку. Предположим, создавая пользовательский Control, вы пожелаете получить больше контроля над эффектами затенения и анимации кнопки. В этом случае первым делом нужно заменить существующий стандартный шаблон кнопки на свой собственный. Для того, чтобы создать шаблон кнопки, вам понадобится нарисовать свой бордюр кнопки, ее фон, а также предусмотреть размещение контента кнопки. На роль бордюра имеется несколько кандидатов, тут все зависит от того, какой корневой элемент вы выберите:

  • Бордюр (Border). Данный элемент решает две задачи: может содержать один элемент внутри себя (скажем TextBlock с заголовком кнопки), и отображать окаймляющий бордюр.
  • Таблица (Grid). Расположив несколько элементов в одном месте, вы можете создать кнопку с каемкой. Воспользуйтесь элементом формы (таким как Rectangle или Path) и в той же ячейке разместите TextBlock. Удостоверьтесь, что описание TextBlock’а в XAML идет после описания фигуры, поскольку текст должен быть наложен на фоновую фигуру, а не наоборот. Одно из достоинств контейнера Grid в том, что он поддерживает автоматический контроль размера, и вы можете быть уверены, что ваш Control будет всегда иметь размер, соответствующий размеру своего содержимого.
  • Канва (Canvas). В Canvas элементы могут размещаться строго по указанным координатам. В обычной ситуации это излишне, но может быть полезным, если вам требуется разместить несколько фигур особым образом относительно друг друга, например, при создании кнопки со сложным рисунком.

В следующем примере используется класс Border для сочетания закругленного оранжевого контура и броского красного фона с белым текстом:

<Button Content="A Custom Button Template">
    <Button.Template>
        <ControlTemplate TargetType="Button" >
            <Border BorderBrush="Orange" BorderThickness="3" 
                    CornerRadius="10" Background="Red">
                <TextBlock Foreground="White" Text="A Templated Button"/>
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>

На рисунке 4.2 вы видите результат:

Очень простая кнопка

Рис. 4.2. Очень простая кнопка

Если вы попробуете использовать эту кнопку, то обнаружите, что данный шаблон слишком примитивен. Он лишен большей части признаков нормальной кнопки (например, визуального эффекта вдавливания в момент нажатия), и фактически игнорирует любое задаваемое свойство кнопки, даже самое основное - "Content". (Наш пример содержит жестко "вшитый" текст в TextBlock, так что команда "Content="A Custom Button Template"" тут только для вида.) Тем не менее, этот шаблон имеет все шансы стать настоящим шаблоном кнопки, и мы будем заниматься его доводкой в следующих параграфах.

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

Повторное использование шаблона Control’а

В предыдущем примере описание шаблона расположено внутри элемента. Однако гораздо чаще шаблон Control’а размещают отдельно, т.к. часто вы будете применять шаблон сразу к нескольким Control’ам. Шаблон можно поместить в ресурсы:

<UserControl.Resources>
    <ControlTemplate x:Key="ButtonTemplate" TargetType="Button" >
        <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="10" 
                Background="Red">
            <TextBlock Foreground="White" Text="A Custom Template"/>
        </Border>
    </ControlTemplate>
</UserControl.Resources>

Затем в теге кнопки дописать ссылку на StaticResource:

<Button Template="{StaticResource ButtonTemplate}"/>

Такой подход не только упростит создание множества специализированных кнопок, но и позволит вам в будущем свободно модифицировать шаблон Control’а без необходимости изменять каждую кнопку, тем самым, уменьшая риск что-то испортить.

Есть еще один вариант – вы можете вложить описание шаблона в описание стиля. Преимущество такого подхода в том, что стиль может сочетать сеттеры, устанавливающие другие свойства, с сеттером, определяющим новый шаблон Control’а. Применяя стиль к кнопке, вы задействуете все сеттеры. В результате ваша кнопка получит новый шаблон, а также все упомянутые в стиле значения свойств.

ContentPresenter

Кнопка из предыдущего примера довольно бесполезна, т.к. отображает только "вшитый" текст. Вы, конечно же, захотите иметь возможность задавать контент кнопки через свойство Button.Content. Для этого вам понадобится метка-заполнитель специального назначения – "ContentPresenter".

ContentPresenter присутствует во всех Control’ах, содержащих какой-либо контент. Это специальная метка означающая "вставь контент здесь" и говорящая Silverlight, куда следует поместить содержимое Control’а. Вот как вы можете применить ее в нашем примере:

<UserControl.Resources>
    <ControlTemplate x:Key="ButtonTemplate" TargetType="Button" >
        <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="10" 
                Background="Red">
             <ContentPresenter/>
        </Border>
    </ControlTemplate>
</UserControl.Resources>

Замечание: ContentPresenter – наиболее востребованная, но не единственная метка. Control’ы представляющие списки и использующие ItemsControl применяют в своих шаблона метку ItemsPresenter, определяющую место, где должна располагаться панель списка элементов. Прокручиваемый контент Control’а ScrollViewer представлен меткой ScrollContentPresenter.

Связывание в Шаблонах

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

<Button Template="{StaticResource ButtonTemplate}" 
        Content="A Templated Button"
        Margin="10"
        Padding="20"/>

В этой разметке свойствам кнопки Margin и Padding заданы значения 10 и 20 соответственно. За учет свойства Margin отвечает контейнер, содержащий кнопку, поэтому с этим проблем не возникает. Но свойство Padding не берется в расчет, поэтому края кнопки вплотную прилегают к ее контенту. Дело в том, что свойство Padding не будет иметь никакого эффекта, пока вы сами не позаботитесь об этом. Иначе говоря, именно ваш шаблон должен позаботиться о дополнительных отступах вокруг контента кнопки, величина которых задается в Padding.

Для этой цели в Silverlight\WPF есть специальная возможность – связывание в шаблонах (template bindings). С помощью template binding шаблон Control’а сможет извлекать значения свойств Control’а, использующего данный шаблон. В следующем примере вы можете использовать template binding для получения значения свойства Padding и создания отступа вокруг ContentPresenter:

<ControlTemplate x:Key="ButtonTemplate" TargetType="Button" >
    <Border BorderBrush="Orange"
            BorderThickness="3"
            CornerRadius="10"
            Background="Red">
        <ContentPresenter Margin="{TemplateBinding Padding}"/>
    </Border>
</ControlTemplate>

Теперь желаемый эффект получен и установлен некоторый зазор между текстом и краями кнопки. рис. 4.3 демонстрирует вашу новую скромную кнопку:

Кнопка пользовательского шаблона Control’а

Рис. 4.3. Кнопка пользовательского шаблона Control’а

Замечание: Связывание в шаблонах похоже на обычное связывание данных (data bindings), но весит гораздо меньше, поскольку предназначено специально для шаблонов и поддерживает только одностороннее связывание данных (другими словами, можно передать информацию от Control’а в шаблон, но не наоборот).

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

<ContentPresenter
    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
    Margin="{TemplateBinding Padding}" 
    VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
    Content="{TemplateBinding Content}" 
    ContentTemplate="{TemplateBinding ContentTemplate}"/>

Связывание в шаблонах очень важно для свойства Content. Благодаря связыванию содержимое извлекается из Control’а и отображается в ContentPresenter. Зачастую можно не включать связывание для некоторых свойств шаблона, если вы не намерены их использовать.

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