关于c#:检查项目后哪个CheckedListBox事件触发?

Which CheckedListBox event triggers after a item is checked?

我有一个CheckedListBox,我想在检查项目后在其中想要一个事件,以便可以将CheckedItems与新状态一起使用。

由于在CheckedItems更新之前已触发ItemChecked,因此无法立即使用。

更新CheckedItems时,可以使用哪种方法或事件来通知?


如果还检查正在单击的项目的新状态,则可以使用ItemCheck事件。在事件args中可以使用e.NewValue的形式。如果选中了NewValue,请在逻辑中包括当前项目以及正确的集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    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)
        {
            ...
        }
    }

再举一个例子,要确定在(取消)检查此项目后该集合是否为空:

1
2
3
4
5
6
7
8
9
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
        ...
}


关于此,有很多相关的StackOverflow帖子...以及Branimir的解决方案,这里还有两个简单的解决方案:

在ItemCheck上延迟执行(也在此处):

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

使用MouseUp事件:

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

我倾向于第一种选择,因为第二种选择会导致误报(即触发次数过多)。


我尝试了一下,它起作用了:

1
2
3
4
5
6
7
8
9
10
11
12
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();        
}


CheckedListBox派生并实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/// <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);
}


经过一些测试,我可以看到事件ItemCheck之后触发了事件SelectedIndexChanged。保持属性CheckOnClick为True

最佳编码


尽管不理想,但是可以使用传递给ItemCheck事件的参数来计算CheckedItems。如果您在MSDN上查看此示例,则可以确定是否已选中或未选中新更改的项目,这使您处于适合处理这些项目的位置。

您甚至可以创建一个新事件,该事件在检查项目后触发,这将为您提供所需的确切信息。


这行得通,但不确定它有多优雅!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

假设您要保留ItemCheck中的参数,但是在更改模型后得到通知,则它应如下所示:

1
2
CheckedListBox ctrl = new CheckedListBox();
ctrl.ItemCheck += (s, e) => BeginInvoke((MethodInvoker)(() => CheckedItemsChanged(s, e)));

CheckedItemsChanged可能是:

1
2
3
4
private void CheckedItemsChanged(object sender, EventArgs e)
{
    DoYourThing();
}

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

我遇到了一些问题,这使我想到了这个职位。只是想分享我的做法,没有什么特别的。

注意:我有CheckOnClick = true,但如果没有

我使用的事件是" SelectedIndexChanged"

我使用的枚举是" .CheckedItems"

这给出了我认为可以预期的结果。如此简化,可以归结为...。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
        }
   }

}


我尝试了一下,它起作用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    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);
        }
    }

通过列表的索引


在正常情况下,当我们检查一项时,在引发事件处理程序之前,该项目的检查状态将更改。
但是CheckListBox具有不同的行为:在项目的检查状态更改之前引发事件处理程序,这使得更正我们的工作变得困难。

我认为,要解决此问题,我们应该推迟事件处理程序。

1
2
3
4
5
6
7
8
9
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());}

我使用计时器来解决此问题。通过ItemCheck事件启用计时器。在"计时器的滴答"事件中采取措施。

无论通过鼠标单击还是按下空格键检查项目,该方法均有效。我们将利用这样一个事实,即刚刚选中(或未选中)的项目始终是Selected Item。

计时器的间隔可以低至1。在Tick事件引发时,将设置新的Checked状态。

此VB.NET代码说明了这一概念。您可以采用多种变体。您可能希望增加计时器的间隔,以允许用户在执行操作之前更改多个项目的检查状态。然后在Tick事件中,顺序传递列表中的所有项目,或使用其CheckedItems集合采取适当的操作。

这就是为什么我们首先在ItemCheck事件中禁用Timer的原因。禁用然后启用将导致间隔时间重新开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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