【问题标题】:Which CheckedListBox event triggers after a item is checked?检查项目后会触发哪个 CheckedListBox 事件?
【发布时间】:2010-09-08 10:18:13
【问题描述】:

我有一个 CheckedListBox,我想要一个事件检查一个项目之后,以便我可以使用带有新状态的 CheckedItems。

由于 ItemChecked 在 CheckedItems 更新之前被触发,因此它不会开箱即用。

当 CheckedItems 更新时,我可以使用什么样的方法或事件来获得通知?

【问题讨论】:

    标签: c# winforms checkedlistbox


    【解决方案1】:

    您可以使用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
            ...
    }
    

    【讨论】:

    • 在每个的第一个中,我们可能需要添加一个 if 条件.. if not item = checkedListBox1.Items[e.Index].ToString()
    • 问题是在处理检查之前触发了 ItemCheck 事件。您的解决方案将涉及保留您自己的列表,本质上是复制标准代码。 Dunc 的第一个建议(ItemCheck 上的延迟执行)是 imo 对 phq 问题的最干净的回答,因为它不需要任何额外的处理。
    • 为了让它有用,必须将checkedListBox1_ItemCheck添加到checkedListBox1.ItemCheck,例如:checkedListBox1.ItemCheck += checkedListBox1_ItemCheck;
    【解决方案2】:

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

    Delayed execution on ItemCheck(也是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);
        }
    

    我更喜欢第一个选项,因为第二个会导致误报(即过于频繁地触发)。

    【讨论】:

    • 第二种方法也会错过通过键盘选中或取消选中的项目。
    • BeginInvoke 正是我所需要的,因为我的事件实际上是在调用一个接口,它不知道它正在处理什么样的控件。接受的答案仅在逻辑可以在事件处理程序中执行或直接从事件处理程序调用的情况下有效。我不是这种情况。感谢这个很棒但简单的解决方案。
    • 谢谢,BeginInvoke 的第一个选项对我有用。也许是一个愚蠢的评论。。但是为什么这个 BUG 在 2010 年开始的主题中报告没有在 2018 年解决??
    • @Goodies 同意,尽管我想如果微软现在改变行为可能会破坏很多代码。 docs 明确声明 The check state is not updated until after the ItemCheck event occurs。一个不同的事件或非任意的解决方法将是很好的 IMO。
    【解决方案3】:

    我试过了,效果很好:

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

    【讨论】:

    • 这个! ...应该是正确的答案,这是最不幸的。这是一个荒谬的 hack,因为 M$ 的某个人忘记了实现 ItemChecked 事件,并且没有人提到它不存在。
    • 虽然这不是一个错误,但我认为这应该被实现,如果你同意考虑通过点击 +1 来支持这个错误报告:connect.microsoft.com/VisualStudio/feedback/details/1759293
    • @Sebastian – 不要在这里要求修复。对此的任何“修复”都会破坏现有的解决方案。如果有两个事件:ItemChecking,ItemChecked,那么你可以使用后一个。但是,如果只实现了一个 (ItemCheck),它会正确地做事,即使用作为参数提供的新值和索引来检查触发事件 before 值。谁想要“更改后”事件,他们可以简单地使用上面的。如果向 Microsoft 提出建议,则建议 新事件 ItemChecked,而不是更改现有事件:请参阅 diimdeep's answer
    • 像这样,但我一直使用的一个小替代方案就是设置某种“跳过”标志,以便 SetItemCheckState 不会重新触发相同的事件。要么是一个简单的全局,要么我喜欢做的是确保标签。例如,将操作包装在 If myCheckListBox.Tag != null 中,然后代替 Event Delete\Add,只需将标记设置为某个值(甚至是空字符串),然后返回 null 以将其重新打开。
    【解决方案4】:

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

    【讨论】:

    • 在第二个答案中使用BeginInvoke 解决方案时,您可以跳过很多额外的代码。
    【解决方案5】:

    虽然不理想,但您可以使用传递给ItemCheck 事件的参数来计算 CheckedItems。如果您查看此example on MSDN,您可以确定新更改的项目是已选中还是未选中,这使您处于处理这些项目的合适位置。

    您甚至可以创建一个在检查项目后触发的新事件,如果您愿意,它会提供您想要的。

    【讨论】:

    • 您对如何创建这个新事件有任何具体想法吗?我怎么知道在 ItemChecke 事件之后 CheckedItems 何时更新?
    【解决方案6】:

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

    最佳编码

    【讨论】:

    • 你说得对,这是最简单的方法。但它仍然有点像黑客,因为它是无证和意外的行为。 Microsoft 的任何新生都可能会想:哦,好吧,为什么只在 Checkstate 更改时触发 SelectedIndexChanged。让我们优化一下。然后 Bang 使用您的代码 :(
    • 此外,当您以编程方式更改检查状态时,SelectedIndexChanged 不会触发。
    • 当您使用 Space 键更改检查状态时,它不会触发。用这个是错误的。
    【解决方案7】:

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

    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】:

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

      我遇到了一些问题,导致我发了这篇文章。只是想分享一下我是如何做到的,没有什么特别的。

      注意:我有 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
              }
         }
      
      }
      

      【讨论】:

      • 当用户使用 Space 键更改检查状态时,SelectedIndexChanged 不会触发。
      • SelectedIndexChanged 在调用 SetItemChecked 以检查或取消选中代码中的项目时不会触发。
      【解决方案9】:

      假设您想保留来自 ItemCheck 的参数,但在模型更改后收到通知,它应该如下所示:

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

      CheckedItemsChanged 可能在哪里:

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

      【讨论】:

        【解决方案10】:

        我试过了,效果很好:

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

        根据列表的索引确定。

        【讨论】:

          【解决方案11】:

          你的意思是checkboxlist,而不是checkedlistbox?如果是这样,则相关事件将是 SelectedIndexChanged。

          例如VB中的处理程序定义头:

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

          【讨论】:

            【解决方案12】:

            我决定不关心控件的感受,并像复选框更改确实已经完成一样处理事件。您需要做的就是获取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);
            }
            

            结果在功能上与假设的期望情况相同,即事件仅在更改后触发。

            【讨论】:

              【解决方案13】:

              VB.NET 版本的 Dunc's answerBeginInvoke 的处理程序,以便检查项目。

              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
              

              【讨论】:

                【解决方案14】:

                如果您像我一样尝试将 Selection 用作一个指示符(用户选择的项目),并且用户想要更改刻度,那么我找到了一个偷偷摸摸的解决方案。

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

                在页面上创建一个计时器。例如,我的叫tmrBan,而我的CheckBoxListclbFTI

                然后,为您的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
                

                您会看到滴答声的闪烁,但请尝试使用计时器间隔以使其更适合您的情况。

                【讨论】:

                  【解决方案15】:

                  我使用定时器来解决这个问题。通过 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
                  

                  【讨论】:

                  • 谢谢分享。另一方面,也许您可​​以从其他答案中学习更好的解决方案。使用 Timer 相对复杂,在这种情况下它是错误的工具,因为您实际上已经获得了新值作为参数。因此,您可以使用this answer 一次性解决方案或使用this one 系统解决方案。使用其中一种在线转换工具将它们从 C# 转换为 VB。
                  【解决方案16】:

                  在正常行为中,当我们检查一个项目时,该项目的检查状态将在事件处理程序被引发之前改变。 但是 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());}
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多