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

时间:2015-02-11 20:03:55

标签: 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();加载窗口时更新绑定,但是显示验证错误(文本框周围的红线)。但是,该按钮是隐藏的,所以有没有办法隐藏验证错误,直到用户在文本框中键入文本为止?

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

我是以正确的方式接近这个吗?

2 个答案:

答案 0 :(得分:0)

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

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

答案 1 :(得分:0)

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

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

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

  1. 何时应进行验证。
  2. 存储验证状态(有效,无效)。
  3. 我只希望在第一次更新后执行验证,因此第一项业务是决定是否应该执行验证。第一次验证将参数存储在Dictionary中,然后如果参数存在则下次执行验证并存储真/假(无效/有效)结果。这也是告诉模型是否已经验证以及它是否有效的方便方法,所以我还添加了一个参数/标志,只要有任何结果和验证状态就返回。这对于绑定命令enable / disable特别有用。

    以下是我完成此操作的方法:

    Base PropertyValidation Model:

    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>
    

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