我需要创建一个验证节点,如果输入的值已经存在,该节点将返回错误。我有一个GUI,其中包含可以设置名称的项目。我想强制要求这些名称是唯一的。
因此,对于每次验证,我需要以下两个参数:
- 所有项的所有名称的列表,或者会告诉我名称存在的某个谓词
- 当前项目名称,将其从上述验证中排除(将名称更改为相同值不应是错误(
数据上下文如下(只是用于说明的界面(:
class AppMainContext
{
public IEnumerable<string> ItemNames {get;}
public Item SelectedItem {get;}
}
class Item
{
public string Name {get;}
}
WPF中的字段如下所示,其父字段绑定到`{SelectedItem}:
<DockPanel DockPanel.Dock="Top">
<Label Content="Name: "/>
<TextBox DockPanel.Dock="Top">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<vmvalidation:UniqueNameRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DockPanel>
验证器看起来像这样:
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Controls;
namespace MyApp.Validation
{
public class UniqueNameRule : ValidationRule
{
public IEnumerable<string> ExistingNames { get; set; }
public string MyName { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if(value is string newValue)
{
// name changed
if(!value.Equals(MyName))
{
if(ExistingNames.Contains(newValue))
{
return new ValidationResult(false, "Name already exists!");
}
}
return new ValidationResult(true, null);
}
else
{
return new ValidationResult(false, "Invalid value type. Is this validator valid for the given field?");
}
}
}
}
我尝试至少将当前名称绑定到验证器。文本框已存在于当前项目数据上下文中,因此正确的绑定为:
<Binding.ValidationRules>
<vmvalidation:UniqueNameRule MyName="{Binding Name}" />
</Binding.ValidationRules>
除了这会给出一个错误:
成员
MyName
无法识别或访问。
所有项目的列表都在windows数据上下文中,可通过ItemNames
访问。我想它可以这样访问:
{Binding Path=DataContext.ItemNames, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}
我试着用下面的答案纠正绑定,但后来我得到了一个错误:
不能在类型为
MyProject_Validation_UniqueNameRule_9_468654
的"MyName"属性上设置"Binding"。只能在DependencyObject的DependencyProperty上设置"Binding"。
看起来根本不支持绑定。
那么,我如何将这些放在一起,以便验证规则可以访问这两个变量呢?
由于验证规则如何落在可视化树上的性质,绑定失败,这可能是您所怀疑的。
RelativeSource还有其他风格的绑定(请参阅该文档中的属性部分(。
最终需要父节点,这里有一个用于可能相关的样式:
<vmvalidation:UniqueNameRule
MyName="{Binding Name, RelativeSource={RelativeSource TemplatedParent}}"/>
或者在链的上游工作,而不是x:Type Window
如何更有可能绑定到父级,如x:Type TextBox
:
<vmvalidation:UniqueNameRule
MyName="{Binding Name, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type TextBox}}"/>