Модификация полосы прокрутки
66WPF --- Шаблоны и пользовательские элементы управления WPF --- Модификация полосы прокрутки
Существует один аспект окна списка, который пока остался нетронутым — полоса прокрутки, расположенная справа. Это часть элемента ScrollViewer, который является частью шаблона ListBox. Несмотря на то что в данном примере переопределяется шаблон ListBox, элемент ScrollViewer в ScrollBar не изменяется.
Чтобы настроить эту деталь, можно было бы создать шаблон ScrollViewer для использования с ListBox и затем указать этот специальный шаблон ScrollBar в качестве шаблона ScrollViewer. Однако доступен более простой способ — создать типизированный в отношении элементов стиль, который изменяет шаблон всех обнаруженных элементов управления ScrollBar. Это позволит избежать дополнительной работы по созданию шаблона ScrollViewer.
Конечно, также следует подумать о том, как это проектное решение затронет остальную часть приложения. Если создать типизированный в отношении элементов стиль ScrollBar и добавить его в коллекцию Resources окна, то все элементы управления в этом окне получат новые стилизованные полосы прокрутки, если они используют ScrollBar, что может быть именно тем, что и требуется. С другой стороны, если нужно изменить только полосу прокрутки в ListBox, понадобится добавить типизированный в отношении элементов стиль ScrollBar в коллекцию ресурсов самого ListBox. И, наконец, чтобы изменить вид полос прокрутки во всем приложении, необходимо добавить стиль в коллекцию ресурсов внутри файла App.xaml.
Элемент управления ScrollBar неожиданно сложен. В действительности он состоит из целой коллекции меньших частей.
Фон полосы прокрутки представлен классом Track — обычно это текстурированный прямоугольник, простирающийся на всю длину полосы. На концах полосы прокрутки находятся кнопки, которые позволяют перемещать на один инкремент вверх или вниз (либо влево или вправо). Это экземпляры класса RepeatButton, унаследованного от ButtonBase. Ключевое отличие между RepeatButton и обычным классом Button состоит в том, что удержание кнопки мыши нажатой на RepeatButton приводит к повторению события Click (это удобно для прокрутки).
Посередине полосы прокрутки расположен элемент Thumb, представляющий текущую позицию прокручиваемого содержимого. И что самое интересное, пустое пространство с каждой стороны от ползунка — это на самом деле еще два объекта RepeatButton, являющиеся прозрачными. При щелчке на любом из них полоса прокрутки прокручивает содержимое на целую страницу (страница определена как часть прокручиваемого содержимого, умещающаяся в окно). Это дает хорошо знакомую возможность быстро перемещаться по прокручиваемому содержимому, щелкая на полосе по обе стороны от ползунка.
Есть несколько ключевых моментов, которые следовало бы отметить:
Вертикальная полоса прокрутки состоит из элемента Grid с тремя строками. Верхняя и нижняя строки содержат кнопки (со стрелочками). Их размер фиксирован и составляет 18 единиц. Средняя часть, которая содержит дорожку, занимает остальное пространство.
Элементы RepeatButton на обоих концах используют одинаковый стиль. Единственное отличие связано со свойством Content, которое содержит элемент Path, рисующий стрелку, потому что верхняя кнопка отображает стрелку вверх, а нижняя — стрелку вниз. Для краткости эти стрелки представлены на мини-языке описания пути. Прочие детали, такие как заполнение фона и окружность вокруг стрелки, определены в шаблоне элемента управления, который установлен в ScrollButtonLineStyle.
Обе кнопки привязаны к командам в классе ScrollBar (LineUpCommand и LineDownCommand). Команды обеспечивают выполнение работы. До тех пор, пока предоставляется кнопка, привязанная к одной из этих команд, не важно, как она именована, каким образом выглядит и какой конкретный класс использует.
Дорожка имеет имя PART_Track. Это имя должно использоваться, чтобы код класса ScrllBack мог успешно работать. Если просмотреть шаблон по умолчанию для класса ScrollBar (который похож на показанный выше, но только длиннее), можно заметить, что это имя присутствует и там.
Свойство Track.ViewportSize установлено в 0. Это — специфическая деталь реализации в данном шаблоне. Она гарантирует, что Thumb всегда будет одного размера. (Обычно размер ползунка пропорционален содержимому, так что при прокрутке содержимого, которое почти целиком умещается в окно, ползунок будет намного больше.)
Элемент Track включает в себя два объекта RepeatButton (стиль которых определен отдельно) и Thumb. Опять-таки, эти кнопки привязаны к соответствующей функциональности с помощью команд.
Ниже показан шаблон скроллбара:
<Style x:Key="ScrollBarLineButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Canvas Height="18">
<Polygon Fill="LightBlue" Points="3,15 15,15 9,3"></Polygon>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ScrollBarLineButtonBottomStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Canvas Height="18">
<Polygon Fill="LightBlue" Points="3,3 9,15 15,3"></Polygon>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ScrollBarPageButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border BorderBrush="Transparent"></Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ScrollBarThumbStyle" TargetType="{x:Type Thumb}">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="Margin" Value="1,0,1,0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Rectangle Fill="LightBlue" Margin="2"></Rectangle>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="18"/>
<RowDefinition Height="*"/>
<RowDefinition MaxHeight="18"/>
</Grid.RowDefinitions>
<RepeatButton Grid.Row="0" Height="18"
Style="{StaticResource ScrollBarLineButtonStyle}"
Command="ScrollBar.LineUpCommand" >
</RepeatButton>
<Track Name="PART_Track" Grid.Row="1"
IsDirectionReversed="True">
<Track.DecreaseRepeatButton>
<RepeatButton Command="ScrollBar.PageUpCommand" Style="{StaticResource ScrollBarPageButtonStyle}">
</RepeatButton>
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumbStyle}">
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Command="ScrollBar.PageDownCommand" Style="{StaticResource ScrollBarPageButtonStyle}">
</RepeatButton>
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton Grid.Row="3" Height="18"
Style="{StaticResource ScrollBarLineButtonBottomStyle}"
Command="ScrollBar.LineDownCommand">
</RepeatButton>
</Grid>
</ControlTemplate>
<Style TargetType="{x:Type ScrollBar}">
<Setter Property="Template" Value="{StaticResource VerticalScrollBar}"/>
</Style>
Также можно заметить, что шаблон использует ключевое имя, которое указывает на то, что это — вертикальная полоса прокрутки. Как известно, установка ключевого имени стиля гарантирует, что оно не применяется автоматически, даже если также установлено свойство TargetType. Причина использования этого подхода в данном примере связана с тем, что шаблон подходит только к полосам прокрутки с вертикальной ориентацией.