c# - 如何在 WPF 应用程序中显示错误消息?

标签 c# wpf xaml mvvm

我正在尝试使用 WPF 创建一个应用程序。我正在尝试使用 MVVM 模型完全构建它。但是,我对如何正确显示错误消息感到困惑。我认为这将是微不足道的步骤,但似乎是最复杂的。

我使用 xaml 创建了以下 View

 <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));
        }
    }
}

我的目标是在不正确的字段周围显示红色边框,然后在其正下方显示错误消息,告诉用户哪里出了问题。

如何正确显示错误?另外,如何在首次加载 View 时不显示任何错误?

基于此blog我需要编辑 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>

但这并没有显示错误消息,而且在首次加载 View 时我也收到错误消息。最后,即使表单有效,操作按钮也会保持禁用状态。

已更新Property="Validation.ErrorTemplate" 移动到 FormControl 组后,它就起作用了。但是,错误消息似乎是通过按钮而不是按下按钮。此外,文本似乎没有垂直换行,允许边框覆盖其他控件,如您在以下屏幕显示中所见。

enter image description here enter image description here enter image description here

最佳答案

我会尽量回答你所有的问题:

如何正确显示错误?

未应用 ErrorTemplate,因为 TextBox 上的 FormControl 样式优先于包含 Validation.ErrorTemplate< 的样式。将 Validation.ErrorTemplate 代码移动到 FormControl 样式中将解决此问题。

另外,如何在首次加载 View 时不显示任何错误?

如果不立即应用,Required 验证有什么用? MinLengthMaxLength 验证只会在您开始在字段中输入时执行。

但是,错误消息似乎是通过按钮而不是按下按钮。

正如 Will 所指出的,这是因为错误消息显示在 AdornerLayer 上,它不会干扰您的控件所在的层。您有以下选择:

  • 使用 AdornerLayer 但在控件之间留一些空间
  • 使用工具提示显示错误消息
  • 在显示错误消息的 TextBox 模板中使用额外的 TextBlock

描述了这些选项 here

关于c# - 如何在 WPF 应用程序中显示错误消息?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48757179/

相关文章:

c# - 如何在 WPF 中暂停绑定(bind)的 UI 更新?

c# - 如何将两个 slider XAML 控件值绑定(bind)在一起?

android - 如何在堆栈布局或框架中添加点击事件

xaml - 具有不同模板的 LongListSelector 项

c# - 如何使用 MEF 导出和导入函数并执行它们?

c# - jquery 查找 gridview 链接按钮值

c# - 如何通过LINQ遍历xml中的属性

c# - 为什么数据未绑定(bind)到我的 WPF 应用程序中的标签

c# - 以互操作方式传递对象 - JavaBean 到 C#

wpf - 将 ListBoxItem 的 IsSelected 属性绑定(bind)到对象源的属性