我正在尝试使用WPF创建一个应用程序,希望使用MVVM模型进行完整构建。 然而,我困惑于如何正确显示错误消息。 我认为这是微不足道的一步,但似乎是最复杂的。
我使用xaml创建了以下视图。
这是我的
我的目标是在错误的字段周围显示红色边框,然后在其下方显示错误消息,告诉用户出了什么问题。
如何正确显示错误?而且,当视图首次加载时,如何不显示任何错误?
根据这个博客,我需要编辑
但是它并没有显示错误消息,而且当视图首次加载时我会得到一个错误。最后,即使表单变为有效,操作按钮仍然保持禁用状态。
更新后,将
我使用xaml创建了以下视图。
<StackPanel Style="{StaticResource Col}">
<DockPanel>
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" ></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Style="{StaticResource Col}">
<Label Content="Name" Style="{StaticResource FormLabel}" />
<Border Style="{StaticResource FormInputBorder}">
<TextBox x:Name="Name" Style="{StaticResource FormControl}" Text="{Binding Name, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}" />
</Border>
</StackPanel>
<StackPanel Grid.Column="1" Style="{StaticResource Col}">
<Label Content="Phone Number" Style="{StaticResource FormLabel}" />
<Border Style="{StaticResource FormInputBorder}">
<TextBox x:Name="Phone" Style="{StaticResource FormControl}" Text="{Binding Phone, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}" />
</Border>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Style="{StaticResource PrimaryButton}" Command="{Binding Create}">Create</Button>
<Button>Reset</Button>
</StackPanel>
</DockPanel>
</StackPanel>
然后我创建了以下ViewModel
public class VendorViewModel : ViewModel
{
protected readonly IUnitOfWork UnitOfWork;
private string _Name { get; set; }
private string _Phone { get; set; }
public VendorViewModel()
: this(new UnitOfWork())
{
}
public VendorViewModel(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
[Required(ErrorMessage = "The name is required")]
[MinLength(5, ErrorMessage = "Name must be more than or equal to 5 letters")]
[MaxLength(50, ErrorMessage = "Name must be less than or equal to 50 letters")]
public string Name
{
get { return _Name; }
set
{
_Name = value;
NotifyPropertyChanged();
}
}
public string Phone
{
get { return _Phone; }
set
{
_Phone = value;
NotifyPropertyChanged();
}
}
/// <summary>
/// Gets the collection of customer loaded from the data store.
/// </summary>
public ICollection<Vendor> Vendors { get; private set; }
protected void AddVendor()
{
var vendor = new Vendor(Name, Phone);
UnitOfWork.Vendors.Add(vendor);
}
public ICommand Create
{
get
{
return new ActionCommand(p => AddVendor(),
p => IsValidRequest());
}
}
public bool IsValidRequest()
{
// There got to be a better way to check if everything passed or now...
return IsValid("Name") && IsValid("Phone");
}
}
这是我的ViewModel
基类的样子
public abstract class ViewModel : ObservableObject, IDataErrorInfo
{
/// <summary>
/// Gets the validation error for a property whose name matches the specified <see cref="columnName"/>.
/// </summary>
/// <param name="columnName">The name of the property to validate.</param>
/// <returns>Returns a validation error if there is one, otherwise returns null.</returns>
public string this[string columnName]
{
get { return OnValidate(columnName); }
}
/// <summary>
/// Validates a property whose name matches the specified <see cref="propertyName"/>.
/// </summary>
/// <param name="propertyName">The name of the property to validate.</param>
/// <returns>Returns a validation error, if any, otherwise returns null.</returns>
protected virtual string OnValidate(string propertyName)
{
var context = new ValidationContext(this)
{
MemberName = propertyName
};
var results = new Collection<ValidationResult>();
bool isValid = Validator.TryValidateObject(this, context, results, true);
if (!isValid)
{
ValidationResult result = results.SingleOrDefault(p => p.MemberNames.Any(memberName => memberName == propertyName));
if (result != null)
return result.ErrorMessage;
}
return null;
}
protected virtual bool IsValid(string propertyName)
{
return OnValidate(propertyName) == null;
}
/// <summary>
/// Not supported.
/// </summary>
[Obsolete]
public string Error
{
get
{
throw new NotSupportedException();
}
}
}
这是我的
ObservableObject
类。public class ObservableObject : INotifyPropertyChanged
{
/// <summary>
/// Raised when the value of a property has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises <see cref="PropertyChanged"/> for the property whose name matches <see cref="propertyName"/>.
/// </summary>
/// <param name="propertyName">Optional. The name of the property whose value has changed.</param>
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
我的目标是在错误的字段周围显示红色边框,然后在其下方显示错误消息,告诉用户出了什么问题。
如何正确显示错误?而且,当视图首次加载时,如何不显示任何错误?
根据这个博客,我需要编辑
Validation.ErrorTemplate
所以我尝试在App.xaml文件中添加以下代码。 <!-- Style the error validation by showing the text message under the field -->
<Style TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Border BorderThickness="1" BorderBrush="DarkRed">
<StackPanel>
<AdornedElementPlaceholder x:Name="errorControl" />
</StackPanel>
</Border>
<TextBlock Text="{Binding AdornedElement.ToolTip, ElementName=errorControl}" Foreground="Red" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
但是它并没有显示错误消息,而且当视图首次加载时我会得到一个错误。最后,即使表单变为有效,操作按钮仍然保持禁用状态。
更新后,将
Property="Validation.ErrorTemplate"
移入FormControl
组中后,它就能正常工作了。然而,错误消息似乎在覆盖按钮,而不是将按钮向下推。此外,文本似乎没有垂直换行,导致边框拉伸到其他控件上,如下图所示。
Validation.Errors
是从哪里来的?这是我需要手动创建和填充的东西吗? - JuniorXAML
代码是问题。 - JuniorFormControl
样式覆盖了来自App.xaml的通用TextBox
样式。你能否将Validation.ErrorTemplate
代码移动到你的FormControl
样式内部? - Bruno V