我主要是网络开发人员,我是WPF的新手。我正在努力开发一个WPF项目。我很难使用Data Annotations和MVVM验证模型。有人可以指出我可能做错了什么。 让我从我的模型开始:
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(),则验证不会显示.. 一旦我将DataContext设置为FamilyMember的一个实例,它就会显示出来。
由于这是一个MVVM模式,我不想直接将我的模型引用到视图中,我有一个RelayCommand,可以在出现任何验证错误时启用和禁用Save按钮。
这是我在用户控件中直接引用我的模型的新实例时的屏幕截图(请原谅我丑陋的视图)
以下是我将ViewModel作为DataContext引用时的屏幕截图:
我非常接近验证模型的正确方法(至少我认为是因为我来自Web开发环境并且过去使用了数据注释验证)但是无法弄清楚出了什么问题它。 因为我是WPF的新手,所以我将不胜感激。
由于
答案 0 :(得分:0)
现在的问题是:如果我将用户控件的dataContext设置为FamilyMemberViewModel.SharedViewModel(),则验证不会显示..一旦我将DataContext设置为FamilyMember的一个实例,它就会显示出来
这是因为FamilyMemberViewModel类没有在视图中绑定的名为“FirstName”和“LastName”的属性,因此绑定将失败。
在XAML中设置绑定时,绑定到元素的DataContext的属性。
如果绑定到DataContext的NewFamilyMember属性的FirstName和LastName属性,即FamilyMemberViewModel.SharedViewModel()方法返回的FamilyMemberViewModel对象,它应该可以工作:
<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}"/>