views:

240

answers:

5

I have found this question http://stackoverflow.com/questions/2245928/mvvm-and-the-textboxs-selectedtext-property. However, I am having trouble getting the solution given to work. This is my non-working code, in which I am trying to display the first textbox's selected text in the second textbox.

View:

SelectedText and Text are just string properties from my ViewModel.

<TextBox Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}"  Height="155" HorizontalAlignment="Left" Margin="68,31,0,0" Name="textBox1" VerticalAlignment="Top" Width="264" AcceptsReturn="True" AcceptsTab="True" local:TextBoxHelper.SelectedText="{Binding SelectedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" />
        <TextBox Text="{Binding SelectedText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Height="154" HorizontalAlignment="Left" Margin="82,287,0,0" Name="textBox2" VerticalAlignment="Top" Width="239" />

TextBoxHelper

 public static class TextBoxHelper
{
    #region "Selected Text"
    public static string GetSelectedText(DependencyObject obj)
    {
        return (string)obj.GetValue(SelectedTextProperty);
    }

    public static void SetSelectedText(DependencyObject obj, string value)
    {
        obj.SetValue(SelectedTextProperty, value);
    }

    // Using a DependencyProperty as the backing store for SelectedText.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedTextProperty =
        DependencyProperty.RegisterAttached(
            "SelectedText",
            typeof(string),
            typeof(TextBoxHelper),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));

    private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        TextBox tb = obj as TextBox;
        if (tb != null)
        {
            if (e.OldValue == null && e.NewValue != null)
            {
                tb.SelectionChanged += tb_SelectionChanged;
            }
            else if (e.OldValue != null && e.NewValue == null)
            {
                tb.SelectionChanged -= tb_SelectionChanged;
            }

            string newValue = e.NewValue as string;

            if (newValue != null && newValue != tb.SelectedText)
            {
                tb.SelectedText = newValue as string;
            }
        }
    }

    static void tb_SelectionChanged(object sender, RoutedEventArgs e)
    {
        TextBox tb = sender as TextBox;
        if (tb != null)
        {
            SetSelectedText(tb, tb.SelectedText);
        }
    }
    #endregion

}

What am I doing wrong?

A: 

You need a normal .net property wrapper for the dependencyproperty, some like:

public string SelectedText
{
   set {SetSelectedText(this, value);}
...

It is not required by runtime (runtime use set/get) but it is required by designer and compiler.

Codism
The SelectedText property is an Attached (Dependency) Property not a 'normal' Dependency Property. For an overview see http://msdn.microsoft.com/en-us/library/ms749011.aspx
Richard C. McGuire
Ok, I was not very careful when I was reading the original question. But don't you think the down vote was too much to a post trying to help?
Codism
Don't take getting downvoted personally. It's just a tool to help people sift out helpful answers from unhelpful ones. If you realize you've posted an unhelpful answer and it's being downvoted, you can delete it.
RandomEngy
+1  A: 

In order for the SelectedTextChanged handler to fire the SelectedText property must have an initial value. If you don't initialize this to some value (string.Empty as a bare minimum) then this handler will never fire and in turn you'll never register the tb_SelectionChanged handler.

Richard C. McGuire
I changed it like this:new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));But it still does not work. Did I misunderstand what you were saying?
Justin
When I ran through your sample code I set the value of the SelectedText property on the ViewModel to string.Empty. Changing the PropertyMetadata will not cause the changed handler to fire.
Richard C. McGuire
I tried setting SelectedText to string.Empty, but the text selected in the first textbox is still not shown in the second textbox.
Justin
A: 

Your binding attempts to bind the Text property of your TextBox to a SelectedText property of the TextBox's current data context. Since you're working with an attached property, not with a property hanging off of your data context, you will need to give more information in your binding:

<TextBox Text="{Binding local:TextBoxHelper.SelectedText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" ... />

Where local has been associated with the CLR namespace containing the TextBoxHelper class.

HTH,
Kent

Kent Boogaart
There is a SelectedText property in the current data context though, and it is bound to the attached property via:local:TextBoxHelper.SelectedText="{Binding SelectedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"
Justin
+1  A: 

The reason this is not working is that the property change callback isn't being raised (as the bound value from your VM is the same as the default value specified in the metadata for the property). More fundamentally though, your behavior will detach when the selected text is set to null. In cases like this, I tend to have another attached property that is simply used to enable the monitoring of the selected text, and then the SelectedText property can be bound. So, something like so:

#region IsSelectionMonitored
public static readonly DependencyProperty IsSelectionMonitoredProperty = DependencyProperty.RegisterAttached(
    "IsSelectionMonitored",
    typeof(bool),
    typeof(PinnedInstrumentsViewModel),
    new FrameworkPropertyMetadata(OnIsSelectionMonitoredChanged));

[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetIsSelectionMonitored(TextBox d)
{
    return (bool)d.GetValue(IsSelectionMonitoredProperty);
}

public static void SetIsSelectionMonitored(TextBox d, bool value)
{
    d.SetValue(IsSelectionMonitoredProperty, value);
}

private static void OnIsSelectionMonitoredChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    TextBox tb = obj as TextBox;
    if (tb != null)
    {
        if ((bool)e.NewValue)
        {
            tb.SelectionChanged += tb_SelectionChanged;
        }
        else
        {
            tb.SelectionChanged -= tb_SelectionChanged;
        }

        SetSelectedText(tb, tb.SelectedText);
    }
}
#endregion

#region "Selected Text"
public static string GetSelectedText(DependencyObject obj)
{
    return (string)obj.GetValue(SelectedTextProperty);
}

public static void SetSelectedText(DependencyObject obj, string value)
{
    obj.SetValue(SelectedTextProperty, value);
}

// Using a DependencyProperty as the backing store for SelectedText.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedTextProperty =
    DependencyProperty.RegisterAttached(
        "SelectedText",
        typeof(string),
        typeof(TextBoxHelper),
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));

private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    TextBox tb = obj as TextBox;
    if (tb != null)
    {
        tb.SelectedText = e.NewValue as string;            
    }
}

static void tb_SelectionChanged(object sender, RoutedEventArgs e)
{
    TextBox tb = sender as TextBox;
    if (tb != null)
    {
        SetSelectedText(tb, tb.SelectedText);
    }
}
#endregion

And then in your XAML, you'd have to add that property to your first TextBox:

<TextBox ... local:TextBoxHelper.IsSelectionMonitored="True" local:TextBoxHelper.SelectedText="{Binding SelectedText, Mode=OneWayToSource}" />
Abe Heidebrecht
+1  A: 

This works for me using the class TextBoxHelper. As other mentioned, you need to initialize the SelectedText property of TextBoxHelper with a non null value. Instead of data binding to a string property (SelText) on the view you should bind to a string property of your VM which should implement INotifyPropertyChanged.

XAML:

<Window x:Class="TextSelectDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TextSelectDemo"
    Height="300" Width="300">
    <StackPanel>
        <TextBox local:TextBoxHelper.SelectedText="{Binding Path=SelText, Mode=TwoWay}" />
        <TextBox Text="{Binding Path=SelText}" />
    </StackPanel>
</Window>

Code behind:

using System.ComponentModel;
using System.Windows;

namespace TextSelectDemo
{
    public partial class Window1 : Window, INotifyPropertyChanged
    {
        public Window1()
        {
            InitializeComponent();

            SelText = string.Empty;

            DataContext = this;
        }

        private string _selText;
        public string SelText
        {
            get { return _selText; }
            set
            {
                _selText = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("SelText"));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}
Wallstreet Programmer