【问题标题】:Validation Rule not updating correctly with 2 validation rules验证规则未使用 2 个验证规则正确更新
【发布时间】:2013-02-05 17:09:45
【问题描述】:

我浏览了一些有关验证规则的帖子,但没有遇到我遇到的问题。

我正在为 WPF 窗口中的文本框使用验证规则。我有两项检查,一项针对空文本框,一项针对使用 RegEx 匹配的无效字符。

我的问题是这样的:

在我的文本框中:

  1. A 型 - 有效,无显示
  2. 空字符串按退格键 - 有效,显示验证错误消息“请在所有字段中输入值”
  3. 键入! - 不起作用 - 它应该显示“发现无效字符”,但仍显示“请在所有字段中输入值”。
  4. 退格到空字符串 - 技术上有效,因为它仍然显示第一个错误“请在所有字段中输入值。”
  5. A 型 - 有效,没有错误消息
  6. 键入! - 很好,显示消息“发现无效字符。

反之亦然。

打开窗口

  1. 键入! - 很好,显示“发现无效字符。
  2. 退格到空字符串 - 仍然显示“发现无效字符”而不是“请在所有字段中输入一个值。

我的代码如下:

ProviderNew.xaml:

<Label Name="lblProviderName" Content="Provider Name: " 
                   Grid.Row="1" Grid.Column="0"/>

<TextBox Name="txtBoxProviderName" Margin="2" 
    MaxLength="20" CharacterCasing="Upper"
    Grid.Row="1" Grid.Column="1" LostFocus="TxtBoxProviderNameLostFocus"   TextChanged="TxtBoxProviderNameTextChanged">
                <TextBox.Text>
                    <Binding ElementName="This" Path="ProviderName" UpdateSourceTrigger="PropertyChanged">
                        <Binding.ValidationRules>
                            <Domain:ProviderValidation />
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
            </TextBox>

            <TextBlock x:Name="tbNameValidation"  Foreground="Red" FontWeight="Bold" Margin="10,0,0,0"
                       Text="{Binding ElementName=txtBoxProviderName, Path=(Validation.Errors), Converter={StaticResource eToMConverter}}"
                       Grid.Row="1" Grid.Column="2" />

Privder 代码背后 - ProviderNew.xaml.cs

public static readonly DependencyProperty NewProviderNameProperty = DependencyProperty.Register("ProviderName",
        typeof (string), typeof(ProviderNew), new UIPropertyMetadata(""));


    public string ProviderName
    {
        get { return (string) GetValue(NewProviderNameProperty); }
        set { SetValue(NewProviderNameProperty, value); }
    }

值转换器类

public class ErrorsToMessageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var sb = new StringBuilder();
        var errors = value as ReadOnlyCollection<ValidationError>;

        if (errors != null && errors.Count > 0)
        {

            foreach (var e in errors.Where(e => e.ErrorContent != null))
            {
                sb.AppendLine(e.ErrorContent.ToString());
            }
        }
        return sb.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

提供者类

private string _providerName;
    public string ProviderName
    {
        get { return _providerName; }
        set
        {
            _providerName = value;
            RaisePropertyChanged("ProviderName");
        }
    }

private void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

验证规则类

public class ProviderValidation : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        var str = value as string;

        if (str != null && !Regex.IsMatch(str, @"^[a-zA-Z0-9]*$"))
        {
           return new ValidationResult(false, "Invalid characters were found.");
        }


        if (String.IsNullOrEmpty(str))
        {
            return new ValidationResult(false, "Please enter a value in all fields.");
        }

        return new ValidationResult(true, null);
    }
}

我尝试过设置 LostFocus 和 TextChanged 事件以强制更新:

var expression = txtBoxProviderName.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
    expression.UpdateSource();

在 Validate 方法上设置断点表明完成了正确的匹配并返回了正确的 ValidationResult,但它没有正确更新文本。

我在做一些非常愚蠢的事情吗?

任何建议将不胜感激。

编辑 1。

是的,我可以使用 MultiDataTrigger 并绑定到文本框。

什么不起作用是当我第一次显示窗口时,按钮是启用的,我不希望它是这样,因为这可能允许用户使用空文本框单击保存。

当窗口打开时,验证规则不会从一开始就直接起作用。

我将焦点设置在文本框上,如果它失去焦点或输入了不正确的数据,则验证规则将启动并禁用该按钮。

将按钮设置为默认禁用,使其在打开时禁用,但在没有验证错误时不启用。

我可以通过在 Load 事件上强制检查验证规则来使其工作,使用

var expression = txtBoxProviderName.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
    expression.UpdateSource();

但是当窗口第一次打开时,它立即显示验证错误消息“请在所有字段中输入一个值”,我不太喜欢它的外观。

无论如何,或者我不能两全其美。

这是按钮代码

<Button x:Name="btnSave" Height="25" Width="100" Content="Save" HorizontalAlignment="Right" Margin="0,0,120,0"
            Grid.Row="2" Click="BtnSaveClick" >
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="IsEnabled" Value="False" />
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding ElementName=txtBoxProviderName, Path=(Validation.HasError)}" Value="False" />
                            <Condition Binding="{Binding ElementName=txtBoxHelpDesk, Path=(Validation.HasError)}" Value="False" />
                            <Condition Binding="{Binding ElementName=txtBoxRechargeInstructions, Path=(Validation.HasError)}" Value="False" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="IsEnabled" Value="True" />
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>     
    </Button>

谢谢,

尼尔

编辑 2

一个简单的问题。我找不到 RelayCommand 命名空间。搜索周围的其他代码,我发现了一个来自 microsoft 的 MVVM 示例,它实现了 RelayCommand : ICommand 类。

这对吗?

代码是:

public class RelayCommand : ICommand
{
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields

    #region Constructors

    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Creates a new command.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members
}

我在 ProviderNew.xaml.cs 中实现了以下内容:

private ICommand saveCommand;
    public ICommand SaveCommand
    {
        get
        {
            if (saveCommand == null)
            {
                saveCommand = new RelayCommand(p => DoSave(), p => CanSave() );
            }
            return saveCommand;
        }
    }

    private bool CanSave()
    {
        if (!string.IsNullOrEmpty(ProviderName))
        {
            ??? What goes here? 
        }


        return true;
    }
private bool DoSave()
    {
        // Save the information to DB
    }

老实说,我不确定“if (!string.IsNullOrEmpty(ProviderName))”的块中应该编码什么

您还说要在 DataContext 中添加 ICommand 代码,所以不确定它是否在正确的位置,因为当我打开窗口时,保存是启用的,单击它什么也不做,即使所有字段都有正确的数据它。

这里是按钮的 ProviderNew xaml 代码

<Button x:Name="btnSave" Height="25" Width="100" Content="Save" HorizontalAlignment="Right" Margin="0,0,120,0"
            Grid.Row="2" Command="{Binding SaveCommand}" >
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="IsEnabled" Value="False" />
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding ElementName=txtBoxHelpDesk, Path=(Validation.HasError)}" Value="False" />
                            <Condition Binding="{Binding ElementName=txtBoxProviderName, Path=(Validation.HasError)}" Value="False" />
                            <Condition Binding="{Binding ElementName=txtBoxRechargeInstructions, Path=(Validation.HasError)}" Value="False" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="IsEnabled" Value="True" />
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>     
    </Button>

非常感谢您的帮助。

问候,

尼尔

【问题讨论】:

  • IDataErrorInfo 验证而不是 ValidationRule 存在同样的问题,以防其他人遇到此问题

标签: c# wpf validation multidatatrigger


【解决方案1】:

好的,我设法重现了这个问题,但它无法正常工作,这让我很烦恼。但我想通了。
问题是当错误发生变化时转换器没有被触发,因为实际属性 (ProviderName) 由于 ValidationRule 而没有改变。如果您在 Converter、ValidationRule 和 Property-setter 中放置断点,则可以看到此行为。

因此,您应该将tbNameValidation 的绑定更改为以下内容,而不是使用IValueConverter

<TextBlock x:Name="tbNameValidation"  
           Text="{Binding ElementName=txtBoxProviderName, 
                  Path=(Validation.Errors).CurrentItem.ErrorContent}">

重要的部分是 Path=(Validation.Errors).CurrentItem.ErrorContent"。 这将确保显示当前的错误消息。

另外,如果您希望“请在所有字段中输入一个值。”消息从头开始显示,您应该将Mode=TwoWay添加到txtBoxProviderName的绑定中: p>

 <Binding ElementName="This" 
          Path="ProviderName" 
          UpdateSourceTrigger="PropertyChanged"
          Mode="TwoWay">

第二部分编辑

要修复按钮的启用属性,您可以使用与问题中相同的代码,但不是使用Click 事件来运行您的保存代码,而是可以使用Command 属性:

在您的 DataContext 中,创建 ICommand 类型的属性,并将按钮绑定到此:

<Button Command="{Binding SaveCommand}" .... />

private ICommand saveCommand; 
public ICommand SaveCommand { 
    get { 
        if (saveCommand == null) {
            saveCommand = new RelayCommand(p => DoSave(), p=> CanSave() ); 
         } 
         return saveCommand; 
      } 
 }

CanSave 实现可以检查是否满足所有期望:

private bool CanSave(){
   if (!string.IsNullOrEmpty(ProviderName)){
       //.... etc
   }
 }

CanSave() 将确定按钮的状态,因为 CommandBinding 将根据有界命令的 CanExecute 方法自动处理启用按钮。不过,验证器的逻辑会回到这里,因此找到一种方法来重用该代码可能是个好主意。

更多关于Relaying Commands的信息

【讨论】:

  • 非常感谢。现在效果很好。你已经回答了我的第二个问题。让规则验证从头开始工作,因为如果规则验证失败,我有一个未启用的保存按钮。然而,Mode="TwoWay" 并没有这样做。双向不是默认模式吗?
  • 您可以通过BindingTrigger 使用Validation.HasError 属性来控制保存按钮的状态
  • 请参阅帖子上的编辑 1,这里的所有信息太短了。谢谢
  • 查看编辑,希望对您有所帮助。在 WPF 恕我直言,验证一直是一件棘手的事情......
  • 如果您愿意,请在聊天框上联系我,我可以提供更多信息。问题不再是原始问题。 chat.stackoverflow.com/rooms/7/c
猜你喜欢
  • 2020-07-09
  • 1970-01-01
  • 2012-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-17
  • 2013-06-03
  • 2012-08-31
相关资源
最近更新 更多