WPF Datagrid——如何强制提交行中的数据?

WPF Datagrid -- How to force commit of data in row?

当数据网格行失去焦点时如何强制提交而不是要求用户按 return 来提交?

我有一个 MVVM 项目,其中一个主 DataGrid 使用一个包含另一个子 DataGridRowDetailsTemplate。它使用在行上按 'Enter' 的默认行为来提交值更改。我为主行和子行都实现了 ValidatonRule

子网格关闭

子网格打开

我假设 CollectionChanged 事件是由 DataGrid 决定该行尚未提交并因此将其从集合中删除。

SO: Cancel collection changed event on an observable collection 可能是取消 CollectionChanged 和删除项目的一种方法,但似乎更需要强制提交更改。另外,我必须弄清楚什么时候 enable/disable 什么时候忽略事件。

我认为这个 可能是一个解决方案(虽然有点矫枉过正)并在按钮代码中实现了它(见下文)但它仍然触发了 CollectionChanged 事件来删除项目事件,当我select 不同的帐户和 ItemsSource 集合已更改。

private void ToggleSubs_Click(object sender, RoutedEventArgs e)
{
    ...
    foreach (var item in BankDataGrid.ItemContainerGenerator.Items)
    {
        var container = BankDataGrid.ItemContainerGenerator.ContainerFromItem(item);
        if (container != null && container is DataGridRow dgr)
        {
            dgr.BindingGroup.CommitEdit();
        }
    }
}

然后我尝试捕获 ValidationStep.ConvertedProposedValue(我发现它是在单击 [Subs] 按钮后触发的)并在该行上强制 BindingGroup.CommitEdit()。但是当我 select 编辑另一个帐户时,再次触发 CollectionChanged 事件以删除该项目。

XAML

<Grid>
    <Grid x:Name="MainPanel">
        <Grid.RowDefinitions>
            <RowDefinition Height="26" />
            <RowDefinition Height="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        ...
        <Grid x:Name="Controls" Grid.Row="1" >
            ...
            <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Left">
                <Label Content="Account" VerticalAlignment="Center" />
                <ComboBox Name="ControlsAccount" VerticalAlignment="Center" Width="210"
                          ItemsSource="{Binding AccountList}" 
                          SelectedItem="{Binding SelectedAccount}"
                          IsEnabled="{Binding HasAccounts}" >
                    <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Name}" 
                                Foreground="{Binding ItemForeColor}" 
                                Background="{Binding ItemBackColor}" />
                        </DataTemplate>
                    </ComboBox.ItemTemplate>
                </ComboBox>
            </StackPanel>
            ...
        </Grid>
        <DataGrid x:Name="BankDataGrid" Grid.Row="2"
            ItemsSource="{Binding SelectedAccount.Transactions, UpdateSourceTrigger=LostFocus}"
            SelectedItem="{Binding SelectedAccount.SelectedTransaction, Converter={StaticResource TransConverter}}" >
            ...
            <DataGrid.RowValidationRules>
                <valid:BankTransactionValidation ValidationStep="UpdatedValue" />
                <valid:BankTransactionValidation ValidationStep="CommittedValue" />
            </DataGrid.RowValidationRules>
            ...
            <DataGrid.Columns>
                ...
            </DataGrid.Columns>

            <DataGrid.RowDetailsTemplate>
                <DataTemplate x:Name="SubDataTemplate">
                    <DataGrid x:Name="SubDataGrid"
                        ItemsSource="{Binding Subtransactions}"
                        SelectedItem="{Binding SelectedSubtransaction, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource SubtransConverter}}" >
                        ...
                        <DataGrid.RowValidationRules>
                            <valid:SubtransactionValidation ValidationStep="UpdatedValue" />
                            <valid:SubtransactionValidation ValidationStep="CommittedValue" />
                        </DataGrid.RowValidationRules>
                        ...
                        <DataGrid.Columns>
                            ...
                        </DataGrid.Columns>
                    </DataGrid>
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>
        </DataGrid>
    </Grid>
    ...
</Grid>

主虚拟机

partial class MainVM : BaseVM
{
    public MainVM()
    {
        LoadSettings();

        InitMainPanel();
        InitTransactionSearch();
        InitLookupMaintenance();
        InitLookupReplacement();
        InitAccountDetails();
        InitBusyPanel();
    }
    ...
    private ObservableCollection<AccountVM> accountList;
    public ObservableCollection<AccountVM> AccountList
    {
        get => accountList;
        set
        {
            accountList = value;
            NotifyPropertyChanged();
        }
    }
    private AccountVM selectedAccount;
    public AccountVM SelectedAccount
    {
        get => selectedAccount;
        set
        {
            selectedAccount = value;
            NotifyPropertyChanged();
            if (selectedAccount != null)
            {
                WindowTitle = $"{selectedAccount.Name} - {AppName}";
                BankDataGridVisibility = VISIBILITY_SHOW;
                BackgroundImageVisibility = VISIBILITY_HIDE;
                SubtransactionsVisibility = VISIBILITY_COLLAPSE;
                SubtransactionVM.XferAccountRemove(selectedAccount.Name, AccountList);
            }
            else
            {
                HideTransactions();
            }
            HasAccounts = (AccountList != null && AccountList.Count > 0) ? LITERAL_TRUE : LITERAL_FALSE;
        }
    }
    ...
}

交易集合的 AccountVM CollectionChanged 处理程序

public class AccountVM : BaseVM
{
    private ObservableCollection<BankTransactionVM> transactions;
    public ObservableCollection<BankTransactionVM> Transactions
    {
        get
        {
            if (transactions == null)
            {
                ReadTransactions();
            }
            return transactions;
        }
    }
    private BankTransactionVM selectedTransaction;
    public BankTransactionVM SelectedTransaction
    {
        get => selectedTransaction;
        set
        {
            selectedTransaction = value;
            NotifyPropertyChanged();
        }
    }
    ...
    private void BankTransactions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        var action = e.Action;
        if (action == NotifyCollectionChangedAction.Remove)
        {
            foreach (BankTransactionVM item in e.OldItems)
            {
                // Notifiy parent of change for properties dependent upon collection items
                Transaction_PropertyChanged(item, new PropertyChangedEventArgs(nameof(TransactionsCount)));
                Transaction_PropertyChanged(item, new PropertyChangedEventArgs(nameof(TransactionsTotal)));

                // Remove the item
                item.Delete();
            }
        }
        else if (action == NotifyCollectionChangedAction.Add)
        {
            foreach (BankTransactionVM item in e.NewItems)
            {
                // Add the item
                item.AccountId = AccountId;
                item.Insert();

                // Add the event handlers
                item.PropertyChanged += new PropertyChangedEventHandler(Transaction_PropertyChanged);
                item.TransferCreated += new TransferCreatedEventHandler(Transaction_TransferCreated);
                item.TransferDeleted += new TransferDeletedEventHandler(Transaction_TransferDeleted);

                // Notifiy parent of change for properties dependent upon collection items
                Transaction_PropertyChanged(item, new PropertyChangedEventArgs(nameof(TransactionsCount)));
                Transaction_PropertyChanged(item, new PropertyChangedEventArgs(nameof(TransactionsTotal)));
            }
        }
    }
}

银行交易验证

public class BankTransactionValidation : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var result = ValidationResult.ValidResult;

        var bg = (BindingGroup)value;
        var row = (DataGridRow)bg.Owner;
        if (row.Item is BankTransactionVM item)
        {
            switch (ValidationStep)
            {
                case ValidationStep.RawProposedValue:
                    break;
                case ValidationStep.ConvertedProposedValue:
                    break;
                case ValidationStep.UpdatedValue:
                    break;
                case ValidationStep.CommittedValue:
                    if (item.TransactionId > 0)
                    {
                        item.Update();
                        if (item.Subtransactions.Count == 0)
                        {
                            item.Subtransactions.Add(new SubtransactionVM(item.TransactionId));
                        }
                    }
                    break;
            }
        }

        return result;
    }
}

我发现密钥使用的是 DataGrid.CommitEdit() 而不是 BindingGroup.CommitEdit()。通过在 ToggleSubs_Click() 方法中使用以下行,在读取子网格的数据之前,它会强制执行与用户在该行上按 [Enter] 相同的顺序。

DataGrid.CommitEdit(DataGridEditingUnit.Row, true)