正在多级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声明资源,我也不能很容易地找到那个方法。

正在多级wpf视图中验证子记录

验证部分可以从这里开始: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 ....
....
}