可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 method or event can I use to be notified when the CheckedItems is updated?
回答1:
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
...
}
回答2:
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).
回答3:
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();
}
回答4:
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);
}
回答5:
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.
回答6:
After some tests, I could see that the event SelectedIndexChanged is triggered after the event ItemCheck. Keep the property CheckOnClick True
Best coding
回答7:
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
回答8:
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
}
}
}
回答9:
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();
}
回答10:
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
回答11:
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());}