developer tip

WPF의 바인딩 된 컨트롤에 대한 강제 유효성 검사

optionbox 2021. 1. 6. 08:05
반응형

WPF의 바인딩 된 컨트롤에 대한 강제 유효성 검사


몇 개의 텍스트 상자가있는 WPF 대화 상자가 있습니다. 텍스트 상자는 내 비즈니스 개체에 바인딩되어 있으며 WPF 유효성 검사 규칙이 연결되어 있습니다.

문제는 사용자가 텍스트 상자에 실제로 데이터를 입력하지 않고도 '확인'버튼을 완벽하게 클릭하고 대화 상자를 닫을 수 있다는 것입니다. 사용자가 텍스트 상자에 정보를 입력하지 않았기 때문에 유효성 검사 규칙이 실행되지 않습니다.

유효성 검사를 강제하고 일부 유효성 검사 규칙이 위반되었는지 확인할 수 있습니까?

사용자가 대화 상자를 닫으려고 할 때이를 수행 할 수 있으며 유효성 검사 규칙이 위반 된 경우에는 수행하지 못하도록 할 수 있습니다.

감사합니다.


응용 프로그램에도이 문제가 있습니다. 유효성 검사는 바인딩이 업데이트 될 때만 발생하므로 직접 업데이트해야합니다. 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();
}

이렇게하면 오류 템플릿 (빨간색 윤곽선)이 나타나고 Validation.HasError 속성이 설정됩니다.이 속성은 확인 버튼을 트리거하여 비활성화합니다.

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

3.5SP1 / 3.0SP2에서는 ValidationRule베이스에 새 속성 인 ValidatesOnTargetUpdated = "True" 도 추가했습니다 . 그러면 대상 컨트롤이 업데이트 될 때만 아니라 소스 개체가 바인딩되는 즉시 유효성 검사가 호출됩니다. 정확히 원하는 것이 아닐 수도 있지만 처음에 수정해야 할 모든 항목을 보는 것은 나쁘지 않습니다.

다음과 같이 작동합니다.

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

다음은 "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);
}

위의 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();
    }
}        

그러나이 코드가 실행되기 전에 바인딩 된 컨트롤이 Visibles인지 확인하십시오!


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:

Xaml

<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

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.

Hope this is helpful to someone.


using the INotifyPropertychanged on your data object

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

it automatically forces the refresh to the control

No need to call yourself the UpdateTarget method

ReferenceURL : https://stackoverflow.com/questions/483419/force-validation-on-bound-controls-in-wpf

반응형