2017-08-07 110 views
0

我有一個具有主 - 細部視圖的項目,其中主部件由選擇對象的列表組成,列表部分顯示該對象的細節並允許對其進行編輯。主從細節驗證WPF

我的問題是,我不能讓2個對象具有相同的名稱,而這聽起來像一個簡單的任務,事實證明,我知道的驗證過程並沒有很好地發揮它。

這是一個簡短的例子。

XAML

<Window x:Class="WpfApplication1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:WpfApplication1="clr-namespace:WpfApplication1" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 
    <Grid> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="*"/> 
      <ColumnDefinition Width="2*"/> 
     </Grid.ColumnDefinitions> 
     <ListView Grid.Column="0" ItemsSource="{Binding Path=People, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" Name="masterList" 
       DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedPerson}" /> 

     <StackPanel Grid.Column="1" Margin="5" DataContext="{Binding Path=SelectedPerson}"> 
      <TextBlock>Name</TextBlock> 
      <TextBox> 
       <TextBox.Text> 
        <Binding Path="Name" Mode="TwoWay" ValidatesOnDataErrors="True" NotifyOnValidationError="True"> 
        </Binding> 
       </TextBox.Text> 
      </TextBox> 
      <TextBlock>Address</TextBlock> 
      <TextBox Text="{Binding Path=Address}" /> 
      <TextBlock>Phone</TextBlock> 
      <TextBox Text="{Binding Path=Phone}" /> 
     </StackPanel> 
    </Grid> 
</Window> 

Person類

using System; 
using System.ComponentModel; 
using System.Runtime.CompilerServices; 

public class Person { 
    public string Name { get; set; } 
    public string Address { get; set; } 
    public string Phone { get; set; } 
} 

RegisteredPeople類

public class RegisteredPeople { 
    public ObservableCollection<Person> People { get; set; } 
    public Person SelectedPerson { get; set; } 
    public RegisteredPeople() { 
     People = new ObservableCollection<Person>() { 
      new Person() {Name = "Ramzi", Address = "A1", Phone = "1"}, 
      new Person() {Name = "Frank", Address = "A2", Phone = "12"}, 
      new Person() {Name = "Ihab", Address = "A3", Phone = "123"} 
     }; 
    } 
} 

這確實沒有任何驗證,但它顯示了我想要的基本機制。


我試圖實現兩個班IDataErrorInfo,沒有任何成功:

其他實現Person類的

using System; 
using System.ComponentModel; 
using System.Runtime.CompilerServices; 

public class Person : System.ComponentModel.IDataErrorInfo, INotifyPropertyChanged { 
    private string m_name; 

    public string Name { 
     get { return m_name; } 
     set { 
      m_name = value; 
      OnPropertyChanged(); 
      OnPropertyChanged("People"); //Name of the list that the master list is bound to. 
     } 
    } 

    public string Address { get; set; } 
    public string Phone { get; set; } 

    public string this[string columnName] { 
     get { 
      switch (columnName) { 
       case "Name": 
        if (string.IsNullOrEmpty(Name)) { 
      /** This one works, but from here I cannot compare with names of other Person objects. **/ 
         return "The name cannot be empty."; 
        } 
        break; 
       default: 
        return string.Empty; 
      } 
      return String.Empty; 
     } 
    } 

    public string Error { get; } 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

其他實施RegisteredPeople類的

public class RegisteredPeople : System.ComponentModel.IDataErrorInfo { 
    public ObservableCollection<Person> People { get; set; } 
    public Person SelectedPerson { get; set; } 
    public RegisteredPeople() { 
     People = new ObservableCollection<Person>() { 
      new Person() {Name = "Ramzi", Address = "A1", Phone = "1"}, 
      new Person() {Name = "Frank", Address = "A2", Phone = "12"}, 
      new Person() {Name = "Ihab", Address = "A3", Phone = "123"} 
     }; 
    } 

    public string this[string columnName] { 
     get { 
      switch (columnName) { 
       case "People": 
        foreach (Person person1 in People) { 
         foreach (Person person2 in People) { 
          if (person1 == person2) { 
           continue; 
          } 
          if (person1.Name == person2.Name) { 
           return "Error, 2 people cannot have the same name."; 
          } 
         } 
        } 
        break; 
       default: 
        return string.Empty; 
      } 
      return string.Empty; 
     } 
    } 

    public string Error { get; } 
} 

我也嘗試玩ValidationRule接口沒有任何成功。

這裏是我的嘗試:

XAML

我換成文本框的名稱與:

 <TextBox> 
      <TextBox.Text> 
       <Binding Path="Name" Mode="TwoWay" ValidatesOnDataErrors="True" NotifyOnValidationError="True"> 
        <Binding.ValidationRules> 
         <WpfApplication1:EventNameValidationRule EventList="{Binding ElementName=masterList, Path=ItemsSource}" /> 
        </Binding.ValidationRules> 
       </Binding> 
      </TextBox.Text> 
     </TextBox> 

EventNameValidationRule

using System.Collections.ObjectModel; 
using System.Globalization; 
using System.Windows.Controls; 

class EventNameValidationRule : ValidationRule { 
    public ObservableCollection<Person> EventList { get; set; } 
    public override ValidationResult Validate(object value, CultureInfo cultureInfo) { 
     return new ValidationResult(false, "Duplicate names are not allowd."); 
    } 
} 

事情是這樣拋出一個異常,說要綁定到EventList是不行的。 (如果沒有這個名單我明明有什麼比較沒有起點。)


我的問題:我怎樣才能標示名稱爲無效,當有兩個peole叫同一個名字?

+0

也許你應該告訴我們你試圖與有效性規則創建的方法。使用ValidationRule似乎是我的選擇。您可以嘗試將您的列表綁定到ValidationRule(如何執行此操作:http://dedjo.blogspot.de/2007/05/fully-binded-validation-by-using.html),然後在Validate方法爲只需檢查您綁定到ValidationRule的列表,如果它包含具有給定名稱的多個項目。 –

+0

謝謝@Coconut博士看我的編輯。 –

回答

3

您需要在Person類中實現驗證邏輯,即實現IDataErrorInfo接口的實際DataContext

例如,你可以在你的RegisteredPeople類,您從Person類的索引器調用,.e.g:

public class Person : IDataErrorInfo, INotifyPropertyChanged 
{ 
    private readonly IValidator _validator; 
    public Person(IValidator validator) 
    { 
     _validator = validator; 
    } 

    private string m_name; 
    public string Name 
    { 
     get { return m_name; } 
     set 
     { 
      m_name = value; 
      OnPropertyChanged(); 
     } 
    } 

    public string Address { get; set; } 
    public string Phone { get; set; } 

    public string this[string columnName] 
    { 
     get 
     { 
      switch (columnName) 
      { 
       case "Name": 
        if (string.IsNullOrEmpty(Name)) 
        { 
         /** This one works, but from here I cannot compare with names of other Person objects. **/ 
         return "The name cannot be empty."; 
        } 
        else 
        { 
         return _validator.Validate(this); 
        } 
       default: 
        return string.Empty; 
      } 
     } 
    } 

    public string Error { get; } 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

public interface IValidator 
{ 
    string Validate(Person person); 
} 

public class RegisteredPeople : IValidator 
{ 
    public ObservableCollection<Person> People { get; set; } 
    public Person SelectedPerson { get; set; } 

    public string Validate(Person person) 
    { 
     if (person != null && People.Any(x => x != person && x.Name == person.Name)) 
      return "Same name!"; 

     return null; 
    } 

    public RegisteredPeople() 
    { 
     People = new ObservableCollection<Person>() { 
     new Person(this) {Name = "Ramzi", Address = "A1", Phone = "1"}, 
     new Person(this) {Name = "Frank", Address = "A2", Phone = "12"}, 
     new Person(this) {Name = "Ihab", Address = "A3", Phone = "123"} 
    }; 
    } 
}