TextBox с описанием и картинкой в WPF


Недавно с ужасом заметил, что для создания TextBox с описанием (не знаю как это правильно называется: в общем, когда в TextBox нет текста и фокуса, там написано, что это за поле ввода, как в висте в проводнике в правом верхнем углу окошко поиска) некоторые люди создают новый UserControl, тогда как гораздо проще(и с гораздо меньшим количеством кода) можно сделать это стилями и ControlTemplate.
TextBox с описанием и картинкой в WPF

Итак начнем с простого: создадим стиль для TextBox. Для этого создадим пустой стиль и в нем создадим пустой Control Template. Теперь внутрь Control Template вставим следующую строку:
<ScrollViewer Margin="{TemplateBinding Padding}" x:Name="PART_ContentHost"/>
Эта строка у нас отвечает за вывод ScrollViewer в который будет вводиться/выводиться текст. Теперь, чтобы TextBox выглядел похожим на поле ввода, а не на пустое место, в которое можно вводить текст, поместим этот скроллвьювер в Border и установим ему нужные параметры (естественно все это я делаю не руками, а используя Microsoft Expression Blend, что и Вам советую), например так:
<ControlTemplate x:Key="SimpleTextBoxWithDescriptionControlTemplate" TargetType="{x:Type TextBox}">
<Border Width="Auto" Height="Auto" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2,2,2,2">
<ScrollViewer Margin="{TemplateBinding Padding}" x:Name="PART_ContentHost"/>
</Border>
</ControlTemplate>

Итак простенький стиль для TextBox мы создали, теперь самое важное: сделаем так, чтобы в том случае когда текст не введен и в поле ввода нет фокуса, выводилось описание. Для этого поместим Grid в имеющийся Border (т.к. в Border может находится только один элемент) и уже в Grid поместим наш ScrollViewer, а вместе с ним - TextBlock. Этот TextBlock и будет использоваться для вывода нашего описания (TextBlock должен находится под ScrollViewer, чтоб не загораживать его и не перекрывать возможность выделения). Теперь нужно настроить свойства TextBlock, например, серенький цвет текста и курсивное начертание, отступы(padding, margin) по желанию, плюс надо как-то получать описание в этот TextBlock(иначе какой от него смысл). Описание я буду хранить в свойстве ToolTip, ибо мне кажется логичным, что тултип и описание должны быть одинаковы. Eсли кому-то кажется, что это не так, то например можно использовать свойство Tag или использовать attached properties. Так вот, чтоб подключить описание к текстблоку настроим биндинг, наш текстблок должен выглядеть следующим образом:
<TextBlock Visibility="Hidden" HorizontalAlignment="Left" Margin="1,0,0,0" x:Name="descriptionTextBlock" VerticalAlignment="Top" FontStyle="Italic" Foreground="#FF7E7E7E" Padding="{TemplateBinding Padding}" Text="{TemplateBinding ToolTip}"/>
И последнее сделаем, чтоб подсказка была видна только тогда, когда текст не введен и фокус не установлен, для этого создадим следующий триггер:
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Text" Value=""/>
<Condition Property="IsFocused" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" TargetName="descriptionTextBlock" Value="Visible"/>
</MultiTrigger>
А полностью стиль + контролтемплейт будут выглядеть так:
<Style x:Key="SimpleTextBoxWithDescriptionStyle" TargetType="{x:Type TextBox}">
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0,20" StartPoint="0,0" MappingMode="Absolute">
<GradientStop Color="#FFABADB3" Offset="0.05"/>
<GradientStop Color="#FFE2E3EA" Offset="0.07"/>
<GradientStop Color="#FFA6AEB6" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Template" Value="{DynamicResource SimpleTextBoxWithDescriptionControlTemplate}"/>
</Style>
<ControlTemplate x:Key="SimpleTextBoxWithDescriptionControlTemplate" TargetType="{x:Type TextBox}">
<Border Width="Auto" Height="Auto" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2,2,2,2">
<Grid Width="Auto" Height="Auto">
<TextBlock Visibility="Hidden" HorizontalAlignment="Left" Margin="1,0,0,0" x:Name="descriptionTextBlock" VerticalAlignment="Top" FontStyle="Italic" Foreground="#FF7E7E7E" Padding="{TemplateBinding Padding}" Text="{TemplateBinding ToolTip}"/>
<ScrollViewer Margin="{TemplateBinding Padding}" x:Name="PART_ContentHost"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Text" Value=""/>
<Condition Property="IsFocused" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" TargetName="descriptionTextBlock" Value="Visible"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
В принципе использовать стиль здесь было не обязательно, просто я так привык, что если делаешь контролтемлейт, то и стиль к нему делаешь тоже (исключая случая, когда к одному стилю подключается много контролтемплейтов), плюс я считаю, что основная работа с цветами должна также происходить в стиле, но это впрочем мое личное мнение.
Теперь чуть усложним задачу - сделаем стиль аналогичный стандартному стилю висты, но с текстом описания и картинкой перед текстом.
Ход работы впринципе аналогичный, только для начала мы не создаем пустой стиль, а берем и копируем стандартный вистовский стиль (и в этом нам поможет Expession Blend :) ). После чего открываем его контролтемплейт, переносим скролвьювер в грид, добавляем текстблок(под скролвьювер, что он не закрывал его), настраиваем его стиль и биндинги, настраиваем триггер. И все вистовский стиль с описанием готов. Теперь добавим картинку. Берем картинку(я взял первую попавшуюся картинку), добавляем ее в проект, после чегодобавляем ее в контролтемплейт на грид, устанавливаем ей нужные размеры (я взял 16х16), устанавливаем у текстблока и у скролвьювера марджин слева(я взял 17, чтоб был один пиксель отставания от картинки). Все готово - стиль долже выглядеть примерно так:
<Style x:Key="VistaTextBoxWithDescriptionStyle" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Microsoft_Windows_Themes:ListBoxChrome SnapsToDevicePixels="true" x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" RenderMouseOver="{TemplateBinding IsMouseOver}">
<Grid Width="Auto" Height="Auto">
<TextBlock Visibility="Hidden" HorizontalAlignment="Left" x:Name="descriptionTextBlock" VerticalAlignment="Top" FontStyle="Italic" Foreground="#FF7E7E7E" Text="{TemplateBinding ToolTip}" TextWrapping="Wrap" Margin="17,0,0,0" Padding="{TemplateBinding Padding}"/>
<ScrollViewer SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" x:Name="PART_ContentHost" Margin="17,0,0,0"/>
<Image HorizontalAlignment="Left" x:Name="image" Width="16" Height="16" Source="Images\Claw.png"/>
</Grid>
</Microsoft_Windows_Themes:ListBoxChrome>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Text" Value=""/>
<Condition Property="IsFocused" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" TargetName="descriptionTextBlock" Value="Visible"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

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

Скачать исходники:

КОММЕНТАРИИ


НОВЫЙ КОММЕНТАРИЙ


*жирный*
_курсив_
+подчеркнутый+
! заголовок 1
!! заголовок 2
* список
** список 2
# нумерованый список
## нумерованый список 2
[url:http://www.example.com]
{"без форматирования"}
Полное описание синтаксиса