26

WPF アプリケーションで ValidationRules の使用を開始していますが、かなり混乱しています。

次の簡単なルールがあります。

class RequiredRule : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        if (String.IsNullOrWhiteSpace(value as string))
        {
            return new ValidationResult(false, "Must not be empty");
        }
        else
        {
            return new ValidationResult(true, null);
        }

    }
}

次のように XAML で使用されます。

<TextBox>
    <TextBox.Text>
        <Binding Path="Identity.Name">
            <Binding.ValidationRules>
                <validation:RequiredRule/>
            </Binding.ValidationRules>
         </Binding>
     </TextBox.Text>
</TextBox>

これはほとんど期待どおりに機能します。ソース プロパティ ( Identity.Name) が設定されていないことに驚きました。変更を認識しない元に戻す機能があり、再入力する以外に値を元に戻す方法はありません (良くありません)。

Microsoft のData Binding Overviewでは、下部にある検証プロセスについて説明しています。これは、この動作を非常によく説明しています。ValidationStepこれに基づいて、私は自分のセットを にしたいと思いUpdatedValueます。

<validation:RequiredRule ValidationStep="UpdatedValue"/>

これは私にとって物事が奇妙になるところです。設定されたプロパティ値 (つまり、文字列) であるオブジェクト値で Validate() が呼び出される代わりに、System.Windows.Data.BindingExpression! Microsoft のドキュメントには、この動作を説明するものは何もありません。

デバッガーで、ソース オブジェクト ( のDataContext)TextBoxを確認し、プロパティへのパスをナビゲートして、値が設定されていることを確認できます。ただし、検証ルール内で適切なプロパティに到達する良い方法がわかりません。

注: ValidationStepasConvertedProposedValueを使用すると、入力された文字列が取得されます (使用しているコンバーターはありません) が、期待どおり、検証が失敗したときにソース プロパティの更新もブロックされます。では、文字列の代わりにCommittedValueを取得します。BindingExpression

ここにはいくつかの質問があります:

  1. ValidationStep の設定に基づいて Validate() に渡される引数の型に一貫性がないのはなぜですか?

  2. BindingExpression から実際の値を取得するにはどうすればよいですか?

  3. または、ユーザーが TextBox を以前の (有効な) 状態に戻せるようにする良い方法はありますか? (私が述べたように、私自身の元に戻す機能は変更を認識しません。)

4

4 に答える 4

19

から値を抽出する問題を解決しましたBindingExpressionが、小さな制限があります。

最初に、より完全な XAML をいくつか示します。

<Window x:Class="ValidationRuleTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ValidationRuleTest"
        Title="MainWindow" Height="100" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="String 1"/>
        <TextBox Grid.Column="1">
            <TextBox.Text>
                <Binding Path="String1" UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <local:RequiredRule ValidationStep="RawProposedValue"/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
        <TextBlock Text="String 2" Grid.Row="1"/>
        <TextBox Grid.Column="1" Grid.Row="1">
            <TextBox.Text>
                <Binding Path="String2" UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <local:RequiredRule ValidationStep="UpdatedValue"/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
    </Grid>
</Window>

最初の TextBox はValidationStep="RawProposedValue"(デフォルト) を使用し、2 番目のTextBox は を使用しますValidationStep="UpdatedValue"が、どちらも同じ検証規則を使用することに注意してください。

単純な ViewModel (INPC やその他の便利なものは無視):

class MainWindowViewModel
{
    public string String1
    { get; set; }

    public string String2
    { get; set; }
}

そして最後に、新しい RequiredRule:

class RequiredRule : ValidationRule
{
    public override ValidationResult Validate(object value,
        System.Globalization.CultureInfo cultureInfo)
    {
        // Get and convert the value
        string stringValue = GetBoundValue(value) as string;

        // Specific ValidationRule implementation...
        if (String.IsNullOrWhiteSpace(stringValue))
        {
            return new ValidationResult(false, "Must not be empty"); 
        }
        else
        {
            return new ValidationResult(true, null); 
        }
    }

    private object GetBoundValue(object value)
    {
        if (value is BindingExpression)
        {
            // ValidationStep was UpdatedValue or CommittedValue (Validate after setting)
            // Need to pull the value out of the BindingExpression.
            BindingExpression binding = (BindingExpression)value;

            // Get the bound object and name of the property
            object dataItem = binding.DataItem;
            string propertyName = binding.ParentBinding.Path.Path;

            // Extract the value of the property.
            object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);

            // This is what we want.
            return propertyValue;
        }
        else
        {
            // ValidationStep was RawProposedValue or ConvertedProposedValue
            // The argument is already what we want!
            return value;
        }
    }
}

このGetBoundValue()メソッドは、BindingExpression を取得した場合は気になる値を掘り出し、そうでない場合は単に引数をキックバックします。本当の鍵は、「パス」を見つけ、それを使用してプロパティとその値を取得することでした。

制限: 私の最初の質問でPath="Identity.Name"は、ViewModel のサブオブジェクトを掘り下げていたので、バインディングに がありました。上記のコードは、パスがバインドされたオブジェクトのプロパティに直接あることを想定しているため、これは機能しません。幸いなことに、ViewModel は既にフラット化されているため、これはもはや当てはまりませんが、回避策として、最初にコントロールのデータ コンテキストをサブオブジェクトに設定することができます。

Eduardo Brites の功績を称えたいと思います。彼の回答と議論のおかげで、私は再びこの問題を掘り下げ、彼のパズルにピースを提供することができました。また、私は ValidationRules を完全に捨てて、代わりに IDataErrorInfo を使用しようとしていましたが、さまざまなタイプと複雑な検証のためにそれらを一緒に使用するという彼の提案が気に入っています。

于 2012-05-18T01:14:55.050 に答える
6

これは mbmcavoy's answerの拡張です。

GetBoundValueバインド パスの制限を取り除くために、メソッドを変更しました。BindingExpression には、ResolvedSource と ResolvedSourcePropertyName のプロパティがあります。これらはデバッガーで表示できますが、通常のコードからはアクセスできません。ただし、リフレクションを介してそれらを取得することは問題ありません。このソリューションは、どのバインディング パスでも機能するはずです。

private object GetBoundValue(object value)
{
    if (value is BindingExpression)
    {
        // ValidationStep was UpdatedValue or CommittedValue (validate after setting)
        // Need to pull the value out of the BindingExpression.
        BindingExpression binding = (BindingExpression)value;

        // Get the bound object and name of the property
        string resolvedPropertyName = binding.GetType().GetProperty("ResolvedSourcePropertyName", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null).ToString();
        object resolvedSource = binding.GetType().GetProperty("ResolvedSource", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null);

        // Extract the value of the property
        object propertyValue = resolvedSource.GetType().GetProperty(resolvedPropertyName).GetValue(resolvedSource, null);

        return propertyValue;
    }
    else
    {
        return value;
    }
}
于 2013-03-07T15:58:17.800 に答える
4

これは、mbmcavoy と adabyron の回答の代替拡張です。

バインド パスの制限を取り除くために、次のメソッドを使用してプロパティ値を取得します。

public static object GetPropertyValue(object obj, string propertyName)
{
    foreach (String part in propertyName.Split('.'))
    {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        PropertyInfo info = type.GetProperty(part);
        if (info == null) { return null; }

        obj = info.GetValue(obj, null);
    }

    return obj;
}

今すぐ変更するだけです

object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);

object propertyValue = GetPropertyValue(dataItem, propertyName);

関連記事: C# でリフレクションを使用して文字列からプロパティ値を取得する

于 2013-05-08T09:26:31.447 に答える
1

あなたの2つの質問に答えるために:

string strVal =(string)((BindingExpression)value).DataItem

于 2012-05-16T14:16:56.120 に答える