Построение сложного шаблона

23

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

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

Раньше уже были показаны два примера требований, которые элемент управления может предъявлять к своему шаблону, с элементами-заполнителями (вроде ContentPresenter и ItemsPresenter) и привязками шаблона. В следующих разделах будут продемонстрированы еще два: элементы со специфическими именами (начинающимися с PART_) и элементы, специально спроектированные для использования в шаблоне определенного элемента управления (такие как Track в ScrollBar). Чтобы создать успешный шаблон элемента управления, следует тщательно изучить стандартный шаблон интересующего элемента, разобраться, как используются в нем эти четыре приема, и затем продублировать их в собственных шаблонах.

Вложенные шаблоны

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

Например, предположим, что планируется изменить знакомый элемент управления ListBox. Первый шаг в создании такого примера состоит в проектировании шаблона для ListBox и (дополнительно) добавлении стиля, который применяет шаблон автоматически. Ниже показаны оба ингредиента, объединенные в одно целое:

<LinearGradientBrush x:Key="GradientForListBox" StartPoint="0,0" EndPoint="1,0.001">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="White" Offset="0.0" />
                <GradientStop Color="White" Offset="0.6" />
                <GradientStop Color="#DDDDDD" Offset="1.2"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>
    
    <Style TargetType="ListBox">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBox">
                    <Border Name="border_lbx"
                            Background="{StaticResource GradientForListBox}"
                            BorderBrush="Chocolate"
                            BorderThickness="2" CornerRadius="3"
                            Margin="5">
                        <ScrollViewer Focusable="False">
                            <ItemsPresenter Margin="2"></ItemsPresenter>
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Стиль использует две кисти для рисования границы и фона. Этот шаблон представляет собой упрощенную версию стандартного шаблона ListBox, но отказывается от класса ListBoxChrome в пользу простого Border. Внутри Border находится элемент ScrollViewer, обеспечивающий прокрутку списка, и ItemsPresenter, хранящий все элементы списка.

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

Как и ListBox, шаблон ListBoxItem можно применить с использованием типизированный в отношении элементов стиля. Показанный ниже базовый шаблон помещает каждый элемент списка в невидимую рамку. Поскольку ListBoxItem — элемент с содержимым, для размещения содержимого внутри него предусмотрен ContentPresenter. Наряду с этим имеется триггер, реагирующий, когда на элемент наводится курсор мыши или на нем выполняется щелчок:

<Style TargetType="ListBoxItem">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <Border Name="lbxi_border"
                            BorderThickness="2" CornerRadius="3"
                       Padding="1" 
                       SnapsToDevicePixels="True">
                       <ContentPresenter />
                    </Border>
                    <ControlTemplate.Triggers>
                        <EventTrigger RoutedEvent="ListBoxItem.MouseEnter">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetProperty="FontSize"
                                                         To="22" Duration="0:0:0.5"></DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="ListBoxItem.MouseLeave">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetProperty="FontSize"
                                                         Duration="0:0:0.5" BeginTime="0:0:0.5"></DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="lbxi_border" Property="BorderBrush" Value="#DDD"></Setter>
                        </Trigger>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter TargetName="lbxi_border" Property="Background" Value="#aaa"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

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

Хотя невозможно показать этот эффект на одной картинке, на рисунке ниже представлен экранный снимок этого списка после быстрого перемещения курсора мыши над несколькими элементами:

Обратите внимание, что анимация уменьшения обходится без свойств From и То. То есть, она всегда уменьшает текст от его текущего размера до исходного. Поместив курсор мыши на ListBoxItem, затем передвинув его обратно, можно получить вполне ожидаемый результат — элемент будет увеличиваться, пока над ним находится курсор мыши, и уменьшаться, когда курсор мыши отодвигается в сторону.

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

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