[ACCEPTED]-MVVM and the TextBox's SelectedText property-selectedtext
There's no straightforward way to bind SelectedText 5 to a data source, because it's not a DependencyProperty... however, it 4 quite easy to create an attached property 3 that you could bind instead.
Here's a basic 2 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 1 that in XAML :
<TextBox Text="{Binding Message}" u:TextBoxHelper.SelectedText="{Binding SelectedText}" />
The sample applications in the WPF Application Framework (WAF) chose another 8 way to solve this issue. There the ViewModel 7 is allowed to access the View through an 6 interface (IView) and so it can request 5 the current SelectedText.
I believe Binding 4 shouldn’t be used in every scenario. Sometimes 3 writing a few lines in code behind is much 2 cleaner than using highly advanced helper 1 classes. But that’s just my opinion :-)
jbe
I know it's been answered and accepted, but 7 I thought I would add my solution. I use 6 a Behavior to bridge between the view model 5 and the TextBox. The behavior has a dependency 4 property (CaretPositionProperty) which can 3 be bound two way to the view model. Internally 2 the behavior deals with the updates to/from 1 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;
}
}
For anyone using the Stylet MVVM Framework, it is possible to 8 accomplish this by taking advantage of its 7 support for binding events to ViewModel 6 methods via an "action" (although 5 some might consider it a little hacky).
The 4 TextBox event that you need to handle is SelectionChanged
. Create 3 a suitable method in the ViewModel to handle 2 this event:
public void OnTextSelectionChanged(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is TextBox textBox)
{
// Do something with textBox.SelectedText
// Note: its value will be "" if no text is selected, not null
}
}
Then, in the XAML, hook the event 1 to this method via a Stylet Action
markup:
xmlns:s="https://github.com/canton7/Stylet"
...
<TextBox SelectionChanged="{s:Action OnTextSelectionChanged}" />
As Timores pointed out in a comment on the 10 solution from Thomas Levesque, there is 9 a problem that the initial call to the propertyChangedCallback 8 for the FrameworkPropertyMetadata might 7 never happen when the property in the view 6 model is not changed.
The problem occurs 5 only when the default value for the FrameworkPropertyMetadata 4 matches the property value in the view model.
I 3 solved that by using a random default value 2 which should be very unlikely to match the 1 value in the view model.
Code:
public static class TextBoxAssist
{
// This strange default value is on purpose it makes the initialization problem very unlikely.
// If the default value matches the default value of the property in the ViewModel,
// the propertyChangedCallback of the FrameworkPropertyMetadata is initially not called
// and if the property in the ViewModel is not changed it will never be called.
private const string SelectedTextPropertyDefault = "pxh3949%lm/";
public static string GetSelectedText(DependencyObject obj)
{
return (string)obj.GetValue(SelectedTextProperty);
}
public static void SetSelectedText(DependencyObject obj, string value)
{
obj.SetValue(SelectedTextProperty, value);
}
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.RegisterAttached(
"SelectedText",
typeof(string),
typeof(TextBoxAssist),
new FrameworkPropertyMetadata(
SelectedTextPropertyDefault,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
SelectedTextChanged));
private static void SelectedTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
if (dependencyObject is not TextBox textBox)
{
return;
}
var oldValue = eventArgs.OldValue as string;
var newValue = eventArgs.NewValue as string;
if (oldValue == SelectedTextPropertyDefault && newValue != SelectedTextPropertyDefault)
{
textBox.SelectionChanged += SelectionChangedForSelectedText;
}
else if (oldValue != SelectedTextPropertyDefault && newValue == SelectedTextPropertyDefault)
{
textBox.SelectionChanged -= SelectionChangedForSelectedText;
}
if (newValue is not null && newValue != textBox.SelectedText)
{
textBox.SelectedText = newValue;
}
}
private static void SelectionChangedForSelectedText(object sender, RoutedEventArgs eventArgs)
{
if (sender is TextBox textBox)
{
SetSelectedText(textBox, textBox.SelectedText);
}
}
}
XAML:
<TextBox Text="{Binding Message}" u:TextBoxAssist.SelectedText="{Binding SelectedText}" />
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.