在C#WinForms应用程序中,DataGridView有一个非常烦人的问题。
我有一个名为 dgvInvoices 的DataGridView,它使用BindingSource bsInvoices 作为其数据源。
bsInvoices 的数据源类型为DTO,称为 InvoicesDTO 。
在加载DataGridView时,实体框架查询返回 InvoiceDTO 的集合,该集合用作 bsInvoices 的数据源,并因此传递给 > dgv发票。
一切正常。
问题源于以下事实,即DataGridView的每一行上都有一个复选框,即 Pay Invoice (付款发票),并且当单击该复选框时,需要进行一些操作(如果当前正在使用该发票,请警告用户暂停,更新该行的“应付金额”列,其他几位)。
所有事情都需要在复选框被打勾或未打勾时发生,并且我确定您都知道,默认情况下,DataGridView仅在离开单元格时触发“某些更改”事件,而不是在您单击单元格时触发复选框。
这当然很容易解决,您只需捕获 CellContentClick 事件,然后调用 CommitEdit 方法:-
private void dgvInvoices_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
dgvInvoices.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
乍看之下,效果很好。它会调用 dgvInvoices_CellValueChanged 方法,该方法会执行所有必需的检查(例如,如果发票被暂停,则提醒用户),如果一切都很好,它将设置 PayInvoice 属性。 InvoiceDTO就是该行的数据源。
private bool _payInvoice;
public bool PayInvoice
{
get { return _payInvoice; }
set
{
_payInvoice = value;
if (value)
{
InvoiceAmountToPay = InvoiceOutstanding;
}
else
{
InvoiceAmountToPay = 0;
}
}
}
如您所见, PayInvoice 属性将设置 InvoiceAmountToPay 属性,具体取决于它是否被打勾(值)。此更改将更新BindingSource bsInvoices ,并更新DataGridView dgvInvoices
同样,一切正常。
问题是由于某种原因,尽管专门说了“将当前单元格提交到数据高速缓存而没有结束编辑模式”,但是调用CommitEdit会导致其中的每一行中的每个单元格刷新DataGridView dgv发票。
这在小型数据集上并不明显,但是我们的一些客户拥有大型到大型数据集,刷新每个刻度上的每个单元格会导致几秒钟的延迟。当他们有很多发票要付款时,他们会发现这非常烦人。
如果我在 InvoicesDTO 中的每个 Get 和 Set 方法上都设置断点,则可以看到以下情况:-
1)我点击“ 支付发票”复选框
2)点击了 PayInvoice 的 Set 方法
3)点击了 InvoiceOutstanding 的 Get 方法
4)点击了 InvoiceAmountToPay 的 Set 方法
到目前为止一切顺利...
5)点击第一列DTO属性的 Get 方法
6)点击第二列DTO属性的 Get 方法
7)点击第三列DTO属性的 Get 方法
...对每一列重复...
18)命中最后一列的DTO属性的 Get 方法
19)点击第一列DTO属性的 Get 方法
20)点击第二列DTO属性的 Get 方法
21)点击第三列DTO属性的 Get 方法
...对每一列重复...
32)命中最后一列的DTO属性的 Get 方法
...重复每一行...
13526)命中了最后一行最后一列的DTO属性的 Get 方法
我尝试了几件事,但都无济于事:-
1)注释掉行
dgvInvoices.CommitEdit(DataGridViewDataErrorContexts.Commit);
这可以阻止我单击付款发票复选框时发生的各种情况,现在单击我的其他内容时它们就会发生。因此,如果我在第1行上单击支付发票,则什么也不会发生,那么我在第2行上单击支付发票,第1行将更新。当我单击第6行上的支付发票时,第2行将更新。等等。取消选中任何支付发票复选框也将相同,这将导致最后一次更改被注册,但是直到下次单击时才注册此更改。
这完全符合预期,以及为什么需要致电
dgvInvoices.CommitEdit(DataGridViewDataErrorContexts.Commit);
2)改用EndEdit
dgvInvoices.EndEdit();
与 CommitEdit 不同,这将结束编辑模式,但是由于我们只是勾选了一个非常合适的复选框。
不幸的是,与 CommitEdit 一样,这也会导致刷新每个单元格。
3)使用 CurrencyManager 阻止刷新发生
诚然,我实际上并不希望这能奏效,但认为值得一试...
CurrencyManager currencyManager = (CurrencyManager)BindingContext[dgvInvoices.DataSource];
currencyManager.SuspendBinding();
dgvInvoices.CommitEdit(DataGridViewDataErrorContexts.Commit);
currencyManager.ResumeBinding();
我希望显式调用 CommitEdit 会覆盖 SuspendBinding 阻止更改,但由于我怀疑没有,所以现在它忽略了对 Pay的更改发票。
因此,我回到了断点并进行了追溯,试图找出导致每个单元格刷新的确切原因。
我在DTO的 Get 方法之一上设置了一个断点,勾选了 Pay Invoice 复选框,并在点击 Get 后,我检查了堆栈跟踪 ...
1)获取方法
2)[外部代码]
3) dgvInvoices.CommitEdit
谢谢,堆栈跟踪!
浏览完工具/选项/调试/符号并加载所有符号(已选择“加载所有模块”)后,我再次尝试...
1)获取方法
2)[外部代码]
3) dgvInvoices.CommitEdit
太棒了!
但是,这次,我确实能够右键单击[外部代码],然后选择“加载外部代码”,这给了我...
DTO.InvoiceDTO.InvoiceAmountToPay.get() Line 460 C# Symbols loaded.
[Native to Managed Transition] Annotated Frame
[Managed to Native Transition] Annotated Frame
System.dll!System.SecurityUtils.MethodInfoInvoke(System.Reflection.MethodInfo method, object target, object[] args) Unknown Non-user code. Skipped loading symbols.
System.dll!System.ComponentModel.ReflectPropertyDescriptor.GetValue(object component) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.DataGridViewDataConnection.GetValue(int boundColumnIndex, int columnIndex, int rowIndex) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewCell.GetValue(int rowIndex) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewTextBoxCell.GetPreferredSize(System.Drawing.Graphics graphics, System.Windows.Forms.DataGridViewCellStyle cellStyle, int rowIndex, System.Drawing.Size constraintSize) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewCell.GetPreferredWidth(int rowIndex, int height) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewColumn.GetPreferredWidth(System.Windows.Forms.DataGridViewAutoSizeColumnMode autoSizeColumnMode, bool fixedHeight) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.AutoResizeColumnInternal(int columnIndex, System.Windows.Forms.DataGridViewAutoSizeColumnCriteriaInternal autoSizeColumnCriteriaInternal, bool fixedHeight) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnCellCommonChange(int columnIndex, int rowIndex) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.DataGridViewDataConnection.ProcessListChanged(System.ComponentModel.ListChangedEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.DataGridViewDataConnection.currencyManager_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.CurrencyManager.OnListChanged(System.ComponentModel.ListChangedEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.CurrencyManager.List_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.BindingSource.OnListChanged(System.ComponentModel.ListChangedEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.dll!System.ComponentModel.PropertyDescriptor.OnValueChanged(object component, System.EventArgs e) Unknown Non-user code. Skipped loading symbols.
System.dll!System.ComponentModel.ReflectPropertyDescriptor.SetValue(object component, object value) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.DataGridViewDataConnection.PushValue(int boundColumnIndex, int columnIndex, int rowIndex, object value) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewCell.SetValue(int rowIndex, object value) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.PushFormattedValue(ref System.Windows.Forms.DataGridViewCell dataGridViewCurrentCell, object formattedValue, out System.Exception exception) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.CommitEdit(ref System.Windows.Forms.DataGridViewCell dataGridViewCurrentCell, System.Windows.Forms.DataGridViewDataErrorContexts context, System.Windows.Forms.DataGridView.DataGridViewValidateCellInternal validateCell, bool fireCellLeave, bool fireCellEnter, bool fireRowLeave, bool fireRowEnter, bool fireLeave) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.CommitEdit(System.Windows.Forms.DataGridViewDataErrorContexts context) Unknown Non-user code. Skipped loading symbols.
dgvInvoices_CellContentClick(object sender, System.Windows.Forms.DataGridViewCellEventArgs e) Line 523 C# Symbols loaded.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnCellContentClick(System.Windows.Forms.DataGridViewCellEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnCommonCellContentClick(int columnIndex, int rowIndex, bool doubleClick) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewCell.OnMouseUpInternal(System.Windows.Forms.DataGridViewCellMouseEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnCellMouseUp(System.Windows.Forms.DataGridViewCellMouseEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnMouseUp(System.Windows.Forms.MouseEventArgs e) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button, int clicks) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) Unknown Non-user code. Skipped loading symbols.
[Native to Managed Transition] Annotated Frame
[Managed to Native Transition] Annotated Frame
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) Unknown Non-user code. Skipped loading symbols.
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) Unknown Non-user code. Skipped loading symbols.
Program.Main() Line 39 C# Symbols loaded.
此列表从顶部开始读取最新信息,因此您可以从底部开始阅读,就像我确定大家都知道的那样。
在我看来,关键部分是:-
1) DataGridView.CommitEdit
2) DataGridView.PushFormattedValue
3) DataGridViewCell.SetValue(int rowIndex,对象值)
4) DataGridView.DataGridViewDataConnection.PushValue(int boundColumnIndex,int columnIndex,int rowIndex,对象值)
5) ReflectPropertyDescriptor.SetValue(对象组件,对象值)
6) PropertyDescriptor.OnValueChanged(对象组件,System.EventArgs e)
7) BindingSource.OnListChanged
8) CurrencyManager.List_ListChanged
从7)和8)开始,似乎是在考虑整个列表已更改,而不仅仅是单个单元格发生了变化,我想这就是为什么每个单元格都得到刷新的原因。
因此,我尝试告诉BindingSource bsInvoices 不要更新:-
CurrencyManager currencyManager = (CurrencyManager)BindingContext[bsInvoice.DataSource];
currencyManager.SuspendBinding();
RPMdgvInvoices.CommitEdit(DataGridViewDataErrorContexts.Commit);
currencyManager.ResumeBinding();
这没有任何效果,每个单元格仍然得到刷新。
最后,我尝试完全绕开整个“提交” ...
private void RPMdgvInvoices_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
var payCell = ((DataGridViewCheckBoxCell)dgvInvoices.Rows[e.RowIndex].Cells[e.ColumnIndex]);
payCell.Value = !(bool)payCell.Value;
//dgvInvoices.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
由于尚未注册单击复选框的效果,它当前正在返回正确值的倒数,因此将其设置为当前值的倒数将正确设置它。
原本以为这是一个短暂而美妙的时刻,但是我为初始数据加载禁用了 Get 方法断点,却忘记了重新启用它们,这就是为什么它们没有启用的原因不被打ah!
回到堆栈跟踪中,我尝试捕获 BindingSource.OnListChanged 事件和 CurrencyManager.List_ListChanged 事件,如果它们是通过勾选< strong>支付发票,但都没有取消事件,而只是执行返回; 不会停止事件传播。
在暴露的堆栈跟踪中,没有任何有用的事件也发生了Cancel事件。
所以我尝试变得很有创意...可以从单独的线程调用的BindingSource! (SyncBindingSource在堆栈溢出的其他位置找到)
public class SyncBindingSource : BindingSource
{
private SynchronizationContext syncContext;
public SyncBindingSource()
{
syncContext = SynchronizationContext.Current;
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (syncContext != null)
{
syncContext.Send(_ => base.OnListChanged(e), null);
}
else
{
base.OnListChanged(e);
}
}
}
string selectedField = dgvInvoices.CurrentCell.OwningColumn.Name;
if (selectedField == "PayInvoice")
{
var payCell = ((DataGridViewCheckBoxCell)dgvInvoices
.Rows[e.RowIndex].Cells[e.ColumnIndex]);
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
payCell.Value = !(bool)payCell.Value;
}).Start();
}
好消息:它奏效了,我可以从线程中更新单元格!
坏消息:因为SyncBindingSource必须将OnListChanged发送回主线程,所以它没有停止延迟。
但是我现在可以访问OnListChanged了,所以我可以阻止它!
public class SyncBindingSource : BindingSource
{
public bool DontUpdateTheEntireTableWhenOneCellChanges = false;
private SynchronizationContext syncContext;
public SyncBindingSource()
{
syncContext = SynchronizationContext.Current;
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (DontUpdateTheEntireTableWhenOneCellChanges)
{
DontUpdateTheEntireTableWhenOneCellChanges = false;
return;
}
if (syncContext != null)
{
syncContext.Send(_ => base.OnListChanged(e), null);
}
else
{
base.OnListChanged(e);
}
}
}
private void dgvInvoices_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
//dgvInvoices.CommitEdit(DataGridViewDataErrorContexts.Commit);
string selectedField = RPMdgvInvoices.CurrentCell.OwningColumn.Name;
if (selectedField == "PayInvoice")
{
var payCell = ((DataGridViewCheckBoxCell)dgvInvoices
.Rows[e.RowIndex].Cells[e.ColumnIndex]);
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
bsInvoice.DontUpdateTheEntireTableWhenOneCellChanges = true;
payCell.Value = !(bool)payCell.Value;
}).Start();
}
}
好消息:这也起作用,我现在可以阻止OnListChanged!
坏消息:这阻止了我想要发生的更改以及所有我不想要的更改。
有人对我还能尝试什么有任何想法吗?