2011-04-08 84 views
4

验证子记录可以说我有这样一组模型类:多层次WPF视图

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> 

整个窗口的DataContext是人的ObservbleCollection。这似乎工作得很好。我现在想添加一些验证,我不知道从哪里开始。

我想验证作业字段,以便用户不能选择另一个作业(或人)如果标题为空或在同一个人内重复,或者如果日期无序。此外,如果姓名为空,则不能更改该人员。

我已经读了一些关于WPF中的验证,但还没有找到任何明确的解决方案。做这样的事情的最佳做法是什么?

另外,是我的绑定确定的方式,我在最后一个面板上设置DataContext?它有效,但感觉有点粗略。另一种方法是为CollectionDataSources声明资源,但我也无法很容易地将该方法计算出来。

+0

我已经尝试在我的数据类中实现IDataErrorInfo,并将ValidatesOnDataErrors = True放入我的绑定中。这正确地突出显示错误的字段,但不会阻止导航。 – captncraig 2011-04-08 19:13:43

回答

2

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

//////////////////////

我想验证工作 场,使得用户不能选择 另一个工作(或个人)如果标题 为空或 同一个人中的重复,或者如果日期是出 Ø f命令。此外,如果姓名为 为空,则不能更改人员 。

一个简单的解决方案是,如果表单有错误(这是我在上面的代码中做的),禁用列表框,并在没有错误时启用它们。另外你也许应该在工具提示上放一个漂亮的红色边框,解释用户为什么不能再选择。

为验证标题是否在同一个人中重复,可以扩展上面的验证机制以使ValidationService类接收一个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像 的最后一个面板上?它的工作原理,但它略微感觉有点 。替代方案是为 CollectionDataSources声明 资源,并且我不能 实际上很容易将该方法排除在外 。

这不是在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类将包含人员列表,您将绑定到该列表。 而对于选择,你会在你的MainWindowViewModel一个CurrentJob特性,那就是当前选择的工作,你会绑定到:

基本上是这样的:

<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 .... 
.... 
} 
+0

我认为关键在于你在哪里指出我没有正确地做mvvm,并且在视图模型类中管理它都会更简单。我绝对觉得我正在努力拼搏。 – captncraig 2011-04-25 21:56:25