WPF Datagrid——如何强制提交行中的数据?
WPF Datagrid -- How to force commit of data in row?
当数据网格行失去焦点时如何强制提交而不是要求用户按 return 来提交?
我有一个 MVVM 项目,其中一个主 DataGrid
使用一个包含另一个子 DataGrid
的 RowDetailsTemplate
。它使用在行上按 'Enter' 的默认行为来提交值更改。我为主行和子行都实现了 ValidatonRule
。
子网格关闭
子网格打开
- 从 ComboBox 中选择一个帐户,用交易填充主网格。单击 [Sub] 按钮打开子网格并显示该交易的子交易。
- 如果用户在事务(主行)中输入值并按下'Enter',则事务值已成功提交;即,
Transaction ValdationStep.CommittedValue
被触发。
- 如果用户在主行中输入值(不按'Enter'),只需单击[Subs]按钮打开子网格,然后在子事务中输入值并按'Enter',子事务值提交成功;即,
SubtransactionValidation ValdationStep.CommittedValue
被触发。
- 但是 如果用户在没有按下主行上的 'Enter' 的情况下点击子网格,则交易中的值为 没有 承诺;即,
Transaction ValdationStep.CommittedValue
从未 触发。然后,当一个新帐户被 selected(这会导致 ItemsSource
集合发生变化)时,ItemsSource
会触发一个 CollectionChanged
事件以删除源项目(未提交的事务)就好像用户点击了 'Esc'.
我假设 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)
当数据网格行失去焦点时如何强制提交而不是要求用户按 return 来提交?
我有一个 MVVM 项目,其中一个主 DataGrid
使用一个包含另一个子 DataGrid
的 RowDetailsTemplate
。它使用在行上按 'Enter' 的默认行为来提交值更改。我为主行和子行都实现了 ValidatonRule
。
子网格关闭
子网格打开
- 从 ComboBox 中选择一个帐户,用交易填充主网格。单击 [Sub] 按钮打开子网格并显示该交易的子交易。
- 如果用户在事务(主行)中输入值并按下'Enter',则事务值已成功提交;即,
Transaction ValdationStep.CommittedValue
被触发。 - 如果用户在主行中输入值(不按'Enter'),只需单击[Subs]按钮打开子网格,然后在子事务中输入值并按'Enter',子事务值提交成功;即,
SubtransactionValidation ValdationStep.CommittedValue
被触发。 - 但是 如果用户在没有按下主行上的 'Enter' 的情况下点击子网格,则交易中的值为 没有 承诺;即,
Transaction ValdationStep.CommittedValue
从未 触发。然后,当一个新帐户被 selected(这会导致ItemsSource
集合发生变化)时,ItemsSource
会触发一个CollectionChanged
事件以删除源项目(未提交的事务)就好像用户点击了 'Esc'.
我假设 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)