No ItemChecked event in a CheckedListBox?
Asked Answered
F

4

30

The ListView control has an ItemCheck event which is fired before the item changes, and an ItemChecked event that is fired after the item changes. See this SO question for more detail.

The CheckedListBox control only has the ItemCheck event.

What is the best way to do something like this with a CheckedListBox?

private void checkedListBox_ItemChecked(object sender ItemCheckedEventArgs e)
{
    okButton.Enabled = (checkedListBox.CheckedItems.Count > 0);
}

Supplemental question: Why is there no CheckedListBox.ItemChecked event?

Fractionate answered 15/12, 2010 at 19:36 Comment(1)
possible duplicate of Which CheckedListBox event triggers after a item is checked?Snead
K
65

A nice trick to deal with events that you cannot process when they are raised is to delay the processing. Which you can do with the Control.BeginInvoke() method, it runs as soon as all events are dispatched, side-effects are complete and the UI thread goes idle again. Often helpful for TreeView as well, another cranky control.

    private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e) {
        this.BeginInvoke((MethodInvoker)delegate { 
            okButton.Enabled = checkedListBox1.CheckedItems.Count > 0;
        });
    }

Just in case: this has nothing to do with threading and the trick is quite cheap.

Why no ItemChecked event? Not really sure. CheckedListBox just isn't a very good control. Definitely not done by one of the gurus in the original Winforms team.

Kaffiyeh answered 15/12, 2010 at 20:34 Comment(5)
One small snag. "BeginInvoke cannot be called on a control until the window handle has been created", and I was setting state via checkedListBox1.SetItemChecked() before showing the form, which throws an exception.Fractionate
Rats, you found a loophole. Test this.IsHandleCreated or move the code to the Load event or don't subscribe the event until you've initialized the items. I'd favor the Load event (OnLoad, actually).Kaffiyeh
Very nice solution; I have no idea why this event doesn't behave like this by default; very much an oversight on Microsoft's side.Greta
Trapped every time, and falling on this answer every time. I wish I could upvote it every time.Boche
BeginInvoke will throw InvalidOperationException if the Form is not rendered yet. In my case, luckily, invoking the delegate was not necessary, so I caught and ignored the exception.Smoke
L
4
    private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e) {
        int count = this.checkedListBox1.CheckedItems.Count;
        if (e.CurrentValue != CheckState.Checked && e.NewValue == CheckState.Checked) {
            count += 1;
        } else if (e.CurrentValue == CheckState.Checked && e.NewValue != CheckState.Checked) {
            count -= 1;
        }
        this.okButton.Enabled = count > 0;
    }
Lysol answered 15/12, 2010 at 20:0 Comment(0)
E
2

Based on Hans Passant's answer I am adding a generic VB.NET version. I needed one method which would be called for all CheckedListBox controls on the form. You can easily tweak this if you need separate methods for each control (adds some redundancy though).

Public Class Form1
    Delegate Sub ProcessItemCheck(ByRef ListBoxObject As CheckedListBox)

    Private Sub ProcessItemCheckSub(ByRef ListBoxObject As CheckedListBox)
        ' Do your actual ItemCheck stuff here
    End Sub

    Private Sub CheckedListBox1_ItemCheck(ByVal sender As Object, ByVal e As System.Windows.Forms.ItemCheckEventArgs) Handles CheckedListBox1.ItemCheck
        Dim Objects As Object() = {CheckedListBox1}
        BeginInvoke(New ProcessItemCheck(AddressOf ProcessItemCheckSub), Objects)
    End Sub
End Class
Entremets answered 2/9, 2015 at 14:14 Comment(0)
M
0

I use a Timer to solve this problem. Enable the timer via the ItemCheck event. Take action in the Timer's Tick event.

This works whether the item is checked via a mouse click or by pressing the Space-Bar. We'll take advantage of the fact that the item just checked (or un-checked) is always the Selected Item.

The Timer's Interval can be as low as 1. By the time the Tick event is raised, the new Checked status will be set.

This VB.NET code shows the concept. There are many variations you can employ. You may want to increase the Timer's Interval to allow the user to change the check status on several items before taking action. Then in the Tick event, make a sequential pass of all the

Items in the List or use its CheckedItems collection to take appropriate action.

That's why we first disable the Timer in the ItemCheck event. Disable then Enable causes the Interval period to re-start.

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
Microcyte answered 10/4, 2015 at 2:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.