【发布时间】:2010-09-08 10:18:13
【问题描述】:
我有一个 CheckedListBox,我想要一个事件在检查一个项目之后,以便我可以使用带有新状态的 CheckedItems。
由于 ItemChecked 在 CheckedItems 更新之前被触发,因此它不会开箱即用。
当 CheckedItems 更新时,我可以使用什么样的方法或事件来获得通知?
【问题讨论】:
标签: c# winforms checkedlistbox
我有一个 CheckedListBox,我想要一个事件在检查一个项目之后,以便我可以使用带有新状态的 CheckedItems。
由于 ItemChecked 在 CheckedItems 更新之前被触发,因此它不会开箱即用。
当 CheckedItems 更新时,我可以使用什么样的方法或事件来获得通知?
【问题讨论】:
标签: c# winforms checkedlistbox
您可以使用ItemCheck 事件,如果您还检查正在单击的项目的新状态。这在事件 args 中可用,为 e.NewValue。如果选中NewValue,则在您的逻辑中包含当前项目以及适当的集合:
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
List<string> checkedItems = new List<string>();
foreach (var item in checkedListBox1.CheckedItems)
checkedItems.Add(item.ToString());
if (e.NewValue == CheckState.Checked)
checkedItems.Add(checkedListBox1.Items[e.Index].ToString());
else
checkedItems.Remove(checkedListBox1.Items[e.Index].ToString());
foreach (string item in checkedItems)
{
...
}
}
再举一个例子,确定在(未)选中此项后集合是否为空:
private void ListProjects_ItemCheck(object sender, ItemCheckEventArgs args)
{
if (ListProjects.CheckedItems.Count == 1 && args.NewValue == CheckState.Unchecked)
// The collection is about to be emptied: there's just one item checked, and it's being unchecked at this moment
...
else
// The collection will not be empty once this click is handled
...
}
【讨论】:
if not item = checkedListBox1.Items[e.Index].ToString()
checkedListBox1_ItemCheck添加到checkedListBox1.ItemCheck,例如:checkedListBox1.ItemCheck += checkedListBox1_ItemCheck;
关于这个有很多相关的 StackOverflow 帖子...以及Branimir's 的解决方案,这里有两个更简单的:
Delayed execution on ItemCheck(也是here):
void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
this.BeginInvoke((MethodInvoker) (
() => Console.WriteLine(checkedListBox1.SelectedItems.Count)));
}
void checkedListBox1_MouseUp(object sender, MouseEventArgs e)
{
Console.WriteLine(checkedListBox1.SelectedItems.Count);
}
我更喜欢第一个选项,因为第二个会导致误报(即过于频繁地触发)。
【讨论】:
The check state is not updated until after the ItemCheck event occurs。一个不同的事件或非任意的解决方法将是很好的 IMO。
我试过了,效果很好:
private void clbOrg_ItemCheck(object sender, ItemCheckEventArgs e)
{
CheckedListBox clb = (CheckedListBox)sender;
// Switch off event handler
clb.ItemCheck -= clbOrg_ItemCheck;
clb.SetItemCheckState(e.Index, e.NewValue);
// Switch on event handler
clb.ItemCheck += clbOrg_ItemCheck;
// Now you can go further
CallExternalRoutine();
}
【讨论】:
ItemChecked 事件,并且没有人提到它不存在。
ItemChecking,ItemChecked,那么你可以使用后一个。但是,如果只实现了一个 (ItemCheck),它会正确地做事,即使用作为参数提供的新值和索引来检查触发事件 before 值。谁想要“更改后”事件,他们可以简单地使用上面的。如果向 Microsoft 提出建议,则建议 新事件 ItemChecked,而不是更改现有事件:请参阅 diimdeep's answer
从CheckedListBox 派生并实现
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.CheckedListBox.ItemCheck"/> event.
/// </summary>
/// <param name="ice">An <see cref="T:System.Windows.Forms.ItemCheckEventArgs"/> that contains the event data.
/// </param>
protected override void OnItemCheck(ItemCheckEventArgs e)
{
base.OnItemCheck(e);
EventHandler handler = AfterItemCheck;
if (handler != null)
{
Delegate[] invocationList = AfterItemCheck.GetInvocationList();
foreach (var receiver in invocationList)
{
AfterItemCheck -= (EventHandler) receiver;
}
SetItemCheckState(e.Index, e.NewValue);
foreach (var receiver in invocationList)
{
AfterItemCheck += (EventHandler) receiver;
}
}
OnAfterItemCheck(EventArgs.Empty);
}
public event EventHandler AfterItemCheck;
public void OnAfterItemCheck(EventArgs e)
{
EventHandler handler = AfterItemCheck;
if (handler != null)
handler(this, e);
}
【讨论】:
BeginInvoke 解决方案时,您可以跳过很多额外的代码。
虽然不理想,但您可以使用传递给ItemCheck 事件的参数来计算 CheckedItems。如果您查看此example on MSDN,您可以确定新更改的项目是已选中还是未选中,这使您处于处理这些项目的合适位置。
您甚至可以创建一个在检查项目后触发的新事件,如果您愿意,它会提供您想要的。
【讨论】:
经过一些测试,我可以看到 SelectedIndexChanged 事件是在 ItemCheck 事件之后触发的。保持属性 CheckOnClick True
最佳编码
【讨论】:
这可行,但不确定它有多优雅!
Private Sub chkFilters_Changed(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles chkFilters.ItemCheck
Static Updating As Boolean
If Updating Then Exit Sub
Updating = True
Dim cmbBox As CheckedListBox = sender
Dim Item As ItemCheckEventArgs = e
If Item.NewValue = CheckState.Checked Then
cmbBox.SetItemChecked(Item.Index, True)
Else
cmbBox.SetItemChecked(Item.Index, False)
End If
'Do something with the updated checked box
Call LoadListData(Me, False)
Updating = False
End Sub
【讨论】:
不知道这是否适用,但我想使用复选框来过滤结果。因此,当用户选中和未选中项目时,我希望列表显示\隐藏项目。
我遇到了一些问题,导致我发了这篇文章。只是想分享一下我是如何做到的,没有什么特别的。
注意:我有 CheckOnClick = true,但如果没有
,它可能仍然可以工作我使用的事件是“SelectedIndexChanged”
我使用的枚举是“.CheckedItems”
这给出了我认为我们可以预期的结果。如此简化,归结为 ....
private void clb1_SelectedIndexChanged(object sender, EventArgs e)
{
// This just spits out what is selected for testing
foreach (string strChoice in clb1.CheckedItems)
{
listBox1.Items.Add(strChoice);
}
//Something more like what I'm actually doing
foreach (object myRecord in myRecords)
{
if (clb1.CheckItems.Contains(myRecord["fieldname"])
{
//Display this record
}
}
}
【讨论】:
假设您想保留来自 ItemCheck 的参数,但在模型更改后收到通知,它应该如下所示:
CheckedListBox ctrl = new CheckedListBox();
ctrl.ItemCheck += (s, e) => BeginInvoke((MethodInvoker)(() => CheckedItemsChanged(s, e)));
CheckedItemsChanged 可能在哪里:
private void CheckedItemsChanged(object sender, EventArgs e)
{
DoYourThing();
}
【讨论】:
我试过了,效果很好:
private List<bool> m_list = new List<bool>();
private void Initialize()
{
for(int i=0; i < checkedListBox1.Items.Count; i++)
{
m_list.Add(false);
}
}
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (e.NewValue == CheckState.Checked)
{
m_list[e.Index] = true;
checkedListBox1.SetItemChecked(e.Index, true);
}
else
{
m_list[e.Index] = false;
checkedListBox1.SetItemChecked(e.Index, false);
}
}
根据列表的索引确定。
【讨论】:
你的意思是checkboxlist,而不是checkedlistbox?如果是这样,则相关事件将是 SelectedIndexChanged。
例如VB中的处理程序定义头:
Protected Sub cblStores_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cblStores.SelectedIndexChanged
【讨论】:
我决定不关心控件的感受,并像复选框更改确实已经完成一样处理事件。您需要做的就是获取CheckedIndices 列表,并使用ItemCheckEventArgs 对象中的信息将其调整为新状态。
然后您可以遍历该列表并从控件的Items 属性中检索指示的项目,并且您有您的CheckedItems 列表。
private void CheckedList_ItemCheck(Object sender, ItemCheckEventArgs e)
{
CheckedListBox checkedList = sender as CheckedListBox;
if (checkedList == null)
return;
// Somehow this still returns the state before the check, so update it manually.
List<Int32> checkedIndices = checkedList.CheckedIndices.Cast<Int32>().ToList();
if (e.NewValue == CheckState.Unchecked)
checkedIndices.Remove(e.Index);
else if (e.NewValue == CheckState.Checked)
checkedIndices.Add(e.Index);
checkedIndices.Sort()
Int32 checkedItemCount = checkedIndices.Length;
Object[] checkedItems = new Object[checkedItemCount]
for (Int32 i = 0; i < checkedItemCount; i++)
checkedItems[i] = checkedList.Items[checkedIndices[i]];
this.UpdateAfterCheckChange(checkedItems);
}
结果在功能上与假设的期望情况相同,即事件仅在更改后触发。
【讨论】:
VB.NET 版本的 Dunc's answer 到 BeginInvoke 的处理程序,以便检查项目。
Private Sub ChkListBox1_ItemCheck(sender As Object, e As ItemCheckEventArgs) Handles ChkListBox1.ItemCheck
Debug.WriteLine($"checked item count {ChkListBox1.CheckedItems.Count}")
Debug.WriteLine($"{ChkListBox1.Items(e.Index)} - {e.Index} - {e.NewValue}")
BeginInvoke(Sub() HandleItemCheck(e))
End Sub
Private Sub HandleItemCheck(e As ItemCheckEventArgs)
Debug.WriteLine($"handle item {ChkListBox1.Items(e.Index)} - {e.Index} - {e.NewValue}")
Debug.WriteLine($"checked item count handle item - {ChkListBox1.CheckedItems.Count}")
End Sub
【讨论】:
如果您像我一样尝试将 Selection 用作一个指示符(用户选择的项目),并且用户想要更改刻度,那么我找到了一个偷偷摸摸的解决方案。
Form variables
Private IsTicked As Boolean = False
Private ListIndex = -1
在页面上创建一个计时器。例如,我的叫tmrBan,而我的CheckBoxList 叫clbFTI。
然后,为您的CheckBoxList 创建一个点击事件。
Private Sub clbFTI_Click(sender As Object, e As EventArgs) Handles lbFTI.MouseClick
ListIndex = sender.SelectedIndex
IsTicked = clbFTI.SelectedIndices.Contains(ListIndex)
tmrBan.Interval = 10
tmrBan.Enabled = True
End Sub
然后,为您的计时器创建一个 Tick 事件
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles tmrBan.Tick
clbFTI.SetItemChecked(ListIndex, IsTicked)
End Sub
您会看到滴答声的闪烁,但请尝试使用计时器间隔以使其更适合您的情况。
【讨论】:
我使用定时器来解决这个问题。通过 ItemCheck 事件启用计时器。在 Timer's Tick 事件中采取行动。
无论是通过鼠标单击还是按空格键检查项目,这都有效。我们将利用刚刚选中(或未选中)的项目始终是选定项目这一事实。
Timer 的 Interval 可以低至 1。在引发 Tick 事件时,将设置新的 Checked 状态。
这段 VB.NET 代码展示了这个概念。您可以使用许多变体。您可能希望增加计时器的间隔,以允许用户在采取行动之前更改多个项目的检查状态。然后在 Tick 事件中,依次传递 List 中的所有项目或使用其 CheckedItems 集合采取适当的操作。
这就是我们首先在 ItemCheck 事件中禁用 Timer 的原因。禁用然后启用会导致间隔周期重新开始。
Private Sub ckl_ItemCheck(ByVal sender As Object, _
ByVal e As System.Windows.Forms.ItemCheckEventArgs) _
Handles ckl.ItemCheck
tmr.Enabled = False
tmr.Enabled = True
End Sub
Private Sub tmr_Tick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles tmr.Tick
tmr.Enabled = False
Debug.Write(ckl.SelectedIndex)
Debug.Write(": ")
Debug.WriteLine(ckl.GetItemChecked(ckl.SelectedIndex).ToString)
End Sub
【讨论】:
在正常行为中,当我们检查一个项目时,该项目的检查状态将在事件处理程序被引发之前改变。 但是 CheckListBox 具有不同的行为:事件处理程序在项目的检查状态更改之前引发,这使得我们很难纠正我们的工作。
在我看来,要解决这个问题,我们应该推迟事件处理程序。
private void _clb_ItemCheck(object sender, ItemCheckEventArgs e) {
// Defer event handler execution
Task.Factory.StartNew(() => {
Thread.Sleep(1000);
// Do your job at here
})
.ContinueWith(t => {
// Then update GUI at here
},TaskScheduler.FromCurrentSynchronizationContext());}
【讨论】: