7

I have a user control that is nested inside a window that is acting as a shell for a dialog display. I ignore focus in the shell window, and in the hosted user control I use the FocusManager to set the initial focus to a named element (a textbox) as shown below.

This works, setting the cursor at the beginning of the named textbox; however I want all text to be selected.

The TextBoxSelectionBehavior class (below) usually does exactly that, but not in this case. Is there an easy xaml fix to get the text in the named textbox selected on initial focus?

Cheers,
Berryl

TextBox Selection Behavior

// in app startup
TextBoxSelectionBehavior.RegisterTextboxSelectionBehavior();

/// <summary>
/// Helper to select all text in the text box on entry
/// </summary>
public static class TextBoxSelectionBehavior
{
    public static void RegisterTextboxSelectionBehavior()
    {
        EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotFocusEvent, new RoutedEventHandler(OnTextBox_GotFocus));
    }

    private static void OnTextBox_GotFocus(object sender, RoutedEventArgs e)
    {
        var tb = (sender as TextBox);
        if (tb != null)
            tb.SelectAll();
    }
}

The hosted UserControl

<UserControl   
<DockPanel KeyboardNavigation.TabNavigation="Local" 
    FocusManager.FocusedElement="{Binding ElementName=tbLastName}" >

            <TextBox x:Name="tbLastName" ... />

stop gap solution

Per comments with Rachel below, I ditched the FocusManger in favor of some code behind:

tbLastName.Loaded += (sender, e) => tbLastName.Focus();

Still would love a declarative approach for a simple and common chore though...

Berryl
  • 12,471
  • 22
  • 98
  • 182

2 Answers2

15

I usually use an AttachedProperty to make TextBoxes highlight their text on focus. It is used like

<TextBox local:HighlightTextOnFocus="True" />

Code for attached property

public static readonly DependencyProperty HighlightTextOnFocusProperty =
    DependencyProperty.RegisterAttached("HighlightTextOnFocus", 
    typeof(bool), typeof(TextBoxProperties),
    new PropertyMetadata(false, HighlightTextOnFocusPropertyChanged));


[AttachedPropertyBrowsableForChildrenAttribute(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetHighlightTextOnFocus(DependencyObject obj)
{
    return (bool)obj.GetValue(HighlightTextOnFocusProperty);
}

public static void SetHighlightTextOnFocus(DependencyObject obj, bool value)
{
    obj.SetValue(HighlightTextOnFocusProperty, value);
}

private static void HighlightTextOnFocusPropertyChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    var sender = obj as UIElement;
    if (sender != null)
    {
        if ((bool)e.NewValue)
        {
            sender.GotKeyboardFocus += OnKeyboardFocusSelectText;
            sender.PreviewMouseLeftButtonDown += OnMouseLeftButtonDownSetFocus;
        }
        else
        {
            sender.GotKeyboardFocus -= OnKeyboardFocusSelectText;
            sender.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDownSetFocus;
        }
    }
}

private static void OnKeyboardFocusSelectText(
    object sender, KeyboardFocusChangedEventArgs e)
{
    var textBox = e.OriginalSource as TextBox;
    if (textBox != null)
    {
        textBox.SelectAll();
    }
}

private static void OnMouseLeftButtonDownSetFocus(
    object sender, MouseButtonEventArgs e)
{
    TextBox tb = FindAncestor<TextBox>((DependencyObject)e.OriginalSource);

    if (tb == null)
        return;

    if (!tb.IsKeyboardFocusWithin)
    {
        tb.Focus();
        e.Handled = true;
    }
}

static T FindAncestor<T>(DependencyObject current)
    where T : DependencyObject
{
    current = VisualTreeHelper.GetParent(current);

    while (current != null)
    {
        if (current is T)
        {
            return (T)current;
        }
        current = VisualTreeHelper.GetParent(current);
    };
    return null;
}

Edit

Based on comments below, what about just getting rid of the FocusManager.FocusedElement and setting tb.Focus() and tb.SelectAll() in the Loaded event of your TextBox?

Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Your code is nice and it works as well as what I was using, but doesn't fix my problem. I'm not sure if I have to invoke MoveFocus somewhere instead of what I am doing currently. Cheers – Berryl Oct 18 '11 at 20:48
  • I *think* the line in your code where you cast obj as UIElement should be followed by "if(sender != null)" instead of obj!=null. Cheers – Berryl Oct 18 '11 at 20:51
  • @Berryl You're right, I've never really noticed that before. I guess as an easy solution you could probably hook into the `Loaded` event of the `TextBox` and set it selected in the code behind. – Rachel Oct 19 '11 at 01:38
  • That is what is weird about this problem - the cursor is being moved *after* all is loaded, and after the attached property has selected all text. Can't put my finger on it yet. – Berryl Oct 19 '11 at 15:22
  • 1
    @Berryl What about just getting rid of the `FocusManager.FocusedElement` and setting `tb.Focus()` and `tb.SelectAll()` in the `Loaded` event of your TextBox? – Rachel Oct 19 '11 at 15:31
  • Interesting, the 'tb.Focus()' works by itself, and now the attached property does its thing and takes care of 'SelectAll()'. I hate code behind but its hard to argue with working code, so I'll live with it for now! Cheers – Berryl Oct 19 '11 at 16:03
  • @Berryl Glad it worked out :) I never mind using code-behind the View if it's a View-specific purpose such as setting the Focus. I'll update my answer to include my last comment – Rachel Oct 19 '11 at 16:06
4

As stated above, you can add an event handler for the Loaded event to set focus and select all text:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        base.DataContext = new Person { FirstName = "Joe", LastName = "Smith" };

        base.Loaded += delegate
        {
            this._firstNameTextBox.Focus();
            this._firstNameTextBox.SelectAll();
        };
    }
}  
Andy
  • 91
  • 2