WPF Attached Behavior Example – Watermark Text

I’ve been working on a relatively simple WPF application lately, in an effort to effectively follow the MVVM pattern, I’ve been working with custom attached properties.  In my app, I have some textboxes that I want to contain some default text that is removed when the user tabs into the control:

Unfocused: image

Focused:image

I could implement an event handler for the GotFocus and LostFocus events, but that would require some code in the code-behind, and isn’t particularly re-usable (show below):

    1:  private void OnInputTextBoxGotFocus(object sender, RoutedEventArgs e)
    2:          {
    3:              var tb = e.OriginalSource as TextBox;
    4:              if (tb != null)
    5:                  ClearTextBox(tb);
    6:          }
    7:   
    8:          private void ResetTextBox(TextBox tb)
    9:          {
   10:              if (tb.Name == "tbWhat")
   11:                  if (string.IsNullOrEmpty(tb.Text))
   12:                      tb.Text = "What.";
   13:              if (tb.Name == "tbWhere")
   14:                  if (string.IsNullOrEmpty(tb.Text))
   15:                      tb.Text = "Where.";
   16:          }
   17:   
   18:          private void ClearTextBox(TextBox tb)
   19:          {
   20:              if (tb.Name == "tbWhat")
   21:                  if (tb.Text == "What.")
   22:                      tb.Text = string.Empty;
   23:              if (tb.Name == "tbWhere")
   24:                  if (tb.Text == "Where.")
   25:                      tb.Text = string.Empty;
   26:          }
   27:   
   28:          private void OnInputTextBoxLostFocus(object sender, RoutedEventArgs e)
   29:          {
   30:              var tb = e.OriginalSource as TextBox;
   31:              if (tb != null)
   32:                  ResetTextBox(tb);
   33:          }

The above code is pretty terrible, and specific to the two textboxes I have on my window (tbWhat and tbWhere).  Ugly stuff, that fortunately can be replaced by a more elegant solution.  There have been some good blog posts on WPF Attached Behaviors: https://www.mindscape.co.nz/blog/index.php/2009/02/01/attached-behaviours-in-wpf/ and https://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx are what I read to get up to speed.

In my implementation, I want some default text to be displayed in the textbox, and I want to text to disappear when the user focuses into the textbox.  To do this, I’m going to have my behavior attach two dependency properties to the textbox, IsWatermarkEnabled and WatermarkText.  When IsWatermarkEnabled is set to true or false, I will attach or detach event handlers to the GotFocus and LostFocus events.  Below is the complete code listing:

    1:      public static class TextBoxFocusBehavior
    2:      {
    3:          public static string GetWatermarkText(DependencyObject obj)
    4:          {
    5:              return (string)obj.GetValue(WatermarkText);
    6:          }
    7:   
    8:          public static void SetWatermarkText(DependencyObject obj, string value)
    9:          {
   10:              obj.SetValue(WatermarkText, value);
   11:          }
   12:   
   13:          public static bool GetIsWatermarkEnabled(DependencyObject obj)
   14:          {
   15:              return (bool)obj.GetValue(IsWatermarkEnabled);
   16:          }
   17:   
   18:          public static void SetIsWatermarkEnabled(DependencyObject obj, bool value)
   19:          {
   20:              obj.SetValue(IsWatermarkEnabled, value);
   21:          }
   22:   
   23:          public static readonly DependencyProperty IsWatermarkEnabled =
   24:              DependencyProperty.RegisterAttached("IsWatermarkEnabled",
   25:              typeof(bool), typeof(TextBoxFocusBehavior),
   26:              new UIPropertyMetadata(false, OnIsWatermarkEnabled));
   27:   
   28:          public static readonly DependencyProperty WatermarkText =
   29:              DependencyProperty.RegisterAttached("WatermarkText",
   30:              typeof(string), typeof(TextBoxFocusBehavior),
   31:              new UIPropertyMetadata(string.Empty, OnWatermarkTextChanged));
   32:   
   33:          private static void OnWatermarkTextChanged(object sender, DependencyPropertyChangedEventArgs e)
   34:          {
   35:              TextBox tb = sender as TextBox;
   36:              if (tb != null)
   37:              {
   38:                  tb.Text = (string) e.NewValue;
   39:              }
   40:          }
   41:   
   42:          private static void OnIsWatermarkEnabled(object sender, DependencyPropertyChangedEventArgs e)
   43:          {
   44:              TextBox tb = sender as TextBox;
   45:              if (tb != null)
   46:              {
   47:                  bool isEnabled = (bool)e.NewValue;
   48:                  if (isEnabled)
   49:                  {
   50:                      tb.GotFocus += OnInputTextBoxGotFocus;
   51:                      tb.LostFocus += OnInputTextBoxLostFocus;
   52:                  }
   53:                  else
   54:                  {
   55:                      tb.GotFocus -= OnInputTextBoxGotFocus;
   56:                      tb.LostFocus -= OnInputTextBoxLostFocus;
   57:                  }
   58:              }
   59:          }
   60:   
   61:          private static void OnInputTextBoxLostFocus(object sender, RoutedEventArgs e)
   62:          {
   63:              var tb = e.OriginalSource as TextBox;
   64:              if (tb != null)
   65:              {
   66:                  if (string.IsNullOrEmpty(tb.Text))
   67:                      tb.Text = GetWatermarkText(tb);
   68:              }
   69:          }
   70:   
   71:          private static void OnInputTextBoxGotFocus(object sender, RoutedEventArgs e)
   72:          {
   73:              var tb = e.OriginalSource as TextBox;
   74:              if (tb != null)
   75:              {
   76:                  if (tb.Text == GetWatermarkText(tb))
   77:                      tb.Text = string.Empty;
   78:              }   
   79:          }
   80:      }

To attach this behavior to my textbox in XAML, I first add an xmlns reference to my behaviors namespace: xmlns:local="clr-namespace:StopForgetting.Behaviors" and add the behavior to the TextBox XAML:

  <TextBox x:Name="tbWhere" local:TextBoxFocusBehavior.IsWatermarkEnabled="true" local:TextBoxFocusBehavior.WatermarkText="Where." Style="{StaticResource InputBox}" Width="150" Margin="5"></TextBox>
               

And that’s it.  Much more re-usable than placing event handlers in the code-behind.

image

image