2017-01-04 3 views
0

Я в основном веб-разработчик, и я супер новичок в WPF. Я пытаюсь работать над одним из проектов WPF. Я с трудом проверяю модель с использованием аннотаций данных и MVVM. Может кто-нибудь, пожалуйста, укажите мне, что я могу делать неправильно. Позвольте мне начать с моей модели:WPF Data Annotation Validation issue

public class FamilyMember : PropertyChangedNotification 
    { 
     public long FamilyMemberId { get; set; } 
     [Required(ErrorMessage = "First Name is required")] 
     [RegularExpression(@"^[a-zA-Z''-'\s]{2,40}$")] 
     public string FirstName { get; set; } 

     [Required(ErrorMessage = "Last Name is required")] 
     public string LastName { get; set; } 
    } 

Вот мой класс PropertyChangedNotification

public abstract class PropertyChangedNotification : INotifyPropertyChanged, IDataErrorInfo 
    { 
     #region Fields 

     private readonly Dictionary<string, object> _values = new Dictionary<string, object>(); 

     #endregion 

     #region Protected 

     /// <summary> 
     /// Sets the value of a property. 
     /// </summary> 
     /// <typeparam name="T">The type of the property value.</typeparam> 
     /// <param name="propertySelector">Expression tree contains the property definition.</param> 
     /// <param name="value">The property value.</param> 
     protected void SetValue<T>(Expression<Func<T>> propertySelector, T value) 
     { 
      string propertyName = GetPropertyName(propertySelector); 

      SetValue<T>(propertyName, value); 
     } 

     /// <summary> 
     /// Sets the value of a property. 
     /// </summary> 
     /// <typeparam name="T">The type of the property value.</typeparam> 
     /// <param name="propertyName">The name of the property.</param> 
     /// <param name="value">The property value.</param> 
     protected void SetValue<T>(string propertyName, T value) 
     { 
      if (string.IsNullOrEmpty(propertyName)) 
      { 
       throw new ArgumentException("Invalid property name", propertyName); 
      } 

      _values[propertyName] = value; 
      NotifyPropertyChanged(propertyName); 
     } 

     /// <summary> 
     /// Gets the value of a property. 
     /// </summary> 
     /// <typeparam name="T">The type of the property value.</typeparam> 
     /// <param name="propertySelector">Expression tree contains the property definition.</param> 
     /// <returns>The value of the property or default value if not exist.</returns> 
     protected T GetValue<T>(Expression<Func<T>> propertySelector) 
     { 
      string propertyName = GetPropertyName(propertySelector); 

      return GetValue<T>(propertyName); 
     } 

     /// <summary> 
     /// Gets the value of a property. 
     /// </summary> 
     /// <typeparam name="T">The type of the property value.</typeparam> 
     /// <param name="propertyName">The name of the property.</param> 
     /// <returns>The value of the property or default value if not exist.</returns> 
     protected T GetValue<T>(string propertyName) 
     { 
      if (string.IsNullOrEmpty(propertyName)) 
      { 
       throw new ArgumentException("Invalid property name", propertyName); 
      } 

      object value; 
      if (!_values.TryGetValue(propertyName, out value)) 
      { 
       value = default(T); 
       _values.Add(propertyName, value); 
      } 

      return (T)value; 
     } 

     /// <summary> 
     /// Validates current instance properties using Data Annotations. 
     /// </summary> 
     /// <param name="propertyName">This instance property to validate.</param> 
     /// <returns>Relevant error string on validation failure or <see cref="System.String.Empty"/> on validation success.</returns> 
     protected virtual string OnValidate(string propertyName) 
     { 
      if (string.IsNullOrEmpty(propertyName)) 
      { 
       throw new ArgumentException("Invalid property name", propertyName); 
      } 

      string error = string.Empty; 
      var value = GetValue(propertyName); 
      var results = new List<System.ComponentModel.DataAnnotations.ValidationResult>(1); 
      var result = Validator.TryValidateProperty(
       value, 
       new ValidationContext(this, null, null) 
       { 
        MemberName = propertyName 
       }, 
       results); 

      if (!result) 
      { 
       var validationResult = results.First(); 
       error = validationResult.ErrorMessage; 
      } 

      return error; 
     } 

     #endregion 

     #region Change Notification 

     /// <summary> 
     /// Raised when a property on this object has a new value. 
     /// </summary> 
     public event PropertyChangedEventHandler PropertyChanged; 

     /// <summary> 
     /// Raises this object's PropertyChanged event. 
     /// </summary> 
     /// <param name="propertyName">The property that has a new value.</param> 
     protected void NotifyPropertyChanged(string propertyName) 
     { 
      this.VerifyPropertyName(propertyName); 

      PropertyChangedEventHandler handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       var e = new PropertyChangedEventArgs(propertyName); 
       handler(this, e); 
      } 
     } 

     protected void NotifyPropertyChanged<T>(Expression<Func<T>> propertySelector) 
     { 
      var propertyChanged = PropertyChanged; 
      if (propertyChanged != null) 
      { 
       string propertyName = GetPropertyName(propertySelector); 
       propertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 

     #endregion // INotifyPropertyChanged Members 

     #region Data Validation 

     string IDataErrorInfo.Error 
     { 
      get 
      { 
       throw new NotSupportedException("IDataErrorInfo.Error is not supported, use IDataErrorInfo.this[propertyName] instead."); 
      } 
     } 

     string IDataErrorInfo.this[string propertyName] 
     { 
      get 
      { 
       return OnValidate(propertyName); 
      } 
     } 

     #endregion 

     #region Privates 

     private string GetPropertyName(LambdaExpression expression) 
     { 
      var memberExpression = expression.Body as MemberExpression; 
      if (memberExpression == null) 
      { 
       throw new InvalidOperationException(); 
      } 

      return memberExpression.Member.Name; 
     } 

     private object GetValue(string propertyName) 
     { 
      object value; 
      if (!_values.TryGetValue(propertyName, out value)) 
      { 
       var propertyDescriptor = TypeDescriptor.GetProperties(GetType()).Find(propertyName, false); 
       if (propertyDescriptor == null) 
       { 
        throw new ArgumentException("Invalid property name", propertyName); 
       } 

       value = propertyDescriptor.GetValue(this); 
       _values.Add(propertyName, value); 
      } 
      var propertyDescriptor1 = TypeDescriptor.GetProperties(GetType()).Find(propertyName, false); 
      if (propertyDescriptor1 == null) 
      { 
       throw new ArgumentException("Invalid property name", propertyName); 
      } 

      value = propertyDescriptor1.GetValue(this); 
      return value; 
     } 

     #endregion 

     #region Debugging 

     /// <summary> 
     /// Warns the developer if this object does not have 
     /// a public property with the specified name. This 
     /// method does not exist in a Release build. 
     /// </summary> 
     [Conditional("DEBUG")] 
     [DebuggerStepThrough] 
     public void VerifyPropertyName(string propertyName) 
     { 
      // Verify that the property name matches a real, 
      // public, instance property on this object. 
      if (TypeDescriptor.GetProperties(this)[propertyName] == null) 
      { 
       string msg = "Invalid property name: " + propertyName; 

       if (this.ThrowOnInvalidPropertyName) 
        throw new Exception(msg); 
       else 
        Debug.Fail(msg); 
      } 
     } 

     /// <summary> 
     /// Returns whether an exception is thrown, or if a Debug.Fail() is used 
     /// when an invalid property name is passed to the VerifyPropertyName method. 
     /// The default value is false, but subclasses used by unit tests might 
     /// override this property's getter to return true. 
     /// </summary> 
     protected virtual bool ThrowOnInvalidPropertyName { get; private set; } 

     #endregion // Debugging Aides 
    } 

Вот мой ViewModel класс

class FamilyMemberViewModel : PropertyChangedNotification 
    { 
     private static FamilyMemberViewModel familyMemberViewModel; 
     public RelayCommand SaveCommand { get; set; } 
     public RelayCommand ClearCommand { get; set; } 
     public RelayCommand SaveDataCommand { get; set; } 
     public static int Errors { get; set; } 
     private FamilyMemberViewModel() 
     { 
      FamilyMembers = new ObservableCollection<FamilyMember>(); 
      //TODO: Get all the family members and add it to the collection 

      NewFamilyMember = new FamilyMember(); 
      SaveCommand = new RelayCommand(Save, CanSave); 
      ClearCommand = new RelayCommand(Clear); 
      SaveDataCommand = new RelayCommand(SaveData); 
     } 


     public ObservableCollection<FamilyMember> FamilyMembers 
     { 
      get { return GetValue(() => FamilyMembers); } 
      set { SetValue(() => FamilyMembers, value); } 
     } 

     public FamilyMember NewFamilyMember 
     { 
      get { return GetValue(() => NewFamilyMember); } 
      set { SetValue(() => NewFamilyMember, value); } 
     } 


     public static FamilyMemberViewModel SharedViewModel() 
     { 
      return familyMemberViewModel ?? (familyMemberViewModel = new FamilyMemberViewModel()); 
     } 




     public void Save(object parameter) 
     { 
      FamilyMembers.Add(NewFamilyMember); 
      Clear(this); 
     } 

     public bool CanSave(object parameter) 
     { 
      if (Errors == 0) 
       return true; 
      else 
       return false; 
     } 

     public void Clear(object parameter) 
     { 
      NewFamilyMember = new FamilyMember(); 
     } 

     public void SaveData(object parameter) 
     { 


     } 
    } 

Вот мой контроль Пользователь:

<UserControl x:Class="MyApp.Resources.AddMember" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <UserControl.Resources> 
     <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}"> 
      <Setter Property="FontSize" Value="16"/> 
      <Setter Property="Margin" Value="0,5,0,0"/> 
      <Setter Property="VerticalAlignment" Value="Center"/> 
      <Setter Property="Foreground" Value="Red"></Setter> 
     </Style> 
     <Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}"> 
      <Setter Property="FontSize" Value="16"/> 
      <Setter Property="Margin" Value="0,5,0,0"/> 
     </Style> 
     <Style TargetType="{x:Type ComboBox}"> 
      <Setter Property="FontSize" Value="16"/> 
      <Setter Property="Margin" Value="0,5,0,0"/> 
     </Style> 
    </UserControl.Resources> 

    <Grid Margin="10"> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="Auto" /> 
      <ColumnDefinition Width="*" /> 
     </Grid.ColumnDefinitions> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto" /> 
      <RowDefinition Height="Auto" /> 
      <RowDefinition Height="*" /> 
     </Grid.RowDefinitions> 

     <Label Target="{Binding ElementName=FirstName}" Grid.Row="0" Grid.Column="0">First Name:</Label> 
     <StackPanel Grid.Column="1" Grid.Row="0" Orientation="Horizontal"> 
      <TextBox Margin="10,0,0,10" x:Name="FirstName" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" Validation.Error="Validation_Error" Width="100"/> 
      <TextBlock Style="{StaticResource TextBlockStyle}" Text="{Binding (Validation.Errors)[0].ErrorContent, ElementName=FirstName}" Margin="10,0,0,10"/> 
     </StackPanel> 

     <Label Target="{Binding ElementName=LastName}" Grid.Row="1" Grid.Column="0">Last Name:</Label> 
     <TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" x:Name="LastName" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" Validation.Error="Validation_Error"/> 
     <Button Content="Save" Grid.Column="1" HorizontalAlignment="Center" Grid.Row="2" VerticalAlignment="Top" Width="75" Margin="0,20,0,0" Command="{Binding SaveCommand}"/> 
    </Grid> 
</UserControl> 

Вот код Сзади для пользовательского элемента управления:

public partial class AddMember : UserControl 
    { 
     private int _noOfErrorsOnScreen = 0; 
     private FamilyMember _member = new FamilyMember(); 
     public AddMember() 
     { 
      InitializeComponent(); 
      //this.DataContext = _member; 
      this.DataContext = FamilyMemberViewModel.SharedViewModel(); 

     } 
     private void Validation_Error(object sender, ValidationErrorEventArgs e) 
     { 
      if (e.Action == ValidationErrorEventAction.Added) FamilyMemberViewModel.Errors += 1; 
      if (e.Action == ValidationErrorEventAction.Removed) FamilyMemberViewModel.Errors -= 1; 
     }  

    } 

Теперь проблема: если я установить DataContext для пользовательского элемента управления, чтобы быть FamilyMemberViewModel.SharedViewModel(), проверка не показывая .. As вскоре, когда я установил DataContext как экземпляр FamilyMember, он появляется.

Поскольку это шаблон MVVM, я не хочу напрямую ссылаться на мою модель в представлении плюс, у меня есть RelayCommand, который включает и отключает кнопку «Сохранить», если есть какая-либо ошибка проверки.

Вот скриншот, когда я ссылаться на новый экземпляр моей модели непосредственно в пользовательский элемент управления (простите мой уродливый глядя вид) enter image description here

Вот скриншот, когда я ссылаться на мой ViewModel, как DataContext: enter image description here

Я очень близко отношусь к правильной проверке модели (по крайней мере, я думаю, что с тех пор, как я пришел из среды веб-разработки и использовал проверку аннотации данных в прошлом), но не могу понять, что с ней не так. я оценю немного больше объяснений, так как я новичок в WPF

Благодаря

ответ

0

Теперь проблема: если я установить DataContext для пользовательского элемента управления, чтобы быть FamilyMemberViewModel.SharedViewModel(), валидация не показывая. Как только я установил DataContext как экземпляр FamilyMember, он появляется.

Это потому, что класс FamilyMemberViewModel не имеет свойств с именем «FirstName» и «LastName», с которыми вы связываетесь в представлении, чтобы привязки не удались.

Когда вы настраиваете привязку в XAML, вы привязываетесь к свойству DataContext элемента.

Если связать со свойствами FirstName и LastName собственности NewFamilyMember в DataContext, то есть объект FamilyMemberViewModel возвращаемой методом FamilyMemberViewModel.SharedViewModel(), он должен работать:

<StackPanel Grid.Column="1" Grid.Row="0" Orientation="Horizontal"> 
    <TextBox Margin="10,0,0,10" x:Name="FirstName" Text="{Binding NewFamilyMember.FirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" Validation.Error="Validation_Error" Width="100"/> 
    <TextBlock Style="{StaticResource TextBlockStyle}" Text="{Binding (Validation.Errors)[0].ErrorContent, ElementName=FirstName}" Margin="10,0,0,10"/> 
</StackPanel> 

<Label Target="{Binding ElementName=LastName}" Grid.Row="1" Grid.Column="0">Last Name:</Label> 
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" x:Name="LastName" Text="{Binding NewFamilyMember.LastName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" Validation.Error="Validation_Error"/> 
<Button Content="Save" Grid.Column="1" HorizontalAlignment="Center" Grid.Row="2" VerticalAlignment="Top" Width="75" Margin="0,20,0,0" Command="{Binding SaveCommand}"/> 
+0

Спасибо большое. Этого я точно не хватало. –

+0

Добро пожаловать. Пожалуйста, не забудьте проголосовать за ответ, если он вам поможет. – mm8