Недавно, в программе мне пришлось реализовывать отображение древовидной структуры. В WPF выбор без промедления пал на элемент TreeView. О том, как работать с TreeView ниже пойдет речь.
TreeView - это элемент управления, который отображает иерархические данные в древовидной структуре и позволяет скрывать/показывать элементы. Инициализация в XAML происходит следующим способом:
Лично меня стандартный вид не устроил и я решил поменять стиль TreeView. Стили отвечают за внешний вид элемента, поэтому чтобы задать вид, нужно править стиль. Например, чтобы задать цвет фона, шрифт, рамку и т.д. нужно править стиль. Если же нужно править содержимое элемента, то уже нужно менять шаблон. Также, стили связаны с шаблонами. Чтобы взять пример шаблона элемента, можно обратиться за помощью к MSDN: http://msdn.microsoft.com/ru-ru/library/ms752043.aspx. Наш же шаблон TreeView находится по адресу: http://msdn.microsoft.com/ru-ru/library/ms752048.aspx. Чтобы задать данный шаблон, нужно скопировать все части и поместить их в файл App.xaml, в котором будут хранится все стили и шаблоны элементов. По умолчанию данный файл "пустой", то есть не содержит стилей:
Как мы видим, изменения на лицо. Вместо "плюсика" стала "стрелка", и изменился фон выделения текущего элемента. Давайте посмотрим на код, который отвечает за данные изменения:
2. Шаблон. Здесь задается сам шаблон. Как видите, шаблон является частью стиля, поэтому, на первый взгляд может возникать небольшая путаница. Но обычно стилем называют стиль, который меняет лишь параметры вида, а именно цвет фона, шрифта и т.д. А если в стиле есть шаблон, который меняет часть, либо само содержимое, то его могут называть шаблоном (хотя фактически это стиль).
3. Менеджер визуальных состояний, группа - SelectionStates. За динамический контент в WPF отвечают триггеры. Они разделяются на три группы: триггеры стиля, триггеры данных и триггеры шаблона (ниже мы их рассмотрим). Но в последствии, был разработан менеджер визуальных состояний, который был внедрен в начале в Silverlight, а позже в WPF версии 3.5. Его задача - расширять работу визуального отображения элемента. Так как визуальных состояний у элемента может быть несколько, то их объединяют в группы. Первая группа - это SelectionStates. В ней содержатся все возможные состояния выделения элемента TreeViewItem, а именно:
3.1. Состояние Selected (выбрано). Визуальное состояние должно иметь Storyboard, в котором и содержатся действия, применяемые к цели TargetProperty. В данном случае цель - это цвет панели (Panel.Background).(SolidColorBrush.Color), а действие - это установка произвольного цвета, взятого из ресурса Value="{StaticResource SelectedBackgroundColor}".
3.2. Состояние Unselected (не выбрано). Данное состояние является по умолчанию, и оно указывается здесь, чтобы задать имя начального состояния. Оно не является обязательным.
3.3. Состояние SelectedInactive. Элемент выбран, но TreeView потерял фокус.
4. Группа - ExpansionStates. Следующа группа - это состояние экспандера. Обычно экспандер можно раскрыть/показать, соответственно у него два состояния.
4.1. Состояние Expanded (раскрыт). Устанавливает видимость ItemsPresenter, отвечающего за размещение дочерних элементов.
4.2. Состояние Collapsed (закрыт). Состояние по умолчанию.
5. Кнопка-экспандер ToggleButton. Имеет свойство IsChecked, которое может содержать одно из трех значений: null, false, true. Здесь указывается стиль Style="{StaticResource ExpandCollapseToggleStyle}", в котором и содержится отображение нашей кнопки (рассмотрим ниже).
6. Рамка Border, в которой содержится заголовок. В рамке (Border) находится ContentPresenter с именем PART_Header, который отвечает за отображение заголовка. Просто изначально, у нас в заголовке может находится любой элемент, не только текст (TextBlock), поэтому здесь указывается ContentPresenter, который может содержать в себе любой визуальный элемент.
7. Потомки - ItemsPresenter. Здесь содержится ItemsPresenter, отвечает за отображение потомков, принадлежащих к заголовку-родителю.
8. Триггеры шаблона. Тут содержатся все триггеры, действия которых применяются к текущему шаблону. Единственное весомое отличие триггеров шаблона <ControlTemplate.Triggers> от тригеров стилей <Style.Triggers> в том, что у триггеров стилей нельзя указать TargetName.
8.1. Если у родителя нет потомков Property="HasItems" Value="false", то скрывать кнопку-экспандер, так как у нас нечего показывать.
8.2; 8.3. Мультриггеры для задания минимальной высоты и ширины. Мультитриггер - это тот же триггер, только может содержать сразу несколько условий, они задаются в <MultiTrigger.Conditions>.
Ниже расположен стиль кнопки-экспандера ExpandCollapseToggleStyle:
1. Значок скрытия. Здесь задается объект, который показывает, что список потомков является закрытым. Объект может быть типа Image, Path, и других.
2. Значок показа. Аналогично, но значок должен соответствовать открытию списка.
Допустим, мы хотим задать другие значки. Для этого мы просто берем соответствующие объекты и заменяем их в стиле кнопки. Я для себя взял стрелки в стиле Metro и поменял размер главного грида на Width=17, Height=15, чтобы они благополучно помещялись:
Зададим немного пространства между элементами. Для этого существует параметр Padding. Так как Padding нужно указывать для каждого элемента TreeView, то нужно его задавать в стиле. Но как быть, стиль ведь у нас уже есть? WPF позваляет наследовать стили. Наследование осуществляется с помощью ключевого слова BasedOn, следовательно нам нужно наследовать базовый стиль, и добавить/заменить нужные нам параметры:
Добавим немного интерактивности для нашего дерева. Начнем с кнопки-экспандера. Пусть при наведении курсора на стрелку, у нее будет меняться цвет и выходить подсказка (ToolTip) с действием, типа: "Раскрыть/Закрыть". Для этого найдем знакомый нам стиль кнопки ExpandCollapseToggleStyle и добавим туда триггеры шаблона:
Сделаем аналогичное выделение для заголовка. Добавим в стиль TreeViewItem триггер:
Также я поменял цвет выделения текущего элемента, он у нас находится в ресурсах с именем SelectedBackgroundColor. Я задал для него #F5B79C:
Дерево может содержать различные элементы, названия которых могут быть аббревиатуры, поэтому хорошо было бы задать для каждого из них подсказку (ToolTip). Зададим подсказку таким образом:
{В первой части, я опишу процесс работы с шаблонами/стилями более подробно}
TreeView - это элемент управления, который отображает иерархические данные в древовидной структуре и позволяет скрывать/показывать элементы. Инициализация в XAML происходит следующим способом:
Ниже предоставлен скриншот:<TreeView Name="SampletreeView" FontFamily="Verdana" FontSize="14"> <TreeViewItem Header="Настройки"> <TreeViewItem Header="Внешний вид"> <TreeViewItem Header="Цвет фона" /> <TreeViewItem
Header
="Цвет шрифта" /> </TreeViewItem> <TreeViewItem Header="Конфигурация"> <TreeViewItem Header="Подключение к серверу" /> </TreeViewItem> </TreeViewItem> </TreeView>
Лично меня стандартный вид не устроил и я решил поменять стиль TreeView. Стили отвечают за внешний вид элемента, поэтому чтобы задать вид, нужно править стиль. Например, чтобы задать цвет фона, шрифт, рамку и т.д. нужно править стиль. Если же нужно править содержимое элемента, то уже нужно менять шаблон. Также, стили связаны с шаблонами. Чтобы взять пример шаблона элемента, можно обратиться за помощью к MSDN: http://msdn.microsoft.com/ru-ru/library/ms752043.aspx. Наш же шаблон TreeView находится по адресу: http://msdn.microsoft.com/ru-ru/library/ms752048.aspx. Чтобы задать данный шаблон, нужно скопировать все части и поместить их в файл App.xaml, в котором будут хранится все стили и шаблоны элементов. По умолчанию данный файл "пустой", то есть не содержит стилей:
<Application x:Class="TreeViewPaper.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
После вставки скачанных стилей, внешний вид нашего элемента станет вот таким:Как мы видим, изменения на лицо. Вместо "плюсика" стала "стрелка", и изменился фон выделения текущего элемента. Давайте посмотрим на код, который отвечает за данные изменения:
1. Параметры по умолчанию. В этом разделе задаются параметры, воспринимаемые стилем по умолчанию. Например, фон (background) изначально прозрачный (transparent), но если вы захотите задать его явно для элемента (<TreeViewItem Header="Настройки" Background="White" />), то текущий цвет будет тот, который вы явно задали. Это возможно благодаря строке Background="{TemplateBinding Background}", которая находится ниже в шаблоне.<Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}"> <!-- 1: [Параметры по умолчанию] --> <Setter Property="Background" Value="Transparent" /> <Setter Property="HorizontalContentAlignment" Value="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" /> <Setter Property="VerticalContentAlignment" Value="{Binding Path=VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" /> <Setter Property="Padding" Value="1,0,0,0" /> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /> <Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}" /> <!-- 2: [Шаблон] --> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TreeViewItem}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition MinWidth="19" Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions>
<!-- 3: [Менеджер визуальных состояний, группа - SelectionStates] --> <!-- 3.1: [Состояние: Selected (выбрано) ] -->
<VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="SelectionStates"> <VisualState x:Name="Selected"> <Storyboard> <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"> <EasingColorKeyFrame KeyTime="0" Value="{StaticResource SelectedBackgroundColor}" /> </ColorAnimationUsingKeyFrames> </Storyboard> </VisualState> <!-- 3.2: [Состояние: Unselected (не выбрано)] --> <VisualState x:Name="Unselected" /> <!-- 3.3: [Состояние: SelectedInactive (элемент выбран, но потерян фокус)] --> <VisualState x:Name="SelectedInactive"> <Storyboard> <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"> <EasingColorKeyFrame KeyTime="0" Value="{StaticResource SelectedUnfocusedColor}" /> </ColorAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> <!-- 4: [Менеджер визуальных состояний, группа - ExpansionStates] --> <VisualStateGroup x:Name="ExpansionStates"> <!-- 4.1: [Состояние: Expanded (элемент раскрыт)] --> <VisualState x:Name="Expanded"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="ItemsHost"> <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <!-- 4.2: [Состояние: Collapsed (элемент закрыт)] --> <VisualState x:Name="Collapsed" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <!-- 5: [Кнопка-экспандер ] --> <ToggleButton x:Name="Expander" Style="{StaticResource ExpandCollapseToggleStyle}" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" /> <!-- 6: [Рамка, в которой содержится заголовок родителя] --> <Border x:Name="Bd" Grid.Column="1" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" /> </Border> <!-- 7: [Потомки, принадлежат родителю] --> <ItemsPresenter x:Name="ItemsHost" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Visibility="Collapsed" /> </Grid> <!-- 8: [Триггеры шаблона] --> <ControlTemplate.Triggers> <!-- 8.1: [Если родитель пустой, то скрывать кнопку-экспандер] --> <Trigger Property="HasItems" Value="false"> <Setter TargetName="Expander" Property="Visibility" Value="Hidden" /> </Trigger> <!-- 8.2: [Мультриггер для задания минимальной ширины] --> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="HasHeader" Value="false" /> <Condition Property="Width" Value="Auto" /> </MultiTrigger.Conditions> <Setter TargetName="PART_Header" Property="MinWidth" Value="75" /> </MultiTrigger> <!-- 8.3: [Мультриггер для задания минимальной высоты] --> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="HasHeader" Value="false" /> <Condition Property="Height" Value="Auto" /> </MultiTrigger.Conditions> <Setter TargetName="PART_Header" Property="MinHeight" Value="19" /> </MultiTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
2. Шаблон. Здесь задается сам шаблон. Как видите, шаблон является частью стиля, поэтому, на первый взгляд может возникать небольшая путаница. Но обычно стилем называют стиль, который меняет лишь параметры вида, а именно цвет фона, шрифта и т.д. А если в стиле есть шаблон, который меняет часть, либо само содержимое, то его могут называть шаблоном (хотя фактически это стиль).
3. Менеджер визуальных состояний, группа - SelectionStates. За динамический контент в WPF отвечают триггеры. Они разделяются на три группы: триггеры стиля, триггеры данных и триггеры шаблона (ниже мы их рассмотрим). Но в последствии, был разработан менеджер визуальных состояний, который был внедрен в начале в Silverlight, а позже в WPF версии 3.5. Его задача - расширять работу визуального отображения элемента. Так как визуальных состояний у элемента может быть несколько, то их объединяют в группы. Первая группа - это SelectionStates. В ней содержатся все возможные состояния выделения элемента TreeViewItem, а именно:
3.1. Состояние Selected (выбрано). Визуальное состояние должно иметь Storyboard, в котором и содержатся действия, применяемые к цели TargetProperty. В данном случае цель - это цвет панели (Panel.Background).(SolidColorBrush.Color), а действие - это установка произвольного цвета, взятого из ресурса Value="{StaticResource SelectedBackgroundColor}".
3.2. Состояние Unselected (не выбрано). Данное состояние является по умолчанию, и оно указывается здесь, чтобы задать имя начального состояния. Оно не является обязательным.
3.3. Состояние SelectedInactive. Элемент выбран, но TreeView потерял фокус.
4. Группа - ExpansionStates. Следующа группа - это состояние экспандера. Обычно экспандер можно раскрыть/показать, соответственно у него два состояния.
4.1. Состояние Expanded (раскрыт). Устанавливает видимость ItemsPresenter, отвечающего за размещение дочерних элементов.
4.2. Состояние Collapsed (закрыт). Состояние по умолчанию.
5. Кнопка-экспандер ToggleButton. Имеет свойство IsChecked, которое может содержать одно из трех значений: null, false, true. Здесь указывается стиль Style="{StaticResource ExpandCollapseToggleStyle}", в котором и содержится отображение нашей кнопки (рассмотрим ниже).
6. Рамка Border, в которой содержится заголовок. В рамке (Border) находится ContentPresenter с именем PART_Header, который отвечает за отображение заголовка. Просто изначально, у нас в заголовке может находится любой элемент, не только текст (TextBlock), поэтому здесь указывается ContentPresenter, который может содержать в себе любой визуальный элемент.
7. Потомки - ItemsPresenter. Здесь содержится ItemsPresenter, отвечает за отображение потомков, принадлежащих к заголовку-родителю.
8. Триггеры шаблона. Тут содержатся все триггеры, действия которых применяются к текущему шаблону. Единственное весомое отличие триггеров шаблона <ControlTemplate.Triggers> от тригеров стилей <Style.Triggers> в том, что у триггеров стилей нельзя указать TargetName.
8.1. Если у родителя нет потомков Property="HasItems" Value="false", то скрывать кнопку-экспандер, так как у нас нечего показывать.
8.2; 8.3. Мультриггеры для задания минимальной высоты и ширины. Мультитриггер - это тот же триггер, только может содержать сразу несколько условий, они задаются в <MultiTrigger.Conditions>.
Ниже расположен стиль кнопки-экспандера ExpandCollapseToggleStyle:
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="15" Height="13" Background="Transparent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Collapsed">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Expanded">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked" />
<VisualState x:Name="Indeterminate" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- 1: [Значок при скрытии потомков] -->
<Path x:Name="Collapsed" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="1,1,1,1" Data="M 4 0 L 8 4 L 4 8 Z">
<Path.Fill>
<SolidColorBrush Color="{DynamicResource GlyphColor}" />
</Path.Fill>
</Path>
<!-- 2: [Значок при показе потомков] -->
<Path x:Name="Expanded" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="1,1,1,1" Data="M 0 4 L 8 4 L 4 8 Z" Visibility="Hidden">
<Path.Fill>
<SolidColorBrush Color="{DynamicResource GlyphColor}" />
</Path.Fill>
</Path>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Здесь мы видим знакомый нам менеджер визуальных состояний с тремя состояниями: Checked, Unckecked и Indeterminate. При состояниях Unckecked и Indeterminate значки не меняются.
В состоянии Checked задается скрытие - Visibility.Hidden для значка закрытия, а значок открытия становится видимым - Visibility.Visible. Тем самым реализуется замена одного значка на другого. Дополнительные комментарии:1. Значок скрытия. Здесь задается объект, который показывает, что список потомков является закрытым. Объект может быть типа Image, Path, и других.
2. Значок показа. Аналогично, но значок должен соответствовать открытию списка.
Допустим, мы хотим задать другие значки. Для этого мы просто берем соответствующие объекты и заменяем их в стиле кнопки. Я для себя взял стрелки в стиле Metro и поменял размер главного грида на Width=17, Height=15, чтобы они благополучно помещялись:
<Path x:Name="Collapsed" Width="12" Height="15" HorizontalAlignment="Left" VerticalAlignment="Center" Stretch="Fill" Margin="1,1,1,1" Data="F1 M 39.8307,37.6042L 36.6641,34.4375L 25.1849,23.3542L 35.4766,23.3542L 50.5182,37.6042L 35.4766,51.8542L 25.1849,51.8542L 36.6641,40.7708L 39.8307,37.6042 Z ">
<Path.Fill>
<SolidColorBrush Color="{DynamicResource GlyphColor}" />
</Path.Fill>
</Path>
<Path x:Name="Expanded" Width="15" Height="12" HorizontalAlignment="Left" VerticalAlignment="Center" Stretch="Fill" Margin="1,1,1,1" Data="F1 M 37.8516,39.5833L 52.1016,24.9375L 52.1016,35.2292L 37.8516,50.2708L 23.6016,35.2292L 23.6016,24.9375L 37.8516,39.5833 Z " Visibility="Hidden">
<Path.Fill>
<SolidColorBrush Color="{DynamicResource GlyphColor}" />
</Path.Fill>
</Path>
Теперь наше дерево выглядит так:Зададим немного пространства между элементами. Для этого существует параметр Padding. Так как Padding нужно указывать для каждого элемента TreeView, то нужно его задавать в стиле. Но как быть, стиль ведь у нас уже есть? WPF позваляет наследовать стили. Наследование осуществляется с помощью ключевого слова BasedOn, следовательно нам нужно наследовать базовый стиль, и добавить/заменить нужные нам параметры:
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="Padding" Value="5" />
</Style>
Данный стиль следует поместить в ресурсы окна - <Window.Resources>. После этого, вид будет таким:Добавим немного интерактивности для нашего дерева. Начнем с кнопки-экспандера. Пусть при наведении курсора на стрелку, у нее будет меняться цвет и выходить подсказка (ToolTip) с действием, типа: "Раскрыть/Закрыть". Для этого найдем знакомый нам стиль кнопки ExpandCollapseToggleStyle и добавим туда триггеры шаблона:
<ControlTemplate.Triggers>
<!-- 1: [Срабатывает при наведение курсора: для закрытой стрелки] -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Collapsed" Property="Fill" Value="OrangeRed" />
<Setter TargetName="Collapsed" Property="ToolTip" Value="Раскрыть" />
</Trigger>
<!-- 2: [Срабатывает при наведение курсора: для открытой стрелки] -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Expanded" Property="Fill" Value="OrangeRed" />
<Setter TargetName="Expanded" Property="ToolTip" Value="Закрыть" />
</Trigger>
</ControlTemplate.Triggers>
Теперь наша стрелка стала более дружелюбной:Сделаем аналогичное выделение для заголовка. Добавим в стиль TreeViewItem триггер:
<Trigger SourceName="PART_Header" Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="OrangeRed" />
</Trigger>
И в раздел начальных значений добавим <Setter Property="BorderThickness" Value="1" />. Обратите внимание, что источник должен быть PART_Header, если его не указать, то выделяться будет не только заголовок, но и его потомки. Ниже представлен результат:Также я поменял цвет выделения текущего элемента, он у нас находится в ресурсах с именем SelectedBackgroundColor. Я задал для него #F5B79C:
<Color x:Key="SelectedBackgroundColor">#F5B79C</Color>
Зададим цвет шрифта черно-серым, добавляя строчку <Setter Property="Foreground" Value="#565656" /> в <Window.Resources>. Для выбранного заголовка зададим произвольный цвет, подчеркивая выделенное. Чтобы это реализовать, посмотрим на стиль TreeViewItem, а конкретно на состояние Selected. Добавляем еще один объект к Storyboard, который бы задавал произвольный цвет шрифта:<!-- Группа SelectionStates, состояние Selected -->
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource SelectedForegroundColor}" />
</ColorAnimationUsingKeyFrames>
Определяем новый цвет SelectedForegroundColor:<Color x:Key="SelectedForegroundColor">Black</Color>
Если запустить программу с данными изменениями, выйдет RuntimeException, потому что у рамки Bd не задано свойство TextBlock.Foreground="{TemplateBinding Foreground}", и поэтому наше дополнение к Storyboard не может найти данное свойство. Взглянем теперь на слегка измененное дерево:Дерево может содержать различные элементы, названия которых могут быть аббревиатуры, поэтому хорошо было бы задать для каждого из них подсказку (ToolTip). Зададим подсказку таким образом:
<TreeViewItem Header="Настройки" ToolTip="Настройки">
После этого заветный ToolTip появляется, но все потомки его наследуют, и если, например, навести на элемент "Внешний вид" (у которого не задан ToolTip), то у него появляется подсказка "Настройки", что естественно, не есть хорошо. Давайте попробуем убрать этот ToolTip. Первое что пришло на ум, это задать для потомков, у которых не должно быть подсказки параметр ToolTipService.IsEnabled="False". Но, как показывает практика, такой финт не прошел. Свойство IsOpen у элемента ToolTip, доступно только для чтения, поэтому задать мы его не сможем. Задание типа {x:Null} дает такой же результат. После гугления, было найдено сообщение на форуме www.cyberforum.ru с подобным же вопросом. Пользователь с ником kenny69 предложил следуещее решение:<TreeView Name="treeView1">
<TreeView.Items>
<TreeViewItem>
<TreeViewItem.Header>
<TextBlock ToolTip="Родительский элемент">Parent Node</TextBlock>
</TreeViewItem.Header>
<TreeViewItem Header="Child Node"/>
</TreeViewItem>
</TreeView.Items>
</TreeView>
То есть он просто задает для заголовка принудительно элемент TextBlock, у которого есть объект ToolTip. Такой прием не сработает, ведь мы теперь переписали наш заранее определенный стиль своим элементом, у которого нет стиля. Соответственно, нужно теперь задавать другие стили, что не есть хорошо :). Я решил пойти другим путем. Попробывал создать простой стиль, в котором задавались бы ширина и высота:<!-- Стиль для скрытия ToolTip -->
<Style x:Key="NullToolTip" TargetType="{x:Type ToolTip}">
<Setter Property="Width" Value="0" />
<Setter Property="Height" Value="0" />
<Setter Property="Content" Value="{x:Null}" />
</Style>
Как видно из кода, мы просто задали нулевую ширину и высоту, с содержимым типа {x:Null}. Теперь зададим его для TreeViewItem:<TreeViewItem Header="Внешний вид">
<TreeViewItem.ToolTip>
<ToolTip Style="{StaticResource NullToolTip}" />
</TreeViewItem.ToolTip>
...
После проверки, данный прием оказывается рабочим, но писать такую конструкцию для каждого элемента затруднительно, поэтому нужно подумать, как ее сократить. Благодаря гибкой поддержки ресурсов в WPF, мы можем задать практически любой элемент в ресурсе. Единственное требование, это наличие ключа Key у каждого элемента, определенного в ресурсах. Определяем ToolTip с нулевым стилем в ресурсах:<ToolTip x:Key="NoToolTip" Style="{StaticResource NullToolTip}" />
И задаем для элемента:<TreeViewItem Header="Внешний вид" ToolTipService.ToolTip="{StaticResource NoToolTip}">
Теперь мы можем задать данную строку в стиле, который наследовали от базового. Ниже предоставлено полное решение скрытия ToolTip:<Style x:Key="NullToolTip" TargetType="{x:Type ToolTip}">
<Setter Property="Width" Value="0" />
<Setter Property="Height" Value="0" />
<Setter Property="Content" Value="{x:Null}" />
</Style>
<ToolTip x:Key="NoToolTip" Style="{StaticResource NullToolTip}" />
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="Padding" Value="5" />
<Setter Property="Foreground" Value="#565656" />
<Setter Property="ToolTipService.ToolTip" Value="{StaticResource NoToolTip}" />
</Style>
Во второй части, я расскажу про работу с функционалом элемента TreeView. Спасибо за внимание!
TreeViewItemFocusVisual откуда берется?? Не работает ничего...
ОтветитьУдалить