调用Items.Clear()触发OnCreateControl()递归调用致ComboBox重复填充?
问题:自定义ComboBox子类填充列表出现重复条目
我自定义了继承自ComboBox的类,遇到异常:下拉列表会被填充两次,最终列表长度是预期的两倍,包含重复条目(明明已经调用了Items.Clear())。
代码如下:
protected override void OnCreateControl() { base.OnCreateControl(); PopulatePayees(); } // 填充列表项 private void PopulatePayees() { Document document = Program.GetDocument(); Items.Clear(); foreach (ListItem listItem in document.Payees.GetListItems()) Items.Add(listItem); }
调试发现,执行PopulatePayees()里的Items.Clear()时,会再次触发OnCreateControl()方法,导致PopulatePayees()被第二次调用。如果注释掉Items.Clear(),列表只会填充一次。
想知道为什么执行Items.Clear()会触发OnCreateControl()的单次递归调用?
调用栈:
BankAccounts.dll!BankAccounts.Controls.PayeeComboBox.PopulatePayees() Line 25 C# BankAccounts.dll!BankAccounts.Controls.PayeeComboBox.OnCreateControl() Line 19 C# [External Code] BankAccounts.dll!BankAccounts.Controls.PayeeComboBox.PopulatePayees() Line 25 C# BankAccounts.dll!BankAccounts.Controls.PayeeComboBox.OnCreateControl() Line 19 C# [External Code] BankAccounts.dll!BankAccounts.MainForm.AddTransaction_Click(object sender, System.EventArgs e) Line 191 C# BankAccounts.dll!BankAccounts.MainForm.Transactions_AddTransaction(object sender, System.EventArgs e) Line 235 C# BankAccounts.dll!BankAccounts.Controls.TransactionListBox.AddTransactionMenu_Click(object sender, System.EventArgs e) Line 153 C# [External Code] BankAccounts.dll!BankAccounts.Program.Main() Line 16 C#
分析与解决方案
原因
这是WinForms控件的内部机制导致的:当ComboBox的Items集合被清空时,控件会触发状态变更或重绘操作,而此时如果控件还未完全完成创建流程(OnCreateControl执行过程中),就会再次触发OnCreateControl方法,形成单次递归调用。
解决办法
可以通过以下几种方式避免重复调用填充逻辑:
方法1:添加标志位控制
private bool _isPopulating = false; protected override void OnCreateControl() { base.OnCreateControl(); if (!_isPopulating) { _isPopulating = true; try { PopulatePayees(); } finally { _isPopulating = false; } } } private void PopulatePayees() { Document document = Program.GetDocument(); Items.Clear(); foreach (ListItem listItem in document.Payees.GetListItems()) Items.Add(listItem); }
方法2:判断控件创建状态
利用IsHandleCreated属性判断控件是否已完成底层句柄创建,避免在未完全初始化时执行操作:
protected override void OnCreateControl() { base.OnCreateControl(); if (IsHandleCreated) { PopulatePayees(); } } private void PopulatePayees() { Document document = Program.GetDocument(); Items.Clear(); foreach (ListItem listItem in document.Payees.GetListItems()) Items.Add(listItem); }
方法3:移至Load事件触发
将填充逻辑绑定到Load事件,该事件只会在控件完全创建完成后触发一次,不会出现递归问题:
public PayeeComboBox() { Load += PayeeComboBox_Load; } private void PayeeComboBox_Load(object sender, EventArgs e) { PopulatePayees(); } private void PopulatePayees() { Document document = Program.GetDocument(); Items.Clear(); foreach (ListItem listItem in document.Payees.GetListItems()) Items.Add(listItem); }
内容的提问来源于stack exchange,提问作者Jonathan Wood
相关产品推荐
相关产品推荐

