5

I have a simple WPF page with one text box field that my client wants highlighted when the page shows up. In code behind, it would be three lines, but I'm sogging through MVVM (which I'm starting to think is a little over-rated). I've tried so many different variants of behaviors and global events and FocusManager.FocusedElement, but nothing I do will do this.

Ultimately the most of the code I've been using calls these two lines:

Keyboard.Focus(textBox);
textBox.SelectAll();

But no matter where I put these lines the text box is only focused; no text is selected. I have never had this much trouble with something so simple. I've been hitting my head against the internets for two hours. Does anyone know how to do this?

Again, all I want to do is have the text box focus and it's text all selected when the page is navigated to. Please help!

Jordan
  • 9,642
  • 10
  • 71
  • 141

2 Answers2

6

"Focus" and "Select All Text from a TextBox" is a View-specific concern.

Put that in code Behind. It does not break the MVVM separation at all.

public void WhateverControl_Loaded(stuff)
{
    Keyboard.Focus(textBox);
    textBox.SelectAll();
}

If you need to do it in response to a specific application/business logic. Create an Attached Property.

Or:

have your View resolve the ViewModel by:

this.DataContext as MyViewModel;

then create some event in the ViewModel to which you can hook:

public class MyViewModel
{
    public Action INeedToFocusStuff {get;set;}

    public void SomeLogic()
    {
        if (SomeCondition)
            INeedToFocusStuff();
    }
}

then hook it up in the View:

public void Window_Loaded(Or whatever)
{
    var vm = this.DataContext as MyViewModel;
    vm.INeedToFocusStuff += FocusMyStuff;
}

public void FocusMyStuff()
{
    WhateverTextBox.Focus();
}

See how this simple abstraction keeps View related stuff in the View and ViewModel related stuff in the ViewModel, while allowing them to interact. Keep it Simple. You don't need NASA's servers for a WPF app.


And no MVVM is not overrated, MVVM is extremely helpful and I would say even necessary. You'll quickly realize this as soon as you begin working with ItemsControls such as ListBoxes or DataGrids.

Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • I've been using WPF and MVVM for years. The benefits I get pail in comparison to some of the difficulties I have. I learned under the school that says to put NOTHING in the code behind and make everything in the UI declarable. Maybe that is what I need to turn my back on, but I have so much trouble drawing that line sometimes. – Jordan Jan 09 '14 at 18:06
  • @Jordan I see that as self-evident: `Data` goes into a `Model`. `Presentation Logic` goes into a ViewModel. `UI` goes into a View. – Federico Berasategui Jan 09 '14 at 18:15
  • The problem seems to be with the call to Keyboard.Focus and textbox.SelectAll itself. No matter where I call `SelectAll` it doesn't. – Jordan Jan 09 '14 at 19:41
  • And when I'm writing a CRUD application it is straight forward, but 90% of the time my applications are not data-centered, so mapping them into that pattern is a little more obscure. – Jordan Jan 09 '14 at 19:42
  • @Jordan are you calling these methods in the `Loaded` event? Post your current code. – Federico Berasategui Jan 09 '14 at 19:47
  • @Jordan regarding your second comment, MVVM is not only for CRUD-style stuff. Look at [this example](http://stackoverflow.com/questions/15819318/how-to-create-and-connect-custom-user-buttons-controls-with-lines-using-windows/15821573#15821573) which uses MVVM to maximize the decoupling of the UI and the Data Model / Application logic, thus gaining scalability and maintainability. – Federico Berasategui Jan 09 '14 at 19:50
1

Here are some workthroughs:

Use Interaction.Behaviors

You can install the NuGet package named Microsoft.Xaml.Behaviors.Wpf, and write your own Behavior:

using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls;

public class AutoSelectAllBehavior : Behavior<TextBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.GotFocus += AssociatedObject_GotFocus;
    }

    private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
    {
        if (AssociatedObject is TextBox box)
            box.SelectAll();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
    }
}

and attach this behavior to the TextBox in the xaml:

<!-- xmlns:i="http://schemas.microsoft.com/xaml/behaviors" -->

<TextBox>
    <i:Interaction.Behaviors>
        <br:AutoSelectAllBehavior />
    </i:Interaction.Behaviors>
</TextBox>

Use Interaction.Triggers

This is in the same package as mentioned in the last section. This special can be considered to let you be able to bind UIElement events to your ViewModel.

In your ViewModel, suppose you have an ICommand relay command (You may also need Microsoft.Toolkit.MVVM so that you can use some handy relay commands):

public ICommand SelectAllCommand { get; }

public ViewModel()
{
    SelectAllCommand = new RelayCommand<TextBox>(box => box.SelectAll());
}

and then attach this command to the TextBox by setting the triggers:

<TextBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="GotFocus">
            <i:InvokeCommandAction Command="{Binding SelectAllCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TextBox}}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

Use Attached Property

You can also use attached property (write your own class derived from TextBox and use dependency property is quite similar):

using System.Windows;
using System.Windows.Controls;

public class TextBoxProperties
{
    public static bool GetAutoSelectAll(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoSelectAllProperty);
    }

    public static void SetAutoSelectAll(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoSelectAllProperty, value);
    }

    public static readonly DependencyProperty AutoSelectAllProperty =
        DependencyProperty.RegisterAttached("AutoSelectAll", typeof(bool), typeof(TextBoxProperties), new PropertyMetadata(false, TextBoxProperties_PropertyChanged));

    private static void TextBoxProperties_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue)
        {
            if (d is TextBox box)
            {
                box.GotFocus += TextBox_GotFocus;
            }
        }
    }
    private static void TextBox_GotFocus(object sender, RoutedEventArgs e)
    {
        var box = sender as TextBox;
        box.SelectAll();
    }
}

Then you can use it like:

<!-- xmlns:ap="..." -->

<TextBox ap:TextBoxProperties.AutoSelectAll="True" />
Obsidian
  • 799
  • 1
  • 6
  • 18