Первая публикация будет сразу вот такая техническая и
посвященная решению проблемы отображения подсказок в WPF компонентах –
TextBox и ComboBox.
Итак, иногда бывает неплохо подсказать пользователю, какие текстовые
данные необходимо ввести в TextBox,
или обозначить, какой классификатор доступен в выпадающем списке. Конечно,
можно щедро снабдить форму различными подписями рядом с полями для ввода, но, к
сожалению, этого не всегда бывает достаточно.
Поэтому ниже речь пойдет о том, как сделать вот такую красоту,
при этом не изменить обычного
поведения TextBox и ComboBox.
Начнем,
пожалуй, с простого – реализуем поведение вспомогательной информационной
подсказки для TextBox. Требования к поведению – при
получении фокуса ввода, либо в случае если свойству Text соответствует
не пустое значение, подсказка должна пропадать. Очевидно, что свойство Text для
отображения подсказки использовать нельзя, иначе компонент не позволит ввести
пустую строку. Помимо этого у TextBox нет стандартного поля
для хранения строки содержащей подсказку.
Пожалуй
самый простой способ добавить поле в компонент – прибегнуть к присоединяемым свойствам.
Класс, содержащий свойство для хранения подсказки будет иметь следующий вид:
public sealed class WaterMarkExtentions
{
public static string GetWaterMark(DependencyObject obj)
{
return (string)obj.GetValue(WaterMarkProperty);
}
public static void SetWaterMark(DependencyObject obj, string value)
{
obj.SetValue(WaterMarkProperty, value);
}
public static readonly DependencyProperty WaterMarkProperty =
DependencyProperty.RegisterAttached("WaterMark"
,typeof(string)
,typeof(FrameworkElement)
,new FrameworkPropertyMetadata(""));
,typeof(string)
,typeof(FrameworkElement)
,new FrameworkPropertyMetadata(""));
}
Далее целесообразно воспользоваться триггерами
для того чтобы управлять отображением подсказки, соответственно необходимо описать
стиль. А вот и
он:
<Style TargetType="TextBox" x:Key="WaterMarkTextboxStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" >
<Grid>
<ScrollViewer x:Name="PART_ContentHost"
/>
<TextBlock x:Name="WatermarkText"
Text="{Binding WaterMark,
RelativeSource={RelativeSource TemplatedParent}}"
Foreground="Gray" Margin="5,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Visibility="Collapsed"
IsHitTestVisible="False"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsKeyboardFocusWithin"
Value="False"/>
Value="False"/>
<Condition Property="Text" Value=""/>
</MultiTrigger.Conditions>
<Setter Property="Visibility"
TargetName="WatermarkText"
Value="Visible"/>
TargetName="WatermarkText"
Value="Visible"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsKeyboardFocusWithin"
Value="False"/>
Value="False"/>
<Condition Property="Text" Value="{x:Null}"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility"
TargetName="WatermarkText"
Value="Visible"/>
TargetName="WatermarkText"
Value="Visible"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Подсказка отображается в TextBlock (WatermarkText), который располагается над контентом нашего TextBox. Свойством Visibility у WatermarkText управляют триггеры в зависимости от того есть ли фокус
ввода у компонента, и от значения свойства Text.
Собственно все, компонент готов к использованию. Ниже пример
использования:
<Window x:Class="WaterMarkStyle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WaterMarkStyle"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox
local:WaterMarkExtentions.WaterMark = "sample textbox watermark text"
local:WaterMarkExtentions.WaterMark = "sample textbox watermark text"
Style="{StaticResource WaterMarkTextboxStyle}"
HorizontalAlignment="Left"/>
</Grid>
</Window>
Далее, как и
обещал в начале, тот же фокус выполним для ComboBox. Идея, в общем, та же, но вот шаблон компонента
значительно сложнее, его можно найти на msdn.
Теперь все что осталось – это переработать описание стиля шаблона для поля
ввода, которое называется – PART_EditableTextBox. Стиль описанный ранее (WaterMarkTextboxStyle) великолепно сюда подходит, однако, в виду сложности шаблона для
ComboBox, придется поправить Binding подсказки следующим образом:
<TextBlock x:Name="WatermarkText"
Text="{Binding WaterMark, RelativeSource=
{RelativeSource Mode=FindAncestor, AncestorType=ComboBox}}"
{RelativeSource Mode=FindAncestor, AncestorType=ComboBox}}"
Foreground="Gray" Margin="5,0,0,0"
HorizontalAlignment="Left" VerticalAlignment="Center"
Visibility="Collapsed" IsHitTestVisible="False"/>
Ну и последний штрих –
теперь за отображение подсказки будет отвечать еще и выбранный в выпадающем
списке элемент. Помимо этого надо не забывать про флаг IsEditable. Поэтому добавим
в триггеры шаблона ComboBox:
<ControlTemplate.Triggers>
...
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEditable" Value="false"/>
<Condition Property="SelectedItem" Value="{x:Null}"/>
</MultiTrigger.Conditions>
<Setter TargetName="PART_EditableTextBox"
Property="Visibility"
Value="Visible"/>
Property="Visibility"
Value="Visible"/>
</MultiTrigger>
Пример использования
будет абсолютно аналогичный:
<ComboBox
local:WaterMarkExtentions.WaterMark = "sample combobox watermark text"
local:WaterMarkExtentions.WaterMark = "sample combobox watermark text"
Style="{StaticResource WaterMarkComboBoxStyle}" >
<System:String>test1</System:String>
<System:String>test2</System:String>
</ComboBox>