Share via


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

Comments

  • Anonymous
    July 20, 2011
    Why all of your methods/class are static? Is it important? May be it simplifies your life, but, is there a way to make them non-static?

  • Anonymous
    May 09, 2014
    The comment has been removed

  • Anonymous
    May 17, 2014
    This is ludicrous.  Ten minutes of searching and not one page that tells me the namespace of Behavior.  Why does everyone leave out using blocks?

  • Anonymous
    May 18, 2014
    I posted the code for this (unfinished project) to github. See the specific file here: github.com/.../TextBoxFocusBehavior.cs

  • Anonymous
    August 03, 2014
    @n/a: in order to use the Behavior class you have to reference the assembly System.Windows.Interactivity.dll which can be found in the Microsoft Expression Blend SDK. The using statementt would, obviously, be: using System.Windows.Interactivity;