强制验证 WPF 中的绑定控件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/483419/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 19:59:10  来源:igfitidea点击:

Force validation on bound controls in WPF

wpfvalidationbusiness-objects

提问by Valentin Vasilyev

I have a WPF dialog with a couple of textboxes on it. Textboxes are bound to my business object and have WPF validation rules attached.

我有一个 WPF 对话框,上面有几个文本框。文本框绑定到我的业务对象并附加了 WPF 验证规则。

The problem is that user can perfectly click 'OK' button and close the dialog, without actually entering the data into textboxes. Validation rules never fire, since user didn't even attempt entering the information into textboxes.

问题是用户可以完美地单击“确定”按钮并关闭对话框,而无需将数据实际输入到文本框中。验证规则永远不会触发,因为用户甚至没有尝试将信息输入到文本框中。

Is it possible to force validation checks and determine if some validation rules are broken?

是否可以强制验证检查并确定某些验证规则是否被破坏?

I would be able to do it when user tries to close the dialog and prohibit him from doing it if any validation rules are broken.

当用户尝试关闭对话框并在任何验证规则被破坏时禁止他这样做时,我将能够做到这一点。

Thank you.

谢谢你。

回答by Ken Smith

In 3.5SP1 / 3.0SP2, they also added a new property to the ValidationRule base, namely, ValidatesOnTargetUpdated="True". This will call the validation as soon as the source object is bound, rather than only when the target control is updated. That may not be exactly what you want, but it's not bad to see initially all the stuff you need to fix.

在 3.5SP1 / 3.0SP2 中,他们还在 ValidationRule 基础中添加了一个新属性,即ValidatesOnTargetUpdated="True"。这将在源对象绑定后立即调用验证,而不是仅在目标控件更新时调用。这可能不是您想要的,但最初看到您需要修复的所有内容也不错。

Works something like this:

像这样工作:

<TextBox.Text>
    <Binding Path="Amount" StringFormat="C">
        <Binding.ValidationRules>
            <validation:RequiredValidationRule 
                ErrorMessage="The pledge amount is required." 
                ValidatesOnTargetUpdated="True"  />
            <validation:IsNumericValidationRule 
                ErrorMessage="The pledge amount must be numeric." 
                ValidationStep="ConvertedProposedValue" 
                ValidatesOnTargetUpdated="True"  />
        </Binding.ValidationRules>
    </Binding>
</TextBox.Text>

回答by Robert Macnee

We have this issue in our application as well. The validation only fires when bindings update, so you have to update them by hand. We do this in the Window's Loadedevent:

我们的应用程序中也有这个问题。验证仅在绑定更新时触发,因此您必须手动更新它们。我们在 Window 的Loaded事件中这样做:

public void Window_Loaded(object sender, RoutedEventArgs e)
{
    // we manually fire the bindings so we get the validation initially
    txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
    txtCode.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}

This will make the error template (red outline) appear, and set the Validation.HasErrorproperty, which we have triggering the OK button to disable:

这将使错误模板(红色轮廓)出现,并设置Validation.HasError属性,我们已触发 OK 按钮禁用该属性:

<Button x:Name="btnOK" Content="OK" IsDefault="True" Click="btnOK_Click">
    <Button.Style>
        <Style TargetType="{x:Type Button}">
            <Setter Property="IsEnabled" Value="false" />
            <Style.Triggers>
                <!-- Require the controls to be valid in order to press OK -->
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding ElementName=txtName, Path=(Validation.HasError)}" Value="false" />
                        <Condition Binding="{Binding ElementName=txtCode, Path=(Validation.HasError)}" Value="false" />
                    </MultiDataTrigger.Conditions>
                    <Setter Property="IsEnabled" Value="true" />
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

回答by ryan

Here is an alternative way that doesn't require calling "UpdateSource()" or "UpdateTarget()":

这是一种不需要调用“UpdateSource()”或“UpdateTarget()”的替代方法:

var binding = thingToValidate.GetBinding(propertyToValidate);
foreach (var rule in binding.ValidationRules)
{
    var value = thingToValidate.GetValue(propertyToValidate);
    var result = rule.Validate(value, CultureInfo.CurrentCulture);
    if (result.IsValid) 
         continue;
    var expr = BindingOperations.GetBindingExpression(thingToValidate, propertyToValidate);
    if (expr == null)  
        continue;
    var validationError = new ValidationError(rule, expr);
    validationError.ErrorContent = result.ErrorContent;
    Validation.MarkInvalid(expr, validationError);
}

回答by lukep

Just in case anyone happens to find this old question and is looking for an answer that addresses Monstieur's comment about UI guidelines, I did the following:

以防万一有人碰巧找到这个老问题并正在寻找解决 Monstieur 关于 UI 指南的评论的答案,我做了以下操作:

Xaml

xml

<TextBox.Text>
    <Binding Path="TextValue" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules>
            <local:RequiredFieldValidationRule>
                    <local:RequiredFieldValidationRule.IsRequiredField>
                    <local:BoolValue Value="{Binding Data.Required, Source={StaticResource proxy}}" />
                </local:RequiredFieldValidationRule.IsRequiredField>
                <local:RequiredFieldValidationRule.ValidationFailed>
                    <local:BoolValue Value="{Binding Data.HasValidationError, Mode=TwoWay, Source={StaticResource proxy}}" />
                </local:RequiredFieldValidationRule.ValidationFailed>
            </local:RequiredFieldValidationRule>
        </Binding.ValidationRules>
    </Binding>
</TextBox.Text>

RequiredFieldValidationRule:

必填字段验证规则:

public class RequiredFieldValidationRule : ValidationRule
{
    private BoolValue _isRequiredField;
    public BoolValue IsRequiredField
    {
        get { return _isRequiredField; }
        set { _isRequiredField = value; }
    }
    private BoolValue _validationFailed;
    public BoolValue ValidationFailed
    {
        get { return _validationFailed; }
        set { _validationFailed = value; }
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        ValidationFailed.Value = IsRequiredField.Value && (value == null || value.ToString().Length == 0);
        return new ValidationResult(!ValidationFailed.Value, ValidationFailed.Value ? "This field is mandatory" : null);
    }
}

In the class that the Xaml binds to

在 Xaml 绑定到的类中

private bool _hasValidationError;
public bool HasValidationError
{
    get { return _hasValidationError; }
    set { _hasValidationError = value; NotifyPropertyChanged(nameof(HasValidationError)); }
}


public void InitialisationMethod() // Or could be done in a constructor
{
    _hasValidationError = Required; // Required is a property indicating whether the field is mandatory or not
}

I then hide my Save button using a bound property, if any of my objects has HasValidationError = true.

然后我使用绑定属性隐藏我的保存按钮,如果我的任何对象具有 HasValidationError = true。

Hope this is helpful to someone.

希望这对某人有帮助。

回答by Alex Pollan

Use the method above proposed by Robert Macnee. For example:

使用 Robert Macnee 提出的上述方法。例如:

//force initial validation
foreach (FrameworkElement item in grid1.Children)
{
    if (item is TextBox)
    {
        TextBox txt = item as TextBox;
        txt.GetBindingExpression(TextBox.TextProperty).UpdateSource();
    }
}        

But, BE SURE that the bound controls are Visibles before this code run!

但是,请确保在此代码运行之前绑定的控件是可见的!

回答by Jenzo

using the INotifyPropertychanged on your data object

在您的数据对象上使用 INotifyPropertychanged

public class MyObject : INotifyPropertyChanged
{
    string _MyPropertyToBind = string.Empty;
    public string MyPropertyToBind
    {
        get
        {
            return _MyPropertyToBind;
        }
        set
        {
            _MyPropertyToBind = value;
            NotifyPropertyChanged("MyPropertyToBind");
        }
    }

    public void NotifyPropertyChanged(string property)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

}

you can add the following code to your control

您可以将以下代码添加到您的控件中

<TextBox Text="{Binding MyPropertyToBind, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >

The textbox susbscribe to the propertychanged event of the datacontext object ( MyObjet in our example) and assumes it is fired when the source data has been updated

文本框订阅 datacontext 对象(在我们的示例中为 MyObjet)的 propertychanged 事件,并假设它在源数据更新时被触发

it automatically forces the refresh to the control

它会自动强制刷新控件

No need to call yourself the UpdateTarget method

无需调用自己的 UpdateTarget 方法