Wednesday, January 27, 2010

Silverlight 3: Auto select text in TextBox

Missing out of the box in Silverlight 3 is the ability to auto select the text in a textbox when it receives focus. Thankfully Attached Properties make it very easy to add the functionality. In this post I will create a AutoSelectText attached property, which when set to true will select all the text as when the textbox receives focus. And because of routed events, it is not required to add the property to every textbox on a page. Instead it can be added to the parent content control and all child textboxes will have to auto select behavior.

Usage

  1. <Grid x:Name="LayoutRoot"
  2.     attachedProperties:AttachedProperties.AutoSelectText="true">
  3.     <StackPanel Orientation="Vertical"
  4.                 HorizontalAlignment="Left">
  5.                  <TextBox Text="Will not auto select"
  6.                  MaxWidth="300"
  7.                  Margin="4"
  8.                  attachedProperties:AttachedProperties.PreventAutoSelectText="true" />
  9.         <TextBox Text="Will auto select 1"
  10.                  MaxWidth="300"
  11.                  Margin="4"/>
  12.         <TextBox Text="Will auto select 2"
  13.                  MaxWidth="300"
  14.                  Margin="4" />
  15.     </StackPanel>
  16. </Grid>

Line 2: The AutoSelectText is set to true on the root level content control (Grid). That’s it. All text boxes within the Grid will participate in the auto select text functionality.
Line 8: A control can opt out by setting the PreventAutoSelectText property to false.

Listed below are some of the key code blocks. The link to the entire source code can be found at the end of the post.

Declaring the attached property

  1. public static readonly DependencyProperty AutoSelectTextProperty = DependencyProperty.RegisterAttached("AutoSelectText",
  2.                                                                        typeof(Boolean),
  3.                                                                        typeof(AttachedProperties),
  4.                                                                        new PropertyMetadata(OnAutoSelectTextChanged));
  5.  
  6. public static readonly DependencyProperty PreventAutoSelectTextProperty = DependencyProperty.RegisterAttached("PreventAutoSelectText",
  7.                                                                               typeof(Boolean),
  8.                                                                               typeof(AttachedProperties),
  9.                                                                               null);

Line 1-2: A new boolean attached property called AutoSelectText is declared.
Line 4: The OnAutoSelectTextChanged event handler is registered, which is be invoked when the value of the property is set in the xaml.
Line 6: A new boolean attached property called PreventAutoSelectText is declared. Controls can use this property to opt out of the auto select functionality.

The dependency property changed event handler

  1. private static void OnAutoSelectTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  2. {
  3.     //This works because of event bubbling.
  4.     FrameworkElement frameworkElement = d as FrameworkElement;
  5.     if (frameworkElement != null)
  6.     {
  7.         if ((bool)e.NewValue)
  8.             frameworkElement.GotFocus += OnGotFocus;
  9.         else
  10.             frameworkElement.GotFocus -= OnGotFocus;
  11.     }
  12. }

Line 8: If the value of the AutoSelectText (accessed using e.NewValue) is true, we add the OnGotFocus event handler to the GotFocus event of the control on which the AutoSelectText property is being set (from the xaml).
Line 9: Similarly if the control does not wish to participate in auto selecting of text, we remove our event handler.

The GotFocus event handler

  1. private static void OnGotFocus(object sender, RoutedEventArgs e)
  2. {
  3.     TextBox textBox = FocusManager.GetFocusedElement() as TextBox;
  4.     if (textBox != null && !(bool)textBox.GetValue(PreventAutoSelectTextProperty))
  5.         textBox.Select(0, textBox.Text.Length);
  6. }

Line 3: Since we are using routed events, the sender parameter will not be the textbox that currently has focus. It will the root level content control (Grid) which has the AutoSelectText attached property. The FocusManager class is used to get a reference to that control that has the focus.

Why two attached properties?

Silverlight does not provide a way to check if a control has a attached property defined in the xaml. Due to this if I replace

  1. if (textBox != null && !(bool)textBox.GetValue(PreventAutoSelectTextProperty))

in the OnGotFocus method with

  1. if (textBox != null && !(bool)textBox.GetValue(AutoSelectTextProperty))

I will always get false for the textBox.GetValue call, even if the AutoSelectText has not been defined for the textbox in xaml.

Download source code for AutoSelectText attached property.

10 comments:

  1. I'm trying this in a WPF project, but FocusManager.GetFocusedElement requires "DependencyObject element" as a parameter - and it won't take null as the value. Any ideas?

    ReplyDelete
  2. I just started with WPF and have the same problem as the previous poster. I have come up with a hacky solution that works for now. It’s not elegant at all so please feel free to show me a better solution. There is a caveat too. The text only gets selected if you use the keyboard and tab between text boxes. If you use the mouse to click in the box nothing happens so watch out for that.

    Change the code in AttachedProperties.cs as follows:

    private static DependencyObject dependencyObject;

    private static void OnAutoSelectTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    dependencyObject = d;

    //This works because of event bubbling.
    FrameworkElement frameworkElement = d as FrameworkElement;
    if (frameworkElement != null)
    {
    if ((bool)e.NewValue)
    frameworkElement.GotFocus += OnGotFocus;
    else
    frameworkElement.GotFocus -= OnGotFocus;
    }
    }

    private static void OnGotFocus(object sender, RoutedEventArgs e)
    {
    //Since we are using routed events, the sender parameter will not be the textbox that currently has focus.
    //It will the root level content control (Grid) which has the AutoSelectText attached property.
    //The FocusManager class is used to get a reference to the control that has the focus.

    dependencyObject = Window.GetWindow(dependencyObject);

    TextBox textBox = FocusManager.GetFocusedElement(dependencyObject) as TextBox;
    if (textBox != null && !(bool)textBox.GetValue(PreventAutoSelectTextProperty))
    textBox.Select(0, textBox.Text.Length);
    }

    ReplyDelete
  3. Very informative blog. I really enjoyed reading it and it has doubled my knowledge in it. Thanks for great work and sharing this useful information.

    ReplyDelete
  4. Thanks a lot.
    I'm using this on a windows phone app, it works pretty well.

    ReplyDelete
  5. Thank you so much. Excellent solution.

    ReplyDelete
  6. I really like to visit your blog,excellent information i got in your blog,thanks a lot for sharing.

    ReplyDelete
  7. In case someone still cares: I also tried it with WPF. I simply replaced FocusManager.GetFocusedElement() with Keyboard.FocusedElement.
    This gives the correct element but the problem still is that the selection will be overwritten immediately when clicking with the mouse.

    ReplyDelete
  8. Subscribing to GotMouseCapture in addition to GotFocus fixes the mouse click problem in WPF

    ReplyDelete

Subscribe to my feed in your favorite feed reader