Pull to refresh

WPF, Поле ввода с подсказкой

Reading time 8 min
Views 26K
Иногда бывает полезно создать эффект для поля ввода, выводящий подсказку в тот момент, когда текст отсутствует.
Например, вот такой:
Пример поля ввода с подсказкой

Это полезно в те моменты, когда надо сэкономить место, выделить особое поле или просто лишний раз дать подсказку совсем ничего не понимающим пользователям.

Что нам необходимо?
Во первых, свойство, которое отвечает за текст подсказки. Без него что-то отобразить будет довольно сложно.
Создадим класс-заготовку.
public class WatermarkedTextBox : DependencyObject
  {
    #region Fields

    private const string _defaultWatermark = "None";

    public static readonly DependencyProperty WatermarkTextProperty = DependencyProperty.Register("WatermarkText", typeof(string), typeof(WatermarkedTextBox), new UIPropertyMetadata(string.Empty, OnWatermarkTextChanged));

    #endregion

    #region Constructor(s)

    /// <summary>
    /// Initializes a new instance of the <see cref="WatermarkedTextBox"/> class with default watermark text.
    /// </summary>
    public WatermarkedTextBox()
      : this(_defaultWatermark)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="WatermarkedTextBox"/> class.
    /// </summary>
    /// <param name="watermark">The watermark to show when value is <c>null</c> or empty.</param>
    public WatermarkedTextBox(string watermark)
    {
      WatermarkText = watermark;
    }

    #endregion

    #region Properties

    public string WatermarkText
    {
      get { return (string)GetValue(WatermarkTextProperty); }
      set { SetValue(WatermarkTextProperty, value); }
    }

    #endregion

    #region Methods

    public static void OnWatermarkTextChanged(DependencyObject box, DependencyPropertyChangedEventArgs e)
    {
      //Add changed functionality here
    }

    #endregion
  }


* This source code was highlighted with Source Code Highlighter.
Теперь, когда создана заготовка и у нас имеются необходимые свойства и методы оперирования с подсказкой, можно приступать непосредственно к реализации. С ходу можно придумать множество вариантов реализации:
Например, можно повесить свои обработчики на установку-получение текста и выводить подсказку как обычный текст (Не раз видел подобное в различных html-формах).
Можно агреггировать TextBox, написать логику и сделать собственное отображение данных.
Но мы воспользуемся третьим, наиболее правильным методом в контексте WPF. Будем использовать стили, чтобы переопределить отображение контрола, а именно переопределим Control Template.

Сказано-сделано. Для начала, унаследуем наш класс от TextBox (вместо DependencyObject).
Если заглянуть вот сюда, то можно увидеть следующий текст.
The ControlTemplate for a TextBox must contain exactly one element that is tagged as the content host element; this element will be used to render the contents of the TextBox. To tag an element as the content host, assign it the special name PART_ContentHost. The content host element must be either a ScrollViewer or an AdornerDecorator. The content host element may not host any child elements.
Это значит, что в шаблоне необходимо будет создать ScrollViewer с именем PART_ContentHost.
Итак, коварный план таков: в те моменты, когда текст внутри TextBox отсутствует — будем показывать заготовленную надпись, из отдельного TextBlockа, иначе будем притворяться обычным TextBox.

То есть где-то внутри нашего стиля будет находится:
<TextBlock x:Name="WatermarkText" Text="{TemplateBinding WatermarkText}" Foreground="Gray" Margin="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Visibility="Collapsed" IsHitTestVisible="False"/>

* This source code was highlighted with Source Code Highlighter.
Я добавил несколько красот в виде отступов и цвета, чтобы усилия были лучше заметны.

И для него можно будет написать следующие триггеры:
<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>


* This source code was highlighted with Source Code Highlighter.
Они обеспечат нам показ текста-подсказки в тот момент, когда значение в поле текста отсутствует и при этом поле находится в состоянии отличном от состояния ввода. К сожалению, приходится писать два практически одинаковых триггера, чтобы одинаково хорошо обрабатывались и string.Empty и null.
Итак, все составные части у имеются, остаётся их обьеденить. В этом нет ничего сложного.
<Style TargetType="{x:Type WatermarkedTextBox:WatermarkedTextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
  <Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type WatermarkedTextBox:WatermarkedTextBox}">
        <Grid>
          <ScrollViewer x:Name="PART_ContentHost" />
          <TextBlock x:Name="WatermarkText" Text="{TemplateBinding WatermarkText}" Foreground="Gray" Margin="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Visibility="Collapsed" IsHitTestVisible="False"/>
        </Grid>
        <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>


* This source code was highlighted with Source Code Highlighter.
Аккуратненько соединили все части, добавили в стиль страницы. Теоретически, уже можно кричать ура и топать ногами в экстазе, но если запустить приложение, то окажется, что рамка куда-то пропала. Попытаемся восстановить эту несправедливость оборачиванием грида.
<Style TargetType="{x:Type WatermarkedTextBox:WatermarkedTextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
  <Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type WatermarkedTextBox:WatermarkedTextBox}">
        <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" >
          <Grid>
            <ScrollViewer x:Name="PART_ContentHost" />
            <TextBlock x:Name="WatermarkText" Text="{TemplateBinding WatermarkText}" 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>

* This source code was highlighted with Source Code Highlighter.
Вот теперь работа завершена. Можно насладиться результатом. Или скачать рабочий пример.
Tags:
Hubs:
-3
Comments 7
Comments Comments 7

Articles