正在多级wpf视图中验证子记录
本文关键字:验证 记录 视图 多级 wpf | 更新日期: 2023-09-27 17:49:23
假设我有一组模型类,如下所示:
public class Person
{
public string Name { get; set; }
public ObservableCollection<Job> Jobs { get; private set; }
}
public class Job
{
public string Title { get; set; }
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
}
我这样设置我的xaml,以便在单个视图中显示人员列表和所有详细信息:
<StackPanel Orientation="Horizontal">
<ListView ItemsSource="{Binding}" Width="200" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" />
<DockPanel Width="200" Margin="10,0">
<TextBox Text="{Binding Name}" DockPanel.Dock="Top" Margin="0,0,0,10"/>
<ListView ItemsSource="{Binding Jobs}" Name="_jobList" DisplayMemberPath="Title" IsSynchronizedWithCurrentItem="True"/>
</DockPanel>
<StackPanel Width="200" DataContext="{Binding ElementName=_jobList, Path=SelectedItem}">
<TextBox Text="{Binding Title}"/>
<DatePicker SelectedDate="{Binding Start}"/>
<DatePicker SelectedDate="{Binding End}"/>
</StackPanel>
</StackPanel>
整个窗口的数据上下文是一个ObservbleCollection of People。这似乎工作得相当好。我现在想添加一些验证,我不知道从哪里开始。
我想验证Job字段,以便用户不能选择另一个Job(或人员),如果标题是空的或同一个人中的重复,或者如果日期是无序的。此外,如果Person的名称为空,则不能更改。
我读了一些关于WPF验证的内容,但是还没有找到任何明确的解决方案。做这类事情的最佳实践是什么?
还有,我在最后一个面板上设置DataContext的方式是正确的吗?它可以工作,但感觉有点粗糙。另一种选择是为collectiondatasource声明资源,我也不能很容易地找到那个方法。
验证部分可以从这里开始:http://codeblitz.wordpress.com/2009/05/08/wpf-validation-made-easy-with-idataerrorinfo/
在你理解了这篇文章之后,你可以像这样修改你的类:例如,让我们为职位名称和工作开始日期添加一个非空的标题验证:
工作类:
public class Job : IDataErrorInfo
{
public string Title { get; set; }
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Title")
{
if (string.IsNullOrEmpty(Title))
result = "Please enter a Title ";
}
if (columnName == "Start")
{
if (Start == null)
result = "Please enter a Start Date";
else if (Start > End)
{
result = "Start Date must be less than end date";
}
}
return result;
}
}
}
//////////假设我们为你的xaml代码使用了一个名为MainWindow的窗口,它看起来是这样的MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
x:Name="mainWindow">
<Window.Resources>
<ControlTemplate x:Key="errorTemplate">
<DockPanel LastChildFill="true">
<Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
<TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
</TextBlock>
</Border>
<AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
<Border BorderBrush="red" BorderThickness="1" />
</AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListView ItemsSource="{Binding}" Width="200" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" IsEnabled="{Binding ElementName=mainWindow, Path=FormHasNoNoErrors}"/>
<DockPanel Width="200" Margin="10,0">
<TextBox Text="{Binding Name}" DockPanel.Dock="Top" Margin="0,0,0,10"/>
<ListView ItemsSource="{Binding Jobs}" Name="_jobList" DisplayMemberPath="Title" IsSynchronizedWithCurrentItem="True" IsEnabled="{Binding ElementName=mainWindow, Path=FormHasNoNoErrors}" />
</DockPanel>
<StackPanel Width="200" DataContext="{Binding ElementName=_jobList, Path=SelectedItem}">
<TextBox Text="{Binding Title, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" Validation.Error="Validation_OnError" Validation.ErrorTemplate="{StaticResource errorTemplate}"/>
<DatePicker SelectedDate="{Binding Start, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" Validation.Error="Validation_OnError" Validation.ErrorTemplate="{StaticResource errorTemplate}"/>
<DatePicker SelectedDate="{Binding End}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
////////MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
var jobs1 = new ObservableCollection<Job>()
{
new Job() {Start = DateTime.Now, End = DateTime.Now.AddDays(1), Title = "Physical Enginer"},
new Job() {Start = DateTime.Now, End = DateTime.Now.AddDays(1), Title = "Mechanic"}
};
var jobs2 = new ObservableCollection<Job>()
{
new Job() {Start = DateTime.Now, End = DateTime.Now.AddDays(1), Title = "Doctor"},
new Job() {Start = DateTime.Now, End = DateTime.Now.AddDays(1), Title = "Programmer"}
};
var personList = new ObservableCollection<Person>()
{
new Person() {Name = "john", Jobs = jobs1},
new Person() {Name="alan", Jobs=jobs2}
};
this.DataContext = personList;
InitializeComponent();
}
private void Validation_OnError(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
NoOfErrorsOnScreen++;
else
NoOfErrorsOnScreen--;
}
public bool FormHasNoNoErrors
{
get { return _formHasNoErrors; }
set
{
if (_formHasNoErrors != value)
{
_formHasNoErrors = value;
PropertyChanged(this, new PropertyChangedEventArgs("FormHasNoErrors"));
}
}
}
public int NoOfErrorsOnScreen
{
get { return _noOfErrorsOnScreen; }
set
{
_noOfErrorsOnScreen = value;
FormHasNoNoErrors = _noOfErrorsOnScreen == 0 ? true : false;
}
}
private int _noOfErrorsOnScreen = 0;
private bool _formHasNoErrors = true;
public event PropertyChangedEventHandler PropertyChanged = delegate {};
}
//////////////////////
我想验证这个Job字段,以便用户无法选择另一个工作(或人),如果标题是空的还是重复的是同一个人,还是日期不清楚的秩序。此外,人不能更改,如果名称是空的。
一个简单的解决方案是在表单有错误时禁用列表框(这就是我在上面的代码中所做的),并在没有错误时启用它们。此外,你可能应该在它们上面加上一个漂亮的红色边框和工具提示,向用户解释为什么他不能再选择了。
为了验证同一个人的头衔是否重复,您可以扩展上面的验证机制,使其具有一个ValidationService类,该类接收一个person,并查看拥有该职位的人是否也有另一个同名的人。
公共类Person: IDataErrorInfo{
public string this[string columnName]
{
get
{
//look inside person to see if it has two jobs with the same title
string result = ValidationService.GetPersonErrors(this);
return result;
}
...
}
还有,我的绑定是否像我设置的那样最后一个面板上的DataContext了吗?管用,但感觉有点粗略的。另一种选择是声明资源collectiondatasource,我不能真的很清楚这个方法很容易。
这不符合MVVM的精神,如果你要做的不仅仅是一个小的测试项目,你应该尝试学习MVVM(你可以从这里开始:http://fernandomachadopirizen.wordpress.com/2010/06/10/a-simple-introduction-to-the-model-view-viewmodel-pattern-for-building-silverlight-and-windows-presentation-foundation-applications/)
然后你不会直接绑定到一个人员列表,而是你会有一些MainWindowViewModel类来绑定。这个MainWindowViewModel类会包含人员列表,你会绑定到那个。对于选择你会有一个CurrentJob属性在MainWindowViewModel中,那是当前选择的作业,你会绑定到它:
基本上是这样的:
<StackPanel Orientation="Horizontal">
<ListView ItemsSource="{Binding PersonList}" SelectedItem="{Binding CurrentPerson, Mode=TwoWay}" Width="200" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" IsEnabled="{Binding ElementName=mainWindow, Path=FormHasNoNoErrors}"/>
<DockPanel Width="200" Margin="10,0">
<TextBox Text="{Binding Name}" DockPanel.Dock="Top" Margin="0,0,0,10"/>
<ListView ItemsSource="{Binding Jobs}" SelectedItem="{Binding CurrentJob, Mode=TwoWay}", DisplayMemberPath="Title" IsSynchronizedWithCurrentItem="True" />
</DockPanel>
<StackPanel Width="200">
<TextBox Text="{Binding CurrentJob.Title}"/>
.....
</StackPanel>
</StackPanel>
和MainWindowViewModel:
public class MainWindowViewModel :
{
...
//Public Properties
public ObservableCollection<Person> PersonList ....
public Job CurrentJob ....
....
}