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

C# 首次加载窗口时隐藏WPF验证错误,c#,wpf,validation,C#,Wpf,Validation,当我第一次加载窗口时,按钮是可见的,并且验证中没有错误(文本框周围没有红线) 在文本框中键入值时,验证规则会正常工作 如果可能的话,我希望在开始时隐藏按钮,并在我开始向框中键入文本时启动验证规则 这是我目前掌握的代码。xaml: <TextBox x:Name="txtName" HorizontalAlignment="Left" Height="23" Margin="156,119,0,0" TextWrapping="Wrap" VerticalAlignment="Top"

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

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

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

这是我目前掌握的代码。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>
我正在使用的错误模板:

    <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()更新绑定,但显示验证错误(文本框周围的红线)。但是,按钮是隐藏的,因此在用户将文本输入文本框之前,是否有任何方法隐藏验证错误


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

一旦您有任何价值,请保存一个标志

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

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

我在网上找到了不少示例,但它们似乎都使用了布尔标志方法。我找到并使用它作为基础,然后调整代码

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

  • 何时应执行验证
  • 存储验证的状态(有效、无效)
  • 我只希望在第一次更新之后执行验证,因此第一个任务是决定是否应该执行验证。第一次运行的验证将参数存储在
    字典中
    ,然后下次如果参数存在,它将执行验证并存储真/假(无效/有效)结果。这也是判断模型是否已验证以及是否有效的一种简便方法,因此我还添加了一个参数/标志,以简单地返回是否有任何结果和验证状态。这对于绑定命令enable/disable特别有用

    我是如何做到这一点的:

    基本属性验证模型: 错误模板: 视图:
    
    拯救
    

    我希望这有助于…

    在此处共享更多代码。
    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;
        }
    }
    
    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>
    
    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;
       }
    }
    
    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>