验证规则未使用2个验证规则正确更新 [英] Validation Rule not updating correctly with 2 validation rules

查看:94
本文介绍了验证规则未使用2个验证规则正确更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I Have looked through some posts regarding Validation Rules but havn't come across the problem I am experiencing.

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

I am using a Validation Rule for a Textbox in a WPF Window. I have two checks, one for empty Textbox, and one for invalid characters using a RegEx match.

我的问题是这样的

在我的文本框中:

  1. A型-正常工作,什么也不显示
  2. 为空字符串命中退格键-Works,显示验证错误消息请在所有字段中输入值"
  3. 输入! -不起作用-它应显示找到了无效字符",但仍显示请在所有字段中输入一个值."
  4. 退格为空字符串-从技术上讲是可行的,因为它仍然显示第一个错误请在所有字段中输入一个值."
  5. 类型A-有效,没有错误消息
  6. 输入! -很好,显示消息发现无效字符.

反之亦然.

打开窗口

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

我的代码如下:

ProviderNew.xaml:

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" />

背后的密码-ProviderNew.xaml.cs

Privder code behind - 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事件以强制使用以下命令进行更新:

What I have tried is setting both LostFocus and TextChanged events to force update using:

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

在Validate方法上设置断点显示正确的匹配已完成,并且返回了正确的ValidationResult,但是它不能正确更新文本.

Setting breakpoints on the Validate method shows that the correct matches are done and the correct ValidationResult is returned, but it does not update the text correctly.

我在做一件令人难以置信的愚蠢的事情吗?

Am I doing something incredibly silly?

任何建议将不胜感激.

编辑1.

是的,我可以使用MultiDataTrigger并绑定到文本框来工作.

Yeah, I have that working, using MultiDataTrigger and Binding to the textboxes.

不起作用的是,当我第一次显示窗口"时,按钮是已启用",我不希望这样,因为这可能会使用户单击带有空文本框的保存".

What doesn't work is when I first show the Window, the button is Enabled, which I don't want it to be because this could allow the user to click save with empty textboxes.

打开窗口时,验证规则并非从一开始就直接起作用.

The Validation Rule doesn't work straight from the beginning when the Window opens.

我将焦点设置在文本框中,如果失去焦点或输入了错误的数据,则验证规则将启动并禁用该按钮.

I set the focus on the textbox and if it loses focus or incorrect data is enter, then The validation rule kicks in and disables the button.

将按钮默认设置为禁用,使其在打开时处于禁用状态,但是在没有验证错误的情况下不会启用.

Setting the button to be disabled by default, makes it disabled on opening, but then it isn't enabling when there are no Validation errors.

我可以通过强制使用诸如Load事件之类的验证规则进行检查来使它起作用,

I can make it work by forcing a check of the Validation Rule on say the Load event, using

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

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

But then when the Window first opens, it immediately shows the Validation Error message "Please enter a value in all fields", which I don't really like the look of.

无论如何,还是我不能两全其美.

Any way round this, or can I not have the best of both worlds.

这是按钮代码

<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类.

A quick question. I couldn't find the RelayCommand namespace. Searching other code around I found an MVVM example from microsoft that implements a RelayCommand : ICommand class.

这正确吗?

代码是:

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中实现了以下内容:

I implemented the following in my 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))'块中应该编码什么

To be honest I am unsure of what should be coded in the block in the 'if (!string.IsNullOrEmpty(ProviderName))'

您还说过要在DataContext中添加ICommand代码,所以不知道它是否在正确的位置,因为当我打开Window时,Save被启用并且单击它不起作用,即使所有字段中都有正确的数据也是如此.它.

Also you said to add the ICommand code in the DataContext, so not sure if it is in the right place because When I open the Window, Save is enable and clicking it does nothing, even if all the fields have correct data in it.

这是按钮的ProviderNew xaml代码

Here is the ProviderNew xaml code for the button

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

非常感谢您的帮助.

此致

尼尔

推荐答案

好,我设法重现了这个问题,这让我很困惑,因为它不起作用.但是我想通了.
问题是错误发生更改时不会触发转换器,因为实际属性(ProviderName)不会由于ValidationRule而更改.如果在Converter,ValidationRule和Property-setter中放置一个断点,则会看到此行为.

Ok I managed to reproduce this problem, and it was bugging me that it wasn't working. But I figured it out.
The problem is that the converter is not being fired when the error changes, because the actual property (ProviderName) does not change due to the ValidationRule. You can see this behavior if you put a breakpoint in the Converter, the ValidationRule and the Property-setter.

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

So instead of using an IValueConverter, you should change the binding of the tbNameValidation to the following:

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

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

the important part being Path=(Validation.Errors).CurrentItem.ErrorContent". This will ensure that the current error message is shown.

此外,如果希望从头开始显示"请在所有字段中输入一个值."消息,则应将Mode=TwoWay添加到txtBoxProviderName的绑定中:

Also, if you would like the "Please enter a value in all fields." message to show from the beginning, you should add Mode=TwoWay to the binding of txtBoxProviderName:

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

第2部分的编辑

要修复按钮的enabled属性,可以使用与问题相同的代码,但是可以使用Command属性代替使用Click事件运行保存代码,而无需使用Click事件来运行保存代码. :

to fix the enabled property of the button, you can use the same code as you have in your question, but instead of using the Click event to run your save-code, you can use the Command property instead:

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

in your DataContext, create a property of the type ICommand, and bind the button to this:

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

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

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

and the CanSave implementation can check if all expectations are met:

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

CanSave()将确定按钮的状态,因为CommandBinding将根据边界命令的CanExecute方法自动执行启用按钮的操作.不过,您的Validator的逻辑将返回此处,因此,找到一种重用该代码的方法可能是一个好主意.

The CanSave() will determine the state of your button, because the CommandBinding will automatically take care of enabling the button according to the CanExecute method of the bounded command. The logic of your Validator will come back here though, so it might be a good idea to find a way to reuse that code.

有关中继命令

这篇关于验证规则未使用2个验证规则正确更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆