【问题标题】:Hide WPF validation error when window is first loaded首次加载窗口时隐藏 WPF 验证错误
【发布时间】:2015-02-11 20:03:55
【问题描述】:

当我第一次加载窗口时,按钮是可见的,并且验证中没有错误(没有红线圆形文本框)。

在文本框中输入值时,验证规则会正常工作。

如果可能,我希望在开始时隐藏按钮,并在我开始在框中输入文本时启动验证规则。

这是我到目前为止的代码。 xml:

 <TextBox x:Name="txtName" HorizontalAlignment="Left" Height="23" Margin="156,119,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}">
        <TextBox.Text>
            <Binding Path="Name" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True">
                <Binding.ValidationRules>
                    <local:ValidationTest/>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>


    <Button x:Name="btn1" Content="Button" HorizontalAlignment="Left" Margin="85,221,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click">
        <Button.Style>
            <Style TargetType="Button">
                <Setter Property="Visibility" Value="Hidden"/>
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Path=(Validation.HasError), ElementName=txtName}" Value="False"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Visibility" Value="Visible"></Setter>
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>

我的验证逻辑:

class ValidationTest : ValidationRule
{
    private int result;
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        if (value == null || string.IsNullOrEmpty(value.ToString()))
        {
            return new ValidationResult(false, "Value cannot be empty.");
        }
        if (value.ToString().Length > 4)
        {
            return new ValidationResult(false, "Name cannot be more than 20 characters long.");
        }
        return ValidationResult.ValidResult;
    }
}

我正在使用的错误模板:

    <Window.Resources>
    <Style TargetType="TextBox">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                    Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                    Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <ControlTemplate x:Key="ValidationErrorTemplate">
        <DockPanel>
            <Border BorderBrush="Red" BorderThickness="1.5">
                <AdornedElementPlaceholder x:Name="ErrorAdorner"></AdornedElementPlaceholder>
            </Border>
        </DockPanel>
    </ControlTemplate>
</Window.Resources>

我尝试在窗口加载时使用txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource(); 更新绑定,但这显示了验证错误(文本框周围的红线)。但是,按钮是隐藏的,那么有什么方法可以隐藏验证错误,直到用户在文本框中输入文本?

我是否以正确的方式处理这个问题?

【问题讨论】:

  • 在这里分享更多代码。

标签: c# wpf validation


【解决方案1】:

我知道我参加聚会有点晚了,但我在想做同样的事情时遇到了这个问题。我唯一不喜欢使用标志来控制何时执行验证的是您需要在代码中的某个位置设置DoValidation 标志,我希望它更加“自动化”。

我在网上找到了很多示例,但它们似乎都使用了布尔标志方法。我找到了this MSDN Article,并以此为基础,然后调整了代码。

我想出了一个似乎非常有效的解决方案。简而言之,我所做的不是让一个变量来跟踪何时应该执行验证,而是创建了另一个 Dictionary 来跟踪:

  1. 何时应执行验证。
  2. 存储验证状态(有效、无效)。

我只希望在第一次更新后执行验证,所以首要任务是决定是否应该执行验证。验证的第一次运行将参数存储在Dictionary 中,然后下一次如果参数存在,它将执行验证并存储真/假(无效/有效)结果。这也是告诉模型是否已验证以及是否有效的便捷方式,因此我还添加了一个参数/标志来简单地返回是否有任何结果和验证状态。这对于绑定命令启用/禁用特别有用。

我是这样实现的:

基础属性验证模型:

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

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

   /// <summary>
   /// This holds the list of validation results and controls when the validation should be 
   /// performed and if the validation is valid.
   /// </summary>
   private Dictionary<string, bool> _validationResults { get; set; } = new Dictionary<string, bool>();

   #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;
      OnPropertyChanged(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)
   {
      string error = string.Empty;

      if (string.IsNullOrEmpty(propertyName))
      {
         throw new ArgumentException("Invalid property name", propertyName);
      }

      //Check if the Field has been added, this keeps track of when the validation
      //is performed.
      if (_validationResults.Any(x => x.Key == propertyName))
      {
         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;

            //Store a true result in the validation to set the error.
            _validationResults[propertyName] = true;
         }
         else
         {
            //If the Validation has been run and not invalid make sure the 
            //paramter in the list is cleared, otherwise validation would 
            //always return invalid once it is invalidated.
            _validationResults[propertyName] = false;
         }
      }
      else
      {
         //This is the first run of the Validation, simply store the paramter
         //in the validation list and wait until next time to validate.
         _validationResults.Add(propertyName, true);
      }

      //Notify that things have changed
      OnPropertyChanged("IsValid");

      //Return the actual result
      return error;
   }

   #endregion

   #region Public

   /// <summary>
   /// This returns if the Validation is Valid or not
   /// </summary>
   /// <returns>True if the Validation has been perfomed and if there are not 
   /// true values. Will return false until the validation has been done once.</returns>
   public bool IsValid {
      get { return (!_validationResults.Any(x => x.Value) && (_validationResults.Count > 0)); }
   }

   /// <summary>
   /// Clears/Reset the Validation
   /// </summary>
   public void ClearValidation()
   {
      _validationResults.Clear();
   }

   #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 OnPropertyChanged([CallerMemberName] string propertyName = null)
   {
      this.VerifyPropertyName(propertyName);

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

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

   #endregion // IOnPropertyChanged 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);
      }

      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
}

型号:

public class MyModel : PropertyValidation
{
   [Required(ErrorMessage = "Name must be specified")]
   [MaxLength(50, ErrorMessage = "Name too long, Name cannot contain more than 50 characters")]
   public string Name {
      get { return GetValue(() => Name); }
      set { SetValue(() => Name, value); }
   }

   [Required(ErrorMessage = "Description must be specified")]
   [MaxLength(150, ErrorMessage = "Description too long, Description cannot contain more than 150 characters")]
   public string Description {
      get { return GetValue(() => Description); }
      set { SetValue(() => Description, value); }
   }
}

错误模板:

<ControlTemplate x:Key="ValidationErrorTemplate">
   <DockPanel LastChildFill="true">
      <Border Background="Red" DockPanel.Dock="right" 
                           Margin="-20,0,0,0" Width="10" Height="10" CornerRadius="10"
                           ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
         <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white"/>
      </Border>
      <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
         <Border BorderBrush="red" BorderThickness="1" >
            <Border.Effect>
               <BlurEffect Radius="5" />
            </Border.Effect>
         </Border>
      </AdornedElementPlaceholder>

   </DockPanel>
</ControlTemplate>

数据模板/表格:

<DataTemplate x:Key="MyModelDetailsTemplate" DataType="{x:Type data:MyModel}" >

   <StackPanel Grid.IsSharedSizeScope="True">

      <Grid>
         <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" SharedSizeGroup="Labels" />
            <ColumnDefinition Width="*" />
         </Grid.ColumnDefinitions>

         <Label Grid.Column="0">Name</Label>
         <TextBox x:Name="Name" 
                  Grid.Column="1" 
                  MinWidth="150"
                  Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
                  Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
      </Grid>
      <Grid>
         <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" SharedSizeGroup="Labels" />
            <ColumnDefinition Width="*" />
         </Grid.ColumnDefinitions>

         <Label Grid.Column="0">Description</Label>
         <TextBox Grid.Column="1" 
                   Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
                  Text="{Binding Description, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" AcceptsReturn="True" VerticalAlignment="Stretch" />

      </Grid>
   </StackPanel>

</DataTemplate>

RelayCommand(为了完整性)

public class RelayCommand : ICommand
{
   private Action<object> execute;

   private Predicate<object> canExecute;

   private event EventHandler CanExecuteChangedInternal;

   public RelayCommand(Action<object> execute) : this(execute, DefaultCanExecute)
   {
   }

   public RelayCommand(Action<object> execute, Predicate<object> canExecute)
   {
      if (execute == null)
      {
         throw new ArgumentNullException("execute");
      }

      if (canExecute == null)
      {
         throw new ArgumentNullException("canExecute");
      }

      this.execute = execute;
      this.canExecute = canExecute;
   }

   public event EventHandler CanExecuteChanged {
      add {
         CommandManager.RequerySuggested += value;
         this.CanExecuteChangedInternal += value;
      }

      remove {
         CommandManager.RequerySuggested -= value;
         this.CanExecuteChangedInternal -= value;
      }
   }

   public bool CanExecute(object parameter)
   {
      return this.canExecute != null && this.canExecute(parameter);
   }

   public void Execute(object parameter)
   {
      this.execute(parameter);
   }

   public void OnCanExecuteChanged()
   {
      EventHandler handler = this.CanExecuteChangedInternal;
      if (handler != null)
      {
         //DispatcherHelper.BeginInvokeOnUIThread(() => handler.Invoke(this, EventArgs.Empty));
         handler.Invoke(this, EventArgs.Empty);
      }
   }

   public void Destroy()
   {
      this.canExecute = _ => false;
      this.execute = _ => { return; };
   }

   private static bool DefaultCanExecute(object parameter)
   {
      return true;
   }
}

视图模型:

** 注意这里的 PropertyValidation 不是必需的,可以使用单独的 INotifyPropertyChanged 基础,我只将它用于 OnPropertyChanged 通知并保持简单**

public class PageHomeVM : PropertyValidation
{
   private ICommand saveCommand;
   public ICommand SaveCommand {
      get {
         return saveCommand;
      }
      set {
         saveCommand = value;
         OnPropertyChanged();
      }
   }

   public MyModel MyModel { get; set; } = new MyModel();

   public PageHomeVM()
   {
      SaveCommand = new RelayCommand(SaveRecord, p => MyModel.IsValid);
      MyModel.ClearValidation();
   }

   public void SaveRecord(object p)
   {
      //Perform the save....
   }
}

查看:

<pages:BasePage.DataContext>
   <ViewModels:PageHomeVM/>
</pages:BasePage.DataContext>

<StackPanel>
   <Label Content="MyModel Details"/>

   <ContentPresenter ContentTemplate="{StaticResource MyModelDetailsTemplate}" Content="{Binding MyModel}" />

   <Button x:Name="btnSave" 
            Command="{Binding SaveCommand}"
            Width="75"
            HorizontalAlignment="Right">Save</Button>

</StackPanel>

我希望这会有所帮助...

【讨论】:

    【解决方案2】:

    一旦你有任何价值就保存一个标志

    class ValidationTest : ValidationRule
    {
        private int result;
        private bool hadValue = false;
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            if (hadValue  && (value == null || string.IsNullOrEmpty(value.ToString())))
            {
                return new ValidationResult(false, "Value cannot be empty.");
            }
            if (value.ToString().Length > 4)
            {
                hadValue = true;
                return new ValidationResult(false, "Name cannot be more than 20 characters long.");
            }
            hadValue = true;
            return ValidationResult.ValidResult;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-08
      • 1970-01-01
      • 2011-09-25
      • 2011-10-05
      • 2010-10-23
      相关资源
      最近更新 更多