In my last blog post, I wrote an article about attached properties. Today at work, I encountered a problem that can be solved in a nice way using an attached property. Because the functionality I wanted to implement is also very simple, I decided to blog about it to give a concrete example.
I wanted to use a TextBox to allow the user of my application to give a description for any item he selects in a TreeView. Because the TextBox’s content could be changed very frequently by the user, I thought it might be useful to select all the TextBox’s text when the user click in the control (so that as soon as he types something, the old content is cleared).
In this article, I will describe various way to implement this feature and I will detail the way I prefer, using of course an attached property !
Background
With a TextBox control, a user can input text in an application. When the user click on a TextBox control, the control got focus and a cursor is displayed in the text.
The TextBox class has a SelectAll() method that can be used to select all the text in the control. In this example, what I want is to select all the text of the control when the user click on it. This allow very fast editing of the content without the need to delete the previous content or moving the cursor with the mouse.
1. Using the code-behind file
The most easy way is maybe to use the code-behind (for example mywindow.xaml.cs) and to subscribe to the events we want (here we must capture MouseDown, MouseUp and GotFocus events):
public Window1()
{
InitializeComponent();
textbox.AddHandler(TextBox.GotFocusEvent, new RoutedEventHandler(OnGotFocus));
textbox.AddHandler(TextBox.MouseDownEvent, new RoutedEventHandler(OnGotFocus));
textbox.AddHandler(TextBox.MouseUpEvent, new RoutedEventHandler(OnGotFocus), true);
}
private static void OnGotFocus(object sender, RoutedEventArgs e)
{
if (sender as TextBox != null)
{
((TextBox)sender).SelectAll();
}
}
This solution is not elegant because:
- it pollutes the code-behind file (that we should try to keep as clean as possible)
- it is not reusable at all
- we have to give a name to the control (how could we do that in a DataTemplate for example ?)
2. Using inheritance
If you want to fix the 3 previous points, you can create a new control that inherit TextBox class and override MouseDown, MouseUp and GotFocus events:
class TextboxInherited : TextBox
{
protected override void OnGotFocus(System.Windows.RoutedEventArgs e)
{
base.OnGotFocus(e);
SelectAll();
}
protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e)
{
base.OnMouseDown(e);
SelectAll();
}
protected override void OnMouseUp(System.Windows.Input.MouseButtonEventArgs e)
{
base.OnMouseUp(e);
SelectAll();
}
}
I don’t really like this solution neither because:
- it creates a new control only to add a very basic functionality
- it is not possible to have a TextBox and enable the functionality “on the fly” (using a DataBinding for example)
- it does not the leverage the attached property mechanism :p
3. Using an attached property
The last solution might look more complicated for a simple feature, but it is also more powerful. I created a static class, SelectableTextbox that defines an attached property SelectAllOnInput of type bool. Setting this attached property to true on a Textbox will cause the OnSelectAllOnClickChanged fonction to add handler on the targeted Textbox.
public static class SelectableTextBox
{
#region SelectAllOnClick attached property
public static readonly DependencyProperty SelectAllOnInputProperty =
DependencyProperty.RegisterAttached("SelectAllOnInput", typeof(bool), typeof(SelectableTextBox),
new FrameworkPropertyMetadata((bool)false,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnSelectAllOnClickChanged)));
public static bool GetSelectAllOnInput(DependencyObject d)
{
return (bool)d.GetValue(SelectAllOnInputProperty);
}
public static void SetSelectAllOnInput(DependencyObject d, bool value)
{
d.SetValue(SelectAllOnInputProperty, value);
}
#endregion
///
/// Handles changes to the SelectAllOnClick property.
///
private static void OnSelectAllOnClickChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender as TextBox != null && (bool)e.NewValue)
{
((TextBox)sender).AddHandler(TextBox.MouseUpEvent, new RoutedEventHandler(OnSelectAllText), true);
((TextBox)sender).AddHandler(TextBox.MouseDownEvent, new RoutedEventHandler(OnSelectAllText));
((TextBox)sender).AddHandler(TextBox.GotFocusEvent, new RoutedEventHandler(OnSelectAllText));
}
else if (sender as TextBox != null && !(bool)e.NewValue)
{
((TextBox)sender).RemoveHandler(TextBox.MouseUpEvent, new RoutedEventHandler(OnSelectAllText));
((TextBox)sender).RemoveHandler(TextBox.MouseDownEvent, new RoutedEventHandler(OnSelectAllText));
((TextBox)sender).RemoveHandler(TextBox.GotFocusEvent, new RoutedEventHandler(OnSelectAllText));
}
}
///
/// Handler that select all TextBox's text
///
private static void OnSelectAllText(object sender, RoutedEventArgs e)
{
if (sender as TextBox != null)
{
((TextBox)sender).SelectAll();
}
}
}
In the XAML, it becomes very straightforward to enable the new functionality on any Textbox, we just need to set the SelectAllOnInput property to true:
Conclusion
In this article I showed 3 ways to add a basic functionality to the Textbox control.
The first one is maybe the one we’ll try first, to see if the behavior matched what we need. However, because it is not reusable and because it pollutes the code-behind file, I do not recommend it.
The second one is the traditional way to add a functionality to an existing control: using inheritance. This solutions works well but does not leverage the new WPF mechanisms. I also think it’s a shame to create a new control just to add this feature.
The latest solution is clean and reusable. Because it uses a very new way to think about the way we design our apps, it might be not the first one we think about. But once we get this attached behavior pattern into the head, it’s a great help ;-).
Also, I recommend you to have a look at Josh’s article about attached behavior. Josh wrote an article about a very nice way to solve a problem that would be hard to fix without this feature.
Here is the demo project that I used while writing this article.
Thanks for the idea. The remaining problems is that all text is select *whenever* a textbox receives a mouse click – even when trying to select some of the text.
Again, an attached property can come to the rescue so the “HasFocus” state of a textbox can be recorded after the first mouse up event and subsequent clicks allowed without selecting all. The HasFocus attached property can be set to false when the text box loses focus so the next time it receives focus the text will again be highlighted.
Bill, thanks for the idea for this particular problem 🙂
Awesome! Thanks you very much for the problem in a much more elegant way! 🙂
Awesome! Thank you very much for solving the problem in a much more elegant way! 🙂