2

I've found this question which helps me achieve part of what I want to do: MVVM- How can I select text in a textbox?

My first problem is that I have two text boxes, with a "Select All" button for each, but I can't work out how to adapt the accepted answer so that I can control each independently.

Also, I would like to add a "Copy selected text" button for each.

How can I do this whilst sticking to the MVVM pattern?

Community
  • 1
  • 1
Andy
  • 3,600
  • 12
  • 53
  • 84

1 Answers1

4

You can either bind the two buttons to different commands which invoke work on different textboxes, or you could use commandParameters to differentiate which to work on.

You can follow through on your linked post by creating an AttachedProperty or just make a custom control. What you need to do essentially is create a bindable property for the text selection. TextBox's property "SelectedText" sounds like a great idea but if you try to bind to it in WPF it throws an error as it is not a DependencyProperty. A property must be a DependencyProperty for you to bind to it.

So you create one, such as IsTextSelected as a bool, and when it changes, your AttachedProperty or custom control handles it and does SelectAll() or maybe SelectedText=Text;

I suggested AttachedProperty if doing a single item. However, you asked for custom control, which I believe should be used if doing multiple functionality improvements to one type of control, of which they are not to be reused on different type.

public class SmartTextBox : TextBox
{
    public static readonly DependencyProperty IsSelectedTextProperty = DependencyProperty.RegisterAttached("IsSelectedText",
        typeof(bool), typeof(SmartTextBox), new FrameworkPropertyMetadata(false, OnIsSelectedChanged));

    public bool IsSelectedText
    {
        get { return (bool)GetValue(IsSelectedTextProperty); }
        set { SetValue(IsSelectedTextProperty, value); }
    }

    private static void OnIsSelectedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        SmartTextBox textbox = sender as SmartTextBox;
        if ((bool)e.NewValue)
        {
            textbox.Focus();
            textbox.SelectAll();
        }
    }
}

Usage: ViewModel

  • Note1 I am not doing an IF on whether the value is the same during the set so that you can force it at any time, not keeping track of what user has done.
  • Note2 Make multiple properties, IsSelectedUsername, IsSelectedFilepath etc, and bind these. Each SmartTextBox is bound to one and will handle one that change.

    public bool IsSelectedText
    {
        get { return isSelectedText; }
        set
        {
            isSelectedText = value;
            RaisePropertyChanged("IsSelectedText");
        }
    }
    
    private void SelectAllExecute()
    {
        IsSelectedText = true;
    }
    

Usage: XAML

xmlns:custom="clr-namespace:xyz.View.Controls"

        <custom:SmartTextBox Text="{Binding Path=MyText}" 
                             IsSelectedText="{Binding Path=IsSelectedText}"/>

Retrieving the selected text, you need to add to the custom control a new dependency property you can bind to as well as the means for the control to update it. I chose when the control leaves focus rather than selection changed since I expect the user to do something like click a button before I need to know the selected text.

    public static readonly DependencyProperty SelectedText2Property = DependencyProperty.RegisterAttached("SelectedText2",
        typeof(string), typeof(SmartTextBox), new PropertyMetadata(""));

    public string SelectedText2
    {
        get { return (string)GetValue(SelectedText2Property); }
        set { SetValue(SelectedText2Property, value); }
    }

    protected override void OnLostFocus(RoutedEventArgs e)
    {
        SelectedText2 = this.SelectedText;
        base.OnLostFocus(e);
    }

XAML has it bound now:

        <custom:SmartTextBox Text="{Binding Path=MyText}" 
                             SelectedText2="{Binding Path=TheSelectedText, Mode=OneWayToSource}"
                             IsSelectedText="{Binding Path=IsSelectedText}"/>

ViewModel has a dumb property (no need to raise the change event since it is OneWayToSource)

public string TheSelectedText { get; set; }

And anywhere you can do

Console.WriteLine(TheSelectedText);
bland
  • 1,968
  • 1
  • 15
  • 22
  • I like the idea of a custom control that extends Textbox and implements some kind of SelectedText property, if I have understood your answer correctly. Can you give any links or code to allow me to do that? – Andy Aug 05 '13 at 14:43
  • Added full yet basic implementation of custom control. Make sure you make note of Note2, in order to do this independently as you specifically requested. – bland Aug 05 '13 at 15:43
  • Awesome! It's working perfectly; thanks for the notes. How could I retrieve the selected text? – Andy Aug 05 '13 at 16:05
  • @Andy Added the retrieval. Please mark as complete if this finished it for you. I think this covers everything on this topic and anything else would be a new topic. Then again, I assumed, and you can just reply back. – bland Aug 05 '13 at 16:58
  • You assumed absolutely correctly! I couldn't ask for more. Thank you very much! – Andy Aug 05 '13 at 17:55
  • Actually, just been testing it, and I can only get to the code in the if statement in `OnIsSelectedChanged` a second time if I set the binded bool back to false, before I set it to true again. Any ideas? (I followed Note1 - there is no check) – Andy Aug 05 '13 at 18:55
  • Not immediately as I didn't experience that in quick testing before posting. First thought is to just do a quick hack for now and immediately set to false after selecting all. – bland Aug 05 '13 at 19:40
  • Yeah, I'm going to do that for now, but hopefully one of us will find a less 'hackish' solution to the problem. By the way, I also have to raise a property changed event after I set it to false for it to work. – Andy Aug 05 '13 at 20:19