开发者

Which CheckedListBox event triggers after a item is checked?

I have a CheckedListBox where I want an event after an item is checked so that I can use CheckedItems with the new state.

Since ItemChecked is fired before CheckedItems is updated it won't work out of the box.

What kind of met开发者_高级运维hod or event can I use to be notified when the CheckedItems is updated?


You can use the ItemCheck event, if you also check the new state of the item which is being clicked. This is available in the event args, as e.NewValue. If NewValue is checked, include the current item along with the collection proper in your logic:

    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)
        {
            ...
        }
    }

As another example, to determine if the collection will be empty after this item is (un-)checked:

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
        ...
}


There are lots of related StackOverflow posts on this... As well as Branimir's solution, here are two more simple ones:

Delayed execution on ItemCheck (also here):

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

Using the MouseUp event:

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

I prefer the first option, as the second would result in false positives (i.e. firing too often).


I tried this and it worked:

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


Derive from CheckedListBox and implement

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


Although not ideal, you can calculate the CheckedItems using the arguments that are passed through to the ItemCheck event. If you look at this example on MSDN, you can work out whether the newly changed item has been checked or unchecked, which leaves you in a suitable position to work with the items.

You could even create a new event that fires after an item is checked, which would give you exactly what you wanted if you wished.


After some tests, I could see that the event SelectedIndexChanged is triggered after the event ItemCheck. Keep the property CheckOnClick True

Best coding


This works, not sure how elegant it is though!

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


I tried this and it worked:

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

determine by index of the list.


Don't know if this applies but I wanted to use a checklistbox to filter results. So as the user checked and unchecked items I wanted the list to show\hide items.

I was having some issues which led me to this post. Just wanted to share how I did it without anything special.

Note: I have CheckOnClick = true but it would probably still work without

The event I use is "SelectedIndexChanged"

the enumeration I use is ".CheckedItems"

This give the results I think we may expect. So simplified it comes down to ....

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

}


Assuming you want to preserve the arguments from ItemCheck but get notified after the model was changed it should look like that:

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

Where CheckedItemsChanged could be:

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


Do you mean checkboxlist, rather than checkedlistbox? If so, the the event concerned would be SelectedIndexChanged.

e.g. handler definition head in VB:

Protected Sub cblStores_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cblStores.SelectedIndexChanged


I decided to just not care about the control's feelings, and handle the event as if the checkbox change indeed went through already. All you need to do is take the CheckedIndices list, and use the information in the ItemCheckEventArgs object to adjust it to the new state.

Then you can just loop over that list and retrieve the indicated items from the Items property of the control, and you have your CheckedItems list.

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

The result is functionally identical to the hypothetical desired case where the event triggers only after the change.


VB.NET version of Dunc's answer to BeginInvoke a handler so the item is checked.

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


If, like me, you were trying to use the Selection as one indicator (the item selected by the user), and the user wanting to changed the tick then I found a sneaky solution.

Form variables
    Private IsTicked As Boolean = False
    Private ListIndex = -1

Create a timer on the page. For instance, mine is called tmrBan, and I have a CheckBoxList called clbFTI.

Then, create a Click Event for your 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

Then, create a Tick Event for your timer

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles tmrBan.Tick
    clbFTI.SetItemChecked(ListIndex, IsTicked)
End Sub

You will see a flicker of the tick, but play around with the timer Interval to make this better for your case.


I ran into a similar issue: On a click of an item, the state should be converted from either checked/ non-checked to opposite. Here i post the event and the check and change:

CheckedListBox ChkLBox;
private void CheckedListBox_SelectedIndexChanged(object sender, EventArgs e)
{
    int SelectedIndex = ChkLBox.SelectedIndex; //
    var Item = ChkLBox.Items[SelectedIndex];

    bool IsChecked = (ChkLBox.GetItemChecked(ChkLBox.SelectedIndex));
    ChkLBox.SetItemChecked(ChkLBox.Items.IndexOf(Item), !IsChecked);
}


This thread comes up when you search for VB dotnet (.NET) solutions, so I'm just going to put the solution I came up with here (Invoke).

Private Sub CheckListBox1_ItemCheck(sender As Object, e As EventArgs) Handles checkListBox1.ItemCheck
    Me.BeginInvoke(New MethodInvoker(AddressOf CustomFunctionHere))
End Sub

Private Sub CustomFunctionHere()
    'Do something
End Sub

EDIT: After using Invoke for a while, I realized it is not a great solution for my project. It works, but the order of operations will be thrown off. Instead, I created a method to calculate the 'fixed' count using the SelectedItems.Count along with the old and new selection values to determine if I should add or subtract one from the total.


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


In normal behaviour, when we check one item, the item's check state will change before the event handler is raised. But a CheckListBox has a different behaviour: The event handler is raised before the check state of the item changes and that makes it difficult to correct our jobs.

In my opinion, to solve this problem, we should defer the event handler.

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜