我有一个 CheckedListBox,在检查项目后我想要一个事件,以便我可以将 CheckedItems 与新状态一起使用。
由于 ItemChecked 在 CheckedItems 更新之前被触发,因此它不会开箱即用。
当 CheckedItems 更新时,我可以使用哪种方法或事件来获得通知?
我有一个 CheckedListBox,在检查项目后我想要一个事件,以便我可以将 CheckedItems 与新状态一起使用。
由于 ItemChecked 在 CheckedItems 更新之前被触发,因此它不会开箱即用。
当 CheckedItems 更新时,我可以使用哪种方法或事件来获得通知?
ItemCheck
如果您还检查正在单击的项目的新状态,则可以使用该事件。这在事件 args 中可用,如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
...
}
关于这个有很多相关的 StackOverflow 帖子......以及Branimir 的解决方案,这里有两个更简单的:
ItemCheck 的延迟执行(也在这里):
void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
this.BeginInvoke((MethodInvoker) (
() => Console.WriteLine(checkedListBox1.SelectedItems.Count)));
}
void checkedListBox1_MouseUp(object sender, MouseEventArgs e)
{
Console.WriteLine(checkedListBox1.SelectedItems.Count);
}
我更喜欢第一个选项,因为第二个会导致误报(即过于频繁地触发)。
我试过了,它奏效了:
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
和实施
/// <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
事件的参数来计算 CheckedItems。如果您查看MSDN 上的此示例,您可以确定新更改的项目是已选中还是未选中,这使您处于处理这些项目的合适位置。
您甚至可以创建一个在检查项目后触发的新事件,如果您愿意,它会为您提供您想要的东西。
经过一些测试,我可以看到 SelectedIndexChanged 事件是在 ItemCheck 事件之后触发的。保持属性 CheckOnClick True
最佳编码
这行得通,但不确定它有多优雅!
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
不知道这是否适用,但我想使用复选框来过滤结果。因此,当用户选中和未选中的项目时,我希望列表显示\隐藏项目。
我遇到了一些问题,导致我写了这篇文章。只是想分享我是如何做到的,没有什么特别的。
注意:我有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
}
}
}
我试过了,它奏效了:
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);
}
}
通过列表的索引确定。
假设您想保留参数ItemCheck
但在模型更改后收到通知,它应该如下所示:
CheckedListBox ctrl = new CheckedListBox();
ctrl.ItemCheck += (s, e) => BeginInvoke((MethodInvoker)(() => CheckedItemsChanged(s, e)));
CheckedItemsChanged
可能在哪里:
private void CheckedItemsChanged(object sender, EventArgs e)
{
DoYourThing();
}
如果像我一样,您试图将选择用作一个指标(用户选择的项目),并且用户想要更改刻度,那么我找到了一个偷偷摸摸的解决方案。
Form variables
Private IsTicked As Boolean = False
Private ListIndex = -1
在页面上创建一个计时器。例如,我的被称为tmrBan
,而我有一个CheckBoxList
被称为clbFTI
。
然后,为您的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
然后,为您的计时器创建一个 Tick 事件
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles tmrBan.Tick
clbFTI.SetItemChecked(ListIndex, IsTicked)
End Sub
您会看到滴答声的闪烁,但请使用计时器 Interval 以使其更适合您的情况。
你的意思是checkboxlist,而不是checkedlistbox?如果是这样,则相关事件将是 SelectedIndexChanged。
例如 VB 中的处理程序定义头:
Protected Sub cblStores_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cblStores.SelectedIndexChanged
我决定不关心控件的感受,并处理该事件,就好像复选框更改确实已经完成一样。您需要做的就是获取CheckedIndices
列表,并使用ItemCheckEventArgs
对象中的信息将其调整为新状态。
然后,您可以遍历该列表并从Items
控件的属性中检索指示的项目,并且您有您的CheckedItems
列表。
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);
}
结果在功能上与仅在更改后才触发事件的假设期望情况相同。
VB.NET 版本的Dunc对BeginInvoke
处理程序的回答,以便检查项目。
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
我使用定时器来解决这个问题。通过 ItemCheck 事件启用计时器。在 Timer's Tick 事件中采取行动。
无论是通过鼠标单击还是按空格键检查项目,这都有效。我们将利用刚刚选中(或未选中)的项目始终是选定项目这一事实。
Timer 的 Interval 可以低至 1。在引发 Tick 事件时,将设置新的 Checked 状态。
这个 VB.NET 代码显示了这个概念。您可以使用许多变体。您可能希望增加计时器的时间间隔,以允许用户在采取行动之前更改多个项目的检查状态。然后在 Tick 事件中,依次传递 List 中的所有项目或使用其 CheckedItems 集合来采取适当的操作。
这就是我们首先在 ItemCheck 事件中禁用 Timer 的原因。禁用然后启用会导致间隔周期重新开始。
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
在正常行为中,当我们检查一个项目时,该项目的检查状态将在事件处理程序被引发之前改变。但是 CheckListBox 有不同的行为:事件处理程序在项目的检查状态更改之前引发,这使得我们很难纠正我们的工作。
在我看来,要解决这个问题,我们应该推迟事件处理程序。
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());}