c# - 首次加载窗口时隐藏 WPF 验证错误

标签 c# wpf validation

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

what window looks like when first loaded

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

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

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

 <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(); 更新绑定(bind),但这显示了验证错误(文本框周围的红线)。但是,该按钮是隐藏的,那么有什么方法可以隐藏验证错误,直到用户在文本框中键入文本?

what i get when i update the binding when the windows loads

我的处理方式是否正确?

最佳答案

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

网上找了好几个例子,好像都是用 bool 标志的方法。我找到了 this MSDN Article并以此为基础,然后调整代码。

我想出了一个似乎非常有效的解决方案。简而言之,我所做的基本上是创建另一个 Dictionary 来跟踪:

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

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

我是这样实现的:

基本属性验证模型:

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

View 模型:

** 请注意,这里不需要 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>

我希望这有助于...

关于c# - 首次加载窗口时隐藏 WPF 验证错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28463443/

相关文章:

javascript - 表中复选框的jquery验证代码

forms - 使用组件的 Angular 2 表单级别验证

c# - Azure部署更新-正在运行的服务的证书与上传的sdk包中的证书不匹配

c# - 寻找将温莎生活方式融入图书馆的方法

c# - 使用 .NET 4.5.2 从 C# 代码更改键盘布局

c# - 将 C# 数组传递给 C++ 方法

c# - 为什么我需要绑定(bind)到代码隐藏而不是 View 模型中的依赖属性?

WPF - HeaderStringFormat 在扩展器中不起作用

wpf - WPF中的绑定(bind)和布局关系

javascript - 有没有更好的非空验证方法?