TextBox с описанием и картинкой в WPF
20 января 2009 - 19:56
Недавно с ужасом заметил, что для создания TextBox с описанием (не знаю как это правильно называется: в общем, когда в TextBox нет текста и фокуса, там написано, что это за поле ввода, как в висте в проводнике в правом верхнем углу окошко поиска) некоторые люди создают новый UserControl, тогда как гораздо проще(и с гораздо меньшим количеством кода) можно сделать это стилями и ControlTemplate.
Итак начнем с простого: создадим стиль для TextBox. Для этого создадим пустой стиль и в нем создадим пустой Control Template. Теперь внутрь Control Template вставим следующую строку:
Эта строка у нас отвечает за вывод ScrollViewer в который будет вводиться/выводиться текст. Теперь, чтобы TextBox выглядел похожим на поле ввода, а не на пустое место, в которое можно вводить текст, поместим этот скроллвьювер в Border и установим ему нужные параметры (естественно все это я делаю не руками, а используя Microsoft Expression Blend, что и Вам советую), например так:
Итак простенький стиль для TextBox мы создали, теперь самое важное: сделаем так, чтобы в том случае когда текст не введен и в поле ввода нет фокуса, выводилось описание. Для этого поместим Grid в имеющийся Border (т.к. в Border может находится только один элемент) и уже в Grid поместим наш ScrollViewer, а вместе с ним - TextBlock. Этот TextBlock и будет использоваться для вывода нашего описания (TextBlock должен находится под ScrollViewer, чтоб не загораживать его и не перекрывать возможность выделения). Теперь нужно настроить свойства TextBlock, например, серенький цвет текста и курсивное начертание, отступы(padding, margin) по желанию, плюс надо как-то получать описание в этот TextBlock(иначе какой от него смысл). Описание я буду хранить в свойстве ToolTip, ибо мне кажется логичным, что тултип и описание должны быть одинаковы. Eсли кому-то кажется, что это не так, то например можно использовать свойство Tag или использовать attached properties. Так вот, чтоб подключить описание к текстблоку настроим биндинг, наш текстблок должен выглядеть следующим образом:
И последнее сделаем, чтоб подсказка была видна только тогда, когда текст не введен и фокус не установлен, для этого создадим следующий триггер:
А полностью стиль + контролтемплейт будут выглядеть так:
В принципе использовать стиль здесь было не обязательно, просто я так привык, что если делаешь контролтемлейт, то и стиль к нему делаешь тоже (исключая случая, когда к одному стилю подключается много контролтемплейтов), плюс я считаю, что основная работа с цветами должна также происходить в стиле, но это впрочем мое личное мнение.
Теперь чуть усложним задачу - сделаем стиль аналогичный стандартному стилю висты, но с текстом описания и картинкой перед текстом.
Ход работы впринципе аналогичный, только для начала мы не создаем пустой стиль, а берем и копируем стандартный вистовский стиль (и в этом нам поможет Expession Blend :) ). После чего открываем его контролтемплейт, переносим скролвьювер в грид, добавляем текстблок(под скролвьювер, что он не закрывал его), настраиваем его стиль и биндинги, настраиваем триггер. И все вистовский стиль с описанием готов. Теперь добавим картинку. Берем картинку(я взял первую попавшуюся картинку), добавляем ее в проект, после чегодобавляем ее в контролтемплейт на грид, устанавливаем ей нужные размеры (я взял 16х16), устанавливаем у текстблока и у скролвьювера марджин слева(я взял 17, чтоб был один пиксель отставания от картинки). Все готово - стиль долже выглядеть примерно так:
В данных двух примерах я рассматривал случай, что наша подсказка - текст, в принципе это может быть и не так, тогда вместо текстблоков для отображения подсказки стоит использовать label, но в этом случае тому, кто будет использовать данный стиль придется самому настраивать внешний вид подсказки(серый цвет, курсив и т.п.).
Да, и заметьте - ни строчки кода на c#, только xaml.
В принципе то, что я тут понаписал примитивно до крайности (по крайне мере мне так думается) и мне казалось, что любой программист пишущий на wpf должен это знать и понимать Но в реальности многие почему-то делают все гораздо сложней - плодят монструозные юзерконтролы и вообще пишут всякие сложные извращения. Я конечно полностью согласен с тем, что без юзерконтролов во многих местах не обойтись, но это имхо не тот случай.
Скачать исходники:
Итак начнем с простого: создадим стиль для TextBox. Для этого создадим пустой стиль и в нем создадим пустой Control Template. Теперь внутрь Control Template вставим следующую строку:
<ScrollViewer Margin="{TemplateBinding Padding}" x:Name="PART_ContentHost"/>
<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 должен это знать и понимать Но в реальности многие почему-то делают все гораздо сложней - плодят монструозные юзерконтролы и вообще пишут всякие сложные извращения. Я конечно полностью согласен с тем, что без юзерконтролов во многих местах не обойтись, но это имхо не тот случай.
Скачать исходники: