使用 ValueConverter 的问题在于,您依赖表示层来处理看起来像域逻辑的东西。你说你有某种状态模式类,它确实听起来像模型的一部分(MVVM 中的第一个“M”)。
如果您将绑定设置为具有 {Binding ....UpdateSourceTrigger=PropertyChanged},您将在每次用户输入单个字符时获得发送给视图模型的值。然后,您需要验证对 setter 的每个调用。
接下来,TextBox 控件中有一个功能/错误。如果 TextBox 是更改的源,则 TextBox 不会侦听绑定的 PropertyChanged 事件。这意味着如果您输入“y”并且您的 setter 实际上将属性设置为“”,然后引发 PropertyChanged 事件,您仍然会看到“y”:(
有一篇文章对此进行了说明 (http://stackoverflow.com/questions/3905227/coerce-a-wpf-textbox-not-working-anymore-in-net-4-0) 但他们使用事件,他们没有做 MVVM。
刚刚为 WPF 项目完成此操作后,我最终获得了一个附加属性。所有的逻辑都在我的 ViewModel 包装的模型中。我能够对我的逻辑进行单元测试,并将附加属性添加到样式中,以便我可以多次重复使用。
我写的代码是这样的。
public sealed class TextBoxBehaviour : DependencyObject
{
#region CoerceValue Attached property
public static bool GetCoerceValue(DependencyObject obj)
{
return (bool)obj.GetValue(CoerceValueProperty);
}
public static void SetCoerceValue(DependencyObject obj, bool value)
{
obj.SetValue(CoerceValueProperty, value);
}
/// <summary>
/// Gets or Sets whether the TextBox should reevaluate the binding after it pushes a change (either on LostFocus or PropertyChanged depending on the binding).
/// </summary>
public static readonly DependencyProperty CoerceValueProperty =
DependencyProperty.RegisterAttached("CoerceValue", typeof(bool), typeof(TextBoxBehaviour), new UIPropertyMetadata(false, CoerceValuePropertyChanged));
static void CoerceValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textbox = d as TextBox;
if (textbox == null)
return;
if ((bool)e.NewValue)
{
if (textbox.IsLoaded)
{
PrepareTextBox(textbox);
}
else
{
textbox.Loaded += OnTextBoxLoaded;
}
}
else
{
textbox.TextChanged -= OnCoerceText;
textbox.LostFocus-= OnCoerceText;
textbox.Loaded -= OnTextBoxLoaded;
}
}
static void OnTextBoxLoaded(object sender, RoutedEventArgs e)
{
var textbox = (TextBox)sender;
PrepareTextBox(textbox);
textbox.Loaded -= OnTextBoxLoaded;
}
static void OnCoerceText(object sender, RoutedEventArgs e)
{
var textBox = (TextBox)sender;
var selectionStart = textBox.SelectionStart;
var selectionLength = textBox.SelectionLength;
textBox.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
if (selectionStart < textBox.Text.Length) textBox.SelectionStart = selectionStart;
if (selectionStart + selectionLength < textBox.Text.Length) textBox.SelectionLength = selectionLength;
}
private static void PrepareTextBox(TextBox textbox)
{
var binding = textbox.GetBindingExpression(TextBox.TextProperty).ParentBinding;
var newBinding = binding.Clone();
newBinding.ValidatesOnDataErrors = true;
textbox.SetBinding(TextBox.TextProperty, newBinding);
if (newBinding.UpdateSourceTrigger == UpdateSourceTrigger.PropertyChanged)
{
textbox.TextChanged += OnCoerceText;
}
else if (newBinding.UpdateSourceTrigger == UpdateSourceTrigger.LostFocus || newBinding.UpdateSourceTrigger == UpdateSourceTrigger.Default)
{
textbox.LostFocus += OnCoerceText;
}
}
#endregion
}
然后您只需要实现 setter(看起来您已经是这样了)并将附加的属性添加到绑定到您的 ViewModel 的文本框中。
<TextBox Text="{Binding Number, UpdateSourceTrigger=PropertyChanged}"
myNamespace:TextBoxBehaviour.CoerceValue="True"/>