我正试图在MVVM的文本框上实现一些简单的验证
public string Property
{
get
{
if (App.PropertyStorageContainer != null)
{
return App.PropertyStorageContainer.Property;
}
else
{
return null;
}
}
set
{
App.PropertyStorageContainer.Property = value;
RaisePropertyChanged("Property");
}
}
然后在我的PropertyStorageContainer类中我有
private string _property;
public string Property
{
get
{
return App.PropertyStorageContainer.Property;
}
set
{
if(value meets some condition)
{
_property = value;
}
else
{
_property = someothervalue;
}
}
}
.
<TextBox Width="50" TextAlignment="Center" Text="{Binding Property, Mode=TwoWay, NotifyOnValidationError=True}" MaxLength="3"></TextBox>
这样做的目的是验证盒子里的内容。现在,如果我直接从代码中设置这个值,那么一切都像我期望的那样工作。它尝试设置值,然后调用RaiseProperyChanged,然后GET值(由于验证可能与最初输入的值不同)。检索到的最终值确实显示在视图上,因此我知道双向绑定正在工作。
我遇到的问题是,SET的输入来自绑定的XAML属性/直接来自用户。在这种情况下,调用SET方法,执行验证,但不执行GET。这将导致未验证的值保留在屏幕上的文本框中。
我的第一个问题是这是一个bug还是预期的行为?当输入直接来自用户时,我可以看到他们是如何通过删除最后一个GET来节省性能的,因为GET不应该有什么新的内容。但如果不是,那么也许我设置它的方式干扰了GET的调用。
第二个问题当然是关于如何解决这个问题的建议。我已经阅读了一些关于其他验证方法的建议,但是我的程序已经在PROD上运行,并且建议的大多数更改都涉及到我的大量返工,所以我希望找到一种方法,使它在属性为SET时调用GET。
我做了几个假设,因为我不确定我完全理解你的代码,但我认为你可以考虑可能实现自定义验证规则。首先,由于您的自定义ValidationRule将负责验证,因此您可以从模型类的属性定义中获取逻辑并"简化"poco:
class PropertyStorageContainer
{
public string Property { get; set; }
}
似乎您希望视图模型充当模型类的基本包装器。同样,我将根据您的场景描述假设这是有效的:
class PropertyStorageContainerViewModel : INotifyPropertyChanged
{
private PropertyStorageContainer model;
public PropertyStorageContainerViewModel(PropertyStorageContainer model)
{
this.model = model;
}
public string Property
{
get
{
if (model != null)
{
return model.Property;
}
else
{
return null;
}
}
set
{
if (model.Property != value)
{
model.Property = value;
RaisePropertyChanged("Property");
}
}
}
// INotifyPropertyChanged implementation...
}
现在创建一个扩展System.Windows.Controls.ValidationRule的新类,并覆盖抽象的Validate方法来实现验证逻辑。对于这个例子,我创建了一个规则,它只检查字符串是空还是空(假设这是一个无效的场景):
class IsNullOrEmptyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string s = (value ?? string.Empty).ToString();
if (string.IsNullOrEmpty(s))
{
// Invalid...
return new ValidationResult(false, "Please enter a value.");
}
else
{
// Valid...
return new ValidationResult(true, null);
}
}
}
现在对于XAML…下面是一个TextBox的例子,它将验证规则添加到它的绑定验证规则(可以是多个规则)。
<TextBox Name="textBox1" Width="50" FontSize="12"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Property" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:IsNullOrEmptyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
然后在某处定义以下资源(如上所述)(例如,windows . resources)。首先使用ControlTemplate来定义TextBox在无效状态下的外观:
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="15" Text="!!!" />
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
另外,您可以定义一个样式触发器来显示错误消息。这里我将它绑定到TextBox的ToolTip属性:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
你马上就要进入INPC地狱了。我去过那里,一点也不好玩。
这是一个很大的禁忌,特别是因为如果在这样的类上做了任何映射,这些getter和setter将在它们的WPF绑定上下文之外被调用,并且会丢失地狱break。
保持简单:直接绑定到App.PropertyStorageContainer.Property
对于第二种情况,可以:
- 使用数据验证
- 让属性不是通过绑定而是通过命令来设置,在命令中可以进行值交换。
帮自己一个忙,不要滥用属性get/set