MVVM and the TextBox's SelectedText property

北城余情 提交于 2019-11-27 20:43:36

There's no straightforward way to bind SelectedText to a data source, because it's not a DependencyProperty... however, it quite easy to create an attached property that you could bind instead.

Here's a basic implementation :

public static class TextBoxHelper
{

    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);
        }
    }

}

You can then use it like that in XAML :

<TextBox Text="{Binding Message}" u:TextBoxHelper.SelectedText="{Binding SelectedText}" />

The sample applications in the WPF Application Framework (WAF) chose another way to solve this issue. There the ViewModel is allowed to access the View through an interface (IView) and so it can request the current SelectedText.

I believe Binding shouldn’t be used in every scenario. Sometimes writing a few lines in code behind is much cleaner than using highly advanced helper classes. But that’s just my opinion :-)

jbe

jjrdk

I know it's been answered and accepted, but I thought I would add my solution. I use a Behavior to bridge between the view model and the TextBox. The behavior has a dependency property (CaretPositionProperty) which can be bound two way to the view model. Internally the behavior deals with the updates to/from the TextBox.

public class SetCaretIndexBehavior : Behavior<TextBox>
    {
        public static readonly DependencyProperty CaretPositionProperty;
        private bool _internalChange;

    static SetCaretIndexBehavior()
    {

    CaretPositionProperty = DependencyProperty.Register("CaretPosition", typeof(int), typeof(SetCaretIndexBehavior), new PropertyMetadata(0, OnCaretPositionChanged));
}

public int CaretPosition
{
    get { return Convert.ToInt32(GetValue(CaretPositionProperty)); }
    set { SetValue(CaretPositionProperty, value); }
}

protected override void OnAttached()
{
    base.OnAttached();
    AssociatedObject.KeyUp += OnKeyUp;
}

private static void OnCaretPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var behavior = (SetCaretIndexBehavior)d;
    if (!behavior._internalChange)
    {
        behavior.AssociatedObject.CaretIndex = Convert.ToInt32(e.NewValue);
    }
}

    private void OnKeyUp(object sender, KeyEventArgs e)
    {
        _internalChange = true;
        CaretPosition = AssociatedObject.CaretIndex;
        _internalChange = false;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!