错误模板设计

11

我记得在这个网站上看到过关于这个问题的另一个问题/答案,但我想不起来答案是什么了,现在也找不到原帖了。

我不喜欢WPF中默认的错误模板。我知道如何更改此错误模板。然而,如果我向文本框等元素添加一些内容,则元素的大小不会更改,而添加的内容可能会被裁剪。在这种情况下,如何修改元素(我认为正确的术语是adorned element),以便不会裁剪任何内容?

下面是错误模板的XAML:

<Style TargetType="{x:Type TextBox}">
  <Setter Property="Validation.ErrorTemplate">
    <Setter.Value>
      <ControlTemplate>
        <StackPanel>
          <AdornedElementPlaceholder />
          <TextBlock Foreground="Red" Text="Error..." />
        </StackPanel>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

以下是表单中一些文本框的XAML代码:

<StackPanel>
  <TextBox Text="{Binding...}" />
  <TextBox />
</StackPanel>

我添加了可能使用的XAML。XAML比我通常使用的要简单得多,但确实能正确演示该问题。任何错误消息实际上会显示在第二个文本框上方。当第一个文本框的错误消息被显示时,我想让第二个文本框自动向下移动(并在错误消息消失时向上移动)。 - Jason Richmeier
1个回答

13
这是根据Josh Smith的文章“绑定到(Validation.Errors)[0]而不创建调试输出”改编的解决方案。
诀窍是定义一个DataTemplate来呈现ValidationError对象,然后使用ContentPresenter来显示错误消息。如果没有错误,则不会显示ContentPresenter。
下面,我分享了我创建的示例应用程序的代码。

Without errors With errors

这里是XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        SizeToContent="WidthAndHeight"
        Title="MainWindow">
    <StackPanel Margin="5">
        <StackPanel.Resources>
            <DataTemplate DataType="{x:Type ValidationError}">
                <TextBlock Text="{Binding ErrorContent}" Foreground="White" Background="Red" VerticalAlignment="Center" FontWeight="Bold"/>
            </DataTemplate>
        </StackPanel.Resources>
        <TextBox Name="TextBox1" Text="{Binding Text1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
        <ContentPresenter Content="{Binding ElementName= TextBox1, Path=(Validation.Errors).CurrentItem}" HorizontalAlignment="Left"/>

        <TextBox Name="TextBox2" Text="{Binding Text2, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
        <ContentPresenter Content="{Binding ElementName= TextBox2, Path=(Validation.Errors).CurrentItem}" HorizontalAlignment="Left"/>
        <Button Content="Validate" Click="Button_Click"/>
    </StackPanel>
</Window>

代码后文件:

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private ViewModel _ViewModel = null;

        public MainWindow()
        {
            InitializeComponent();
            _ViewModel = new ViewModel();
            DataContext = _ViewModel;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _ViewModel.Validate = true;
            _ViewModel.OnPropertyChanged("Text1");
            _ViewModel.OnPropertyChanged("Text2");
        }
    }
}

视图模型:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace WpfApplication1
{
    public class ViewModel : INotifyPropertyChanged, IDataErrorInfo
    {
        private string _Text1;
        public string Text1
        {
            get { return _Text1; }
            set
            {
                _Text1 = value;
                OnPropertyChanged("Text1");
            }
        }

        private string _Text2;
        public string Text2
        {
            get { return _Text2; }
            set
            {
                _Text2 = value;
                OnPropertyChanged("Text2");
            }
        }

        public bool Validate { get; set; }

        #region INotifyPropertyChanged Implemenation
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region IDataErrorInfo Implementation
        public string Error
        {
            get { return null; }
        }

        public string this[string columnName]
        {
            get
            {
                string errorMessage = string.Empty;
                if (Validate)
                {
                    switch (columnName)
                    {
                        case "Text1":
                            if (Text1 == null)
                                errorMessage = "Text1 is mandatory.";
                            else if (Text1.Trim() == string.Empty)
                                errorMessage = "Text1 is not valid.";
                            break;
                        case "Text2":
                            if (Text2 == null)
                                errorMessage = "Text2 is mandatory.";
                            else if (Text2.Trim() == string.Empty)
                                errorMessage = "Text2 is not valid.";
                            break;
                    }
                }
                return errorMessage;
            }
        }
        #endregion
    }
}

谢谢你的回答。我忘记检查是否有任何回复了。你提供的示例正好实现了我想要达到的目标。 - Jason Richmeier
谢谢你分享这个宝贵的信息。正是我一直在寻找的。我还没有遇到过任何好的例子,点赞! - Andez
谢谢你的回答!我已经用这个案例创建了一个项目。在XAML中的两个ContentPresenter控件中,它抱怨ReadOnlyObservableCollection'1'类型中找不到“CurrentItem”属性,但尽管如此仍然可以运行并正常工作。 - Rod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接