пятница, 9 ноября 2012 г.

Подсказки в WPF компонентах - TextBox и ComboBox

   Первая публикация будет сразу вот такая техническая и посвященная решению проблемы отображения подсказок в 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(""));
    }

   Далее целесообразно воспользоваться триггерами для того чтобы управлять отображением подсказки, соответственно необходимо описать стиль. А вот и он:

<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"/>
                        <Condition Property="Text" Value=""/>
                      </MultiTrigger.Conditions>
                   <Setter Property="Visibility" 
                           TargetName="WatermarkText" 
                           Value="Visible"/>
                   </MultiTrigger>
                   <MultiTrigger>
                      <MultiTrigger.Conditions>
                        <Condition Property="IsKeyboardFocusWithin" 
                                   Value="False"/>
                        <Condition Property="Text" Value="{x:Null}"/>
                       </MultiTrigger.Conditions>
                   <Setter Property="Visibility"
                           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"
        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}}"
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"/>
</MultiTrigger>

   Пример использования будет абсолютно аналогичный:

  <ComboBox 
        local:WaterMarkExtentions.WaterMark = "sample combobox watermark text"
         Style="{StaticResource WaterMarkComboBoxStyle}" >
     <System:String>test1</System:String>
     <System:String>test2</System:String>
 </ComboBox>


2 комментария:

  1. День добрый, подскажите пожалуйста, а как данный вариант реализовать в таблице DataGrid, спасибо.

    ОтветитьУдалить
  2. Best casino games and bonuses - DrMCD
    Casino games — The 서울특별 출장샵 top casinos online 강원도 출장안마 that are 남원 출장마사지 great for 여수 출장안마 casino players are the top casino 남원 출장마사지 games by number of gamblers. If you're looking for the

    ОтветитьУдалить