MVVM- How can I select text in a textbox?

15,930

Solution 1

Whenever I am trying to directly affect the the View in a "pure" MVVM application (no code-behind in View), I will use Attached Properties to encapsulate whatever effect I am trying to achieve. I will create an interface that defines the actions I wish to take using custom events. I then implement this interface in each ViewModel that will be "running" these commands on the View. Finally, I bind my ViewModel to the attached property in my View definition. The following code shows how to this for SelectAll and a TextBox. This code can be easily expanded to perform just about any action on any component in the View.

My Attached Property and interface definition:

using System.Windows;
using System.Windows.Controls;
using System;
using System.Collections.Generic;

namespace SelectAllSample
{
    public static class TextBoxAttach
    {
        public static readonly DependencyProperty TextBoxControllerProperty = DependencyProperty.RegisterAttached(
            "TextBoxController", typeof(ITextBoxController), typeof(TextBoxAttach),
            new FrameworkPropertyMetadata(null, OnTextBoxControllerChanged));
        public static void SetTextBoxController(UIElement element, ITextBoxController value)
        {
            element.SetValue(TextBoxControllerProperty, value);
        }
        public static ITextBoxController GetTextBoxController(UIElement element)
        {
            return (ITextBoxController)element.GetValue(TextBoxControllerProperty);
        }

        private static readonly Dictionary<ITextBoxController, TextBox> elements = new Dictionary<ITextBoxController, TextBox>();
        private static void OnTextBoxControllerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var element = d as TextBox;
            if (element == null)
                throw new ArgumentNullException("d");

            var oldController = e.OldValue as ITextBoxController;
            if (oldController != null)
            {
                elements.Remove(oldController);
                oldController.SelectAll -= SelectAll;
            }

            var newController = e.NewValue as ITextBoxController;
            if (newController != null)
            {
                elements.Add(newController, element);
                newController.SelectAll += SelectAll;
            }
        }
        private static void SelectAll(ITextBoxController sender)
        {
            TextBox element;
            if (!elements.TryGetValue(sender, out element))
                throw new ArgumentException("sender");
            element.Focus();
            element.SelectAll();
        }
    }

    public interface ITextBoxController
    {
        event SelectAllEventHandler SelectAll;
    }

    public delegate void SelectAllEventHandler(ITextBoxController sender);
}

My ViewModel definition:

public class MyViewModel : ITextBoxController
{
    public MyViewModel()
    {
        Value = "My Text";
        SelectAllCommand = new RelayCommand(p =>
        {
            if (SelectAll != null)
                SelectAll(this);
        });
    }

    public string Value { get; set; }
    public RelayCommand SelectAllCommand { get; private set; }

    public event SelectAllEventHandler SelectAll;
}

My View definition:

<Window x:Class="SelectAllSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:loc="clr-namespace:SelectAllSample"
    Title="Window1" Height="150" Width="150">
    <x:Code><![CDATA[
        public Window1()
        {
            InitializeComponent();
            DataContext = new MyViewModel();
        }
    ]]></x:Code>
    <StackPanel>
        <TextBox Text="{Binding Value}" loc:TextBoxAttach.TextBoxController="{Binding}" />
        <Button Content="Select All" Command="{Binding SelectAllCommand}" />
    </StackPanel>
</Window>

Note: Thanks to Josh Smith for RelayCommand (see code in Figure 3 on this page). It is used in MyViewModel in this example (and just about all my MVVM code).

Solution 2

find a good introduction to attached properties here: http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx

Share:
15,930

Related videos on Youtube

Justin
Author by

Justin

I am very new to programming. Learning C# on the internet. Am working on a minimalistic text editor.

Updated on April 18, 2022

Comments

  • Justin
    Justin about 2 years

    Is there a MVVM way to select text in a textbox? The MVVM framework that I am using is Laurent Bugnion's MVVM Light Toolkit.

  • Justin
    Justin about 14 years
    I am getting an error: "Delegate 'System.Action' does not take 1 arguments"?
  • Joseph Sturtevant
    Joseph Sturtevant about 14 years
    Where is the error at? Make sure that the number of parameters on your event handler definition are consistent with the way you define your handler (and use it). I'm using a Lambda with RelayCommand to raise the event in my example.
  • Justin
    Justin about 14 years
    I have a relay command, like in your example and this code in the constructor of my viewmodel: SelectAllCommand = new RelayCommand(p => SelectAll(this) The error is on the p in the lambda expression. Btw thank you for continuing to help me.
  • Justin
    Justin about 14 years
    At the moment, I am using the exact code you provided in your example.
  • Joseph Sturtevant
    Joseph Sturtevant about 14 years
    Aha! I just looked at the MVVM Light definition of RelayCommand. It doesn't take a parameter (Josh Smith's, which I used, does). This means you just need to define your lambda without a parameter like this: SelectAllCommand = new RelayCommand(() => { if(SelectAll != null) SelectAll(this); } )
  • Joseph Sturtevant
    Joseph Sturtevant about 14 years
    Another thing to note: if you raise the SelectAll event without checking that it isn't null (as you show in your comment), you risk a NullReferenceException if no one has registered to listen for the event.
  • Rafal Roszak
    Rafal Roszak about 14 years
    Joseph, there is a generic version (RelayCommand<T>) in the MVVM Light toolkit which you can use for commands when you need CommandParameter. Cheers, Laurent
  • Joel B Fant
    Joel B Fant about 13 years
    public event EventHandler MyEvent = (o,e) => {}; This style of event declaration eliminates the need to copy the multicast delegate MyEvent to a local variable (to avoid race conditions) and check if it is null before raising it. It'll never be null.
  • Gusdor
    Gusdor almost 13 years
    An elegant solution but surely just defining 2 attached properties (SelectionStart, SelectionEnd) would simplify the process alot? I understand you are trying to encapsulate as much textbox functionality as you can in the controller but I'm worried about how maintainable it is if you want to add new functionality.
  • Andy
    Andy almost 11 years
    I know that this is an old question, but I'm hoping somebody will still respond! :) How could I adapt this solution so that I can have two different buttons to select text in two different textboxes? Thank you.