当一个项目被选中后,哪个CheckedListBox事件会触发?

117

我有一个 CheckedListBox,希望在项目被勾选后触发事件以便使用新状态的CheckedItems

由于 ItemChecked 事件是在 CheckedItems 更新之前触发的,所以它默认情况下无法实现。

有哪种方法或事件可以让我在 CheckedItems 更新时得到通知?

18个回答

101

如果您还检查了被单击的项目的新状态,可以使用ItemCheck事件。 此项在事件参数中作为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
        ...
}

3
在第一个foreach循环中,我们可能需要添加一个if条件语句...if not item = checkedListBox1.Items[e.Index].ToString() - Raj
10
问题在于ItemCheck事件会在检查被处理之前触发。你的解决方案将涉及到保持自己的列表,基本上是复制标准代码。Dunc的第一个建议(ItemCheck上的延迟执行)是我认为对phq问题最干净的答案,因为它不需要任何额外的处理。 - Berend Engelbrecht
为了让它有用,需要将 checkedListBox1_ItemCheck 添加到 checkedListBox1.ItemCheck 中,例如: checkedListBox1.ItemCheck += checkedListBox1_ItemCheck; - Aipi

47

这方面在StackOverflow上有很多相关的帖子...除了Branimir的解决方案,还有另外两种更简单的解决方法:

ItemCheck延迟执行(也可以看看这里):

    void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
    {
        this.BeginInvoke((MethodInvoker) (
            () => Console.WriteLine(checkedListBox1.SelectedItems.Count)));
    }

使用 MouseUp 事件:

    void checkedListBox1_MouseUp(object sender, MouseEventArgs e)
    {
        Console.WriteLine(checkedListBox1.SelectedItems.Count);
    }

我更喜欢第一个选项,因为第二个选项会导致误报(即过于频繁地触发)。


15
第二种方法也可能无法检测到通过键盘勾选或取消勾选的项目。 - user565869
2
BeginInvoke正是我所需要的,因为我的事件实际上在调用一个接口,而该接口不知道它正在处理什么类型的控件。被接受的答案只适用于在事件处理程序内部执行逻辑或直接从事件处理程序调用某些内容的情况。但这对我来说并不适用。感谢这个简单而强大的解决方案。 - Jesse
谢谢,使用BeginInvoke的第一个选项对我有用。也许这是个愚蠢的评论..但为什么在2010年开始的主题中报告的这个BUG在2018年仍然没有解决呢? - Goodies
1
@Goodies 同意,虽然我猜如果微软现在更改行为,可能会破坏很多代码。 文档 明确说明 The check state is not updated until after the ItemCheck event occurs。在我的意见中,不同的事件或非武断的解决方法会更好。 - Dunc

27

我尝试过这个方法,它起作用了:

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();        
}

8
这!......应该是正确答案,这太不幸了。 这是一个荒谬的技巧,它能够运行是因为微软中某个人忘记实现“ItemChecked”事件,而且从来没有人解决过它不存在的问题。 - RLH
虽然这不是一个bug,但我认为应该实现它,如果您同意,请通过点击+1来支持这个错误报告:https://connect.microsoft.com/VisualStudio/feedback/details/1759293 - SCBuergel
@Sebastian - 不要在这里要求修复。任何对此的“修复”都会破坏现有的解决方案。如果有两个事件:ItemCheckingItemChecked,那么您可以使用后者。但是如果只实现了一个(ItemCheck),它正在正确地执行操作,即在使用新值和索引作为参数检查值之前触发事件。谁想要“更改后”事件,他们可以简单地使用上面的方法。如果向Microsoft建议什么,请建议一个新事件 ItemChecked,而不是更改现有事件:请参见diimdeep的答案 - miroxlav
就像这样,但我经常使用的一种略微不同的方法是设置某种“跳过”标志,以便SetItemCheckState不会重新触发相同的事件。 可以使用简单的全局变量,或者像我喜欢的一样,确保使用标签。例如,在If myCheckListBox.Tag!= null中包装操作,而不是在删除\添加事件中,只需将标签设置为某些内容(甚至是一个空字符串),然后再将其设回null即可重新启动。 - da_jokker

11

继承自 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解决方案时,您可以跳过很多额外的代码。 - Łukasƨ Fronczyk

5
虽然不是理想的解决方案,但你可以使用传递给ItemCheck事件的参数来计算CheckedItems。如果你查看MSDN上的示例,就可以确定新更改的项目是已选还是未选,这样你就可以处理这些项目了。
甚至可以创建一个在项目被选中后触发的新事件,这将完全符合你的需求。

1
你有没有关于如何创建这个新事件的具体想法?在ItemChecke事件之后,我怎样才能知道CheckedItems已经被更新了呢? - hultqvist

5

经过一些测试,我发现事件SelectedIndexChanged在事件ItemCheck之后触发。保持属性CheckOnClick为True。

最佳编码。


1
你说得对,这是最简单的方法。但这仍然像是一个hack,因为它是不受文档支持和意料之外的行为。微软的任何新手都可能会想:哦好吧,当只有Checkstate更改时为什么要触发SelectedIndexChanged。让我们优化一下。然后你的代码就崩了:( - Rolf
1
此外,当您以编程方式更改检查状态时,SelectedIndexChanged不会触发。 - Rolf
2
当您使用空格键更改检查状态时,它不会触发。这样做是错误的。 - Elmue

3

这个可以工作,但不确定它有多优雅!

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

3
我尝试过这个方法,它有效:

    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);
        }
    }

按列表的索引确定。


1
假设您想保留来自ItemCheck的参数,但在模型更改后得到通知,则应该如下所示:
CheckedListBox ctrl = new CheckedListBox();
ctrl.ItemCheck += (s, e) => BeginInvoke((MethodInvoker)(() => CheckedItemsChanged(s, e)));

其中CheckedItemsChanged可以是:

private void CheckedItemsChanged(object sender, EventArgs e)
{
    DoYourThing();
}

1

不知道是否适用,但我想使用一个 checklistbox 来过滤结果。当用户选中和取消选中项目时,我希望列表显示/隐藏项目。

我遇到了一些问题,导致我看到了这篇文章。只是想分享一下我如何在没有任何特殊工具的情况下完成它。

注意:我有 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
        }
   }

}

当用户使用空格键更改检查状态时,SelectedIndexChanged不会触发。 - Elmue
在代码中调用SetItemChecked来选中或取消选中一个项目时,SelectedIndexChanged不会触发。 - bkqc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接