首页 > 解决方案 > WPF:当 DataGridComboBox SelectedValueBinding 更改时,已触发 ValidationRule 但 IsDirty == false

问题描述

我的 XAML 显示绑定到配置的ObservableCollectionDataGrid RowValidationRule。每当更改 a 中的值时,DataGridTextColumn就会触发 ValidationRule 并且IsDirty == true. 但是,对于一个DataGridComboBoxColumnValidationRule 被触发,但IsDirty == false即使该SelectedValueBinding属性实际上已被更新。我不明白为什么或应该做些什么不同的DataGridComboBoxColumn条目才能正确导致IsDirty == true.

更新

我通过运行ValidationRule所有 ValidationSteps 发现 Payees ComboBox 的值已经ValidationStep.RawProposedValue. 这与在 之前不出现更改的所有其他列不同ValidationStep.UpdatedValue。即使是状态组合框(对枚举起作用)也不会更改,直到ValidationStep.UpdatedValue第一步时 Payees 值已经存在。也许它假设 ComboBox 本身应该已经验证了它?

XAML

<Window.Resources>
    <ObjectDataProvider x:Key="statusEnum" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="data:TransactionStatus" />
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
    <CollectionViewSource x:Key="PayeesView" Source="{Binding Payees}" />
    <convert:BankTransactionConverter x:Key="TransConverter" />
</Window.Resources>
<Grid>
    ...
    <DataGrid x:Name="BankDataGrid" Grid.Row="2"
            ItemsSource="{Binding BankTransactions}"
            SelectedItem="{Binding SelectedBankTransaction, Converter={StaticResource TransConverter}}"
        <DataGrid.RowValidationRules>
            <valid:BankTransactionValidationRule ValidationStep="UpdatedValue"/>
        </DataGrid.RowValidationRules>
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="BankTransReference" Header="Ref" Width="70"
                                    Binding="{Binding Reference}" />
                <DataGridTemplateColumn x:Name="BankTransDate" Header="Date" Width="100" SortMemberPath="Date" >
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Date, StringFormat=\{0:MM/dd/yyyy\}}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding Date}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>

                <DataGridComboBoxColumn x:Name="BankTransPayee" Header="Payee"
                        ItemsSource="{Binding Source={StaticResource PayeesView}}" DisplayMemberPath="Name" 
                        SelectedValueBinding="{Binding PayeeName, UpdateSourceTrigger=LostFocus}" SelectedValuePath="Name" >
                    <DataGridComboBoxColumn.EditingElementStyle>
                        <Style TargetType="ComboBox">
                            <Setter Property="IsEditable" Value="True" />
                        </Style>
                    </DataGridComboBoxColumn.EditingElementStyle>
                </DataGridComboBoxColumn>

                <DataGridTextColumn x:Name="BankTransAmount" Header="Amount" IsReadOnly="True" Width="100" 
                                    Binding="{Binding Amount, StringFormat=\{0:N2\}}" >
                    <DataGridTextColumn.CellStyle>
                        <Style>
                            <Setter Property="TextBlock.TextAlignment" Value="Right" />
                        </Style>
                    </DataGridTextColumn.CellStyle>
                </DataGridTextColumn>

                <DataGridComboBoxColumn x:Name="BankTransStatus" Header="?" Width="18" 
                                        ItemsSource="{Binding Source={StaticResource statusEnum}, Mode=OneWay}" 
                                        SelectedItemBinding="{Binding Status}" />

                <DataGridTextColumn x:Name="BankTransMemo" Header="Memo" Width="220" 
                                    Binding="{Binding Memo}" />
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Click="Button_ShowHideSub">Subs</Button>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
    </DataGrid>
</Grid>

验证规则

public class BankTransactionValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var bg = (BindingGroup)value;
        var row = (DataGridRow)bg.Owner;
        var item = (BankTransaction)row.Item;

        switch (ValidationStep)
        {
            case ValidationStep.RawProposedValue:
                System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: ValidationStep={ValidationStep.RawProposedValue}");
                ShowInfo(item);
                break;
            case ValidationStep.ConvertedProposedValue:
                System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: ValidationStep={ValidationStep.ConvertedProposedValue}");
                ShowInfo(item);
                break;
            case ValidationStep.UpdatedValue:
                System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: ValidationStep={ValidationStep.UpdatedValue}");
                ShowInfo(item);
                break;
            case ValidationStep.CommittedValue:
                System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: ValidationStep={ValidationStep.CommittedValue}");
                ShowInfo(item);
                break;
            default:    // Should not get here
                System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: DEFAULT\n");
                break;
        }

        if (bg.IsDirty)
        {
            System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: {item.TransactionId} is dirty\n");
        }
        else
        {
            System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: {item.TransactionId} is NOT dirty\n");
        }

        return ValidationResult.ValidResult;
    }

    private void ShowInfo(BankTransaction trans)
    {
        System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: Reference={trans.Reference}");
        System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: Date={trans.Date}");
        System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: PayeeName={trans.PayeeName}");
        System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: Status={trans.Status}");
        System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionTestValidationRule)}: Memo={trans.Memo}");
    }
}

输出

BankTransactionTestValidationRule: ValidationStep=RawProposedValue
>> BankTransactionTestValidationRule: Reference=
BankTransactionTestValidationRule: Date=12/24/2019 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=Amazon
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=ConvertedProposedValue
>> BankTransactionTestValidationRule: Reference=
BankTransactionTestValidationRule: Date=12/24/2019 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=Amazon
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=UpdatedValue
>> BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=12/24/2019 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=Amazon
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=CommittedValue
>> BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=12/24/2019 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=Amazon
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=RawProposedValue
BankTransactionTestValidationRule: Reference=2nd row
>> BankTransactionTestValidationRule: Date=12/24/2019 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=Amazon
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=ConvertedProposedValue
BankTransactionTestValidationRule: Reference=2nd row
>> BankTransactionTestValidationRule: Date=12/24/2019 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=Amazon
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=UpdatedValue
BankTransactionTestValidationRule: Reference=2nd row
>> BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=Amazon
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=CommittedValue
BankTransactionTestValidationRule: Reference=2nd row
>> BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=Amazon
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=RawProposedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
>> BankTransactionTestValidationRule: PayeeName=PenFed
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is NOT dirty

BankTransactionTestValidationRule: ValidationStep=ConvertedProposedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
>> BankTransactionTestValidationRule: PayeeName=PenFed
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is NOT dirty

BankTransactionTestValidationRule: ValidationStep=UpdatedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
>> BankTransactionTestValidationRule: PayeeName=PenFed
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is NOT dirty

BankTransactionTestValidationRule: ValidationStep=CommittedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
>> BankTransactionTestValidationRule: PayeeName=PenFed
BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is NOT dirty

BankTransactionTestValidationRule: ValidationStep=RawProposedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=PenFed
>> BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=ConvertedProposedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=PenFed
>> BankTransactionTestValidationRule: Status=N
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=UpdatedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=PenFed
>> BankTransactionTestValidationRule: Status=V
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is NOT dirty

BankTransactionTestValidationRule: ValidationStep=CommittedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=PenFed
>> BankTransactionTestValidationRule: Status=V
BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is NOT dirty

BankTransactionTestValidationRule: ValidationStep=RawProposedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=PenFed
BankTransactionTestValidationRule: Status=V
>> BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=ConvertedProposedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=PenFed
BankTransactionTestValidationRule: Status=V
>> BankTransactionTestValidationRule: Memo=
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=UpdatedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=PenFed
BankTransactionTestValidationRule: Status=V
>> BankTransactionTestValidationRule: Memo=give me some memo
BankTransactionTestValidationRule: 2 is dirty

BankTransactionTestValidationRule: ValidationStep=CommittedValue
BankTransactionTestValidationRule: Reference=2nd row
BankTransactionTestValidationRule: Date=10/6/2020 12:00:00 AM
BankTransactionTestValidationRule: PayeeName=PenFed
BankTransactionTestValidationRule: Status=V
>> BankTransactionTestValidationRule: Memo=give me some memo
BankTransactionTestValidationRule: 2 is dirty

标签: c#wpfvalidationdatagrid

解决方案


[我将其发布为答案只是因为它提供了所需的行为。但是,我仍然希望其他人有正确的答案。]

我最终添加了一个隐藏列,该列绑定DataGrid到. 此属性在 内更新,方法是将其设置为。我还添加了一个属性(带有私有集),当所有其他属性更改时,除了绑定到. 见下文...HiddenPayeeItemSourceValidationRulePayeeNameIsDirtytruePayeeNameDataGridComboBox

XAML 更改

<DataGrid.RowValidationRules>
    <valid:BankTransactionValidationRule ValidationStep="UpdatedValue" />
</DataGrid.RowValidationRules>
<DataGrid.Columns>
    ...
    <DataGridComboBoxColumn x:Name="BankTransPayee" Header="Payee" 
            ItemsSource="{Binding Source={StaticResource PayeesView}}" DisplayMemberPath="Name" 
            SelectedValueBinding="{Binding PayeeName}" SelectedValuePath="Name" >
        <DataGridComboBoxColumn.EditingElementStyle>
            <Style TargetType="ComboBox">
                <Setter Property="IsEditable" Value="True" />
            </Style>
        </DataGridComboBoxColumn.EditingElementStyle>
    </DataGridComboBoxColumn>

    <DataGridTextColumn x:Name="BankTransHiddenPayee" Binding="{Binding HiddenPayee}" Visibility="Hidden" />
    ...
</DataGrid.Columns>

银行交易变更

public class BankTransaction : INotifyPropertyChanged, ICloneable
{
    private bool loading = false;

    // Default constructor is required by DataGrid in order to add new empty row
    public BankTransaction()
    {
        IsDirty = false;
        Subtransactions.CollectionChanged += Subtransactions_CollectionChanged;
    }

    public BankTransaction(int accountId, int transactionId, DateTime date, string memo, TransactionStatus status, string payee, string reference)
        : this()
    {
        loading = true;

        // For reading from the database
        AccountId = accountId;
        TransactionId = transactionId;
        Date = date;
        Memo = memo;
        Status = status;
        PayeeName = payee;
        HiddenPayee = payee;
        Reference = reference;

        loading = false;
    }

    public bool IsDirty { get; private set; }

    private string hiddenPayee;
    public string HiddenPayee
    {
        get => hiddenPayee;
        set
        {
            if (hiddenPayee != value)
            {
                hiddenPayee = value;
                NotifyPropertyChanged();
                IsDirty = !loading;
            }
        }
    }
    private string payeeName = "";
    public string PayeeName
    {
        get => payeeName;
        set
        {
            payeeName = value;
            NotifyPropertyChanged();
            // Don't set IsDirty here, it is handled
            // in HiddenPayee
        }
    }
    private string reference = "";
    public string Reference
    {
        get => reference;
        set
        {
            reference = value;
            NotifyPropertyChanged();
            IsDirty = !loading;
        }
    }
    ...
}

验证规则更改

public class BankTransactionValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var bg = (BindingGroup)value;
        var row = (DataGridRow)bg.Owner;
        var item = (BankTransaction)row.Item;

        // A change in a DataGridComboBoxColumn immediately changes the value of the SelectedValue (even at RawProposedValue)
        // so use hidden properties that are set here and evaluated in the view model class to set its IsDirty property.
        item.HiddenPayee = item.PayeeName;

        if (item.IsDirty)
        {
            System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionValidationRule)}: {item.TransactionId} is dirty");

            if (row.IsNewItem)
            {
                item.Insert();
            }
            else
            {
                item.Update();
            }
        }
        else
        {
            System.Diagnostics.Debug.WriteLine($"{nameof(BankTransactionValidationRule)}: {item.TransactionId} is NOT dirty");
        }

        return ValidationResult.ValidResult;
    }
}

推荐阅读