2011-04-08 116 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