我有一个带有 ContextMenu 的 TextBox。当用户在 TextBox 内右键单击并选择适当的 MenuItem 时,我想在我的视图模型中获取 SelectedText。我还没有找到一种“MVVM”方式的好方法。
到目前为止,我的应用程序使用了 Josh Smith 的 MVVM 方式。我正在寻找转移到Cinch。不确定 Cinch 框架是否会处理此类问题。想法?
我有一个带有 ContextMenu 的 TextBox。当用户在 TextBox 内右键单击并选择适当的 MenuItem 时,我想在我的视图模型中获取 SelectedText。我还没有找到一种“MVVM”方式的好方法。
到目前为止,我的应用程序使用了 Josh Smith 的 MVVM 方式。我正在寻找转移到Cinch。不确定 Cinch 框架是否会处理此类问题。想法?
没有直接的方法将 SelectedText 绑定到数据源,因为它不是 DependencyProperty...但是,创建一个可以绑定的附加属性非常容易。
这是一个基本的实现:
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);
}
}
}
然后,您可以像在 XAML 中那样使用它:
<TextBox Text="{Binding Message}" u:TextBoxHelper.SelectedText="{Binding SelectedText}" />
WPF 应用程序框架 (WAF)中的示例应用程序选择了另一种方法来解决此问题。那里允许 ViewModel 通过接口 (IView) 访问 View,因此它可以请求当前的 SelectedText。
我相信不应该在每种情况下都使用绑定。有时在后面写几行代码比使用高级助手类要干净得多。但那只是我的个人意见 :-)
jbe
我知道它已被回答并接受,但我想我会添加我的解决方案。我使用 Behavior 在视图模型和 TextBox 之间架起桥梁。该行为有一个依赖属性(CaretPositionProperty),可以通过两种方式绑定到视图模型。在内部,该行为处理文本框的更新/来自文本框的更新。
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;
}
}
正如 Timores 在对 Thomas Levesque 解决方案的评论中指出的那样,当视图模型中的属性未更改时,可能永远不会发生对 FrameworkPropertyMetadata 的 propertyChangedCallback 的初始调用的问题。
仅当 FrameworkPropertyMetadata 的默认值与视图模型中的属性值匹配时,才会出现此问题。我通过使用随机默认值解决了这个问题,该默认值不太可能与视图模型中的值匹配。
代码:
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}" />
对于使用Stylet MVVM 框架的任何人,都可以通过“操作”利用其对将事件绑定到 ViewModel 方法的支持来实现这一点(尽管有些人可能认为它有点 hacky)。
您需要处理的TextBox事件是SelectionChanged
. 在 ViewModel 中创建一个合适的方法来处理这个事件:
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
}
}
Action
然后,在 XAML 中,通过 Stylet标记将事件挂钩到此方法:
xmlns:s="https://github.com/canton7/Stylet"
...
<TextBox SelectionChanged="{s:Action OnTextSelectionChanged}" />