Qual evento CheckedListBox é disparado depois que um item é verificado?


96

Eu tenho um CheckedListBox onde desejo um evento após a verificação de um item para que eu possa usar CheckedItems com o novo estado.

Como ItemChecked é disparado antes de CheckedItems ser atualizado, ele não funcionará imediatamente.

Que tipo de método ou evento posso usar para ser notificado quando o CheckedItems for atualizado?

Respostas:


88

Você pode usar o ItemCheckevento, se também verificar o novo estado do item que está sendo clicado. Isso está disponível nos argumentos do evento, como e.NewValue. Se NewValueestiver marcada, inclua o item atual junto com a coleção adequada em sua lógica:

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

Como outro exemplo, para determinar se a coleção estará vazia após este item ser (des) marcado:

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

3
no primeiro para cada um, talvez seja necessário adicionar uma condição if ..if not item = checkedListBox1.Items[e.Index].ToString()
Lenin Raj Rajasekaran

8
O problema é que o evento ItemCheck é disparado antes que a verificação seja processada. Sua solução envolveria manter sua própria lista, essencialmente duplicando o código padrão. A primeira sugestão de Dunc (Execução atrasada no ItemCheck) é imo a resposta mais limpa para a questão do phq, porque não requer nenhum tratamento adicional.
Berend Engelbrecht

34

Existem várias postagens StackOverflow relacionadas a isso ... Além da solução de Branimir , aqui estão duas outras simples:

Execução atrasada no ItemCheck (também aqui ):

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

Usando o evento MouseUp :

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

Prefiro a primeira opção, pois a segunda resultaria em falsos positivos (ou seja, disparar com muita frequência).


13
O segundo método também perderia os itens sendo verificados ou desmarcados por meio do teclado.

1
BeginInvoke era exatamente o que eu precisava, pois meu evento estava realmente chamando uma interface, que não tinha ideia de que tipo de controle estava lidando. A resposta aceita funciona apenas nos casos em que a lógica pode ser executada no manipulador de eventos ou em algo chamado diretamente do manipulador de eventos. Este não foi o meu caso. Obrigado por esta solução incrível, mas simples.
Jesse de

Thx, a primeira opção com BeginInvoke funciona para mim. Pode ser um comentário bobo pessoal .. mas por que esse BUG é reportado em um tópico iniciado em 2010 não resolvido em 2018 ??
Goodies

1
@Goodies Agreed, embora eu ache que poderia quebrar muitos códigos se a Microsoft mudasse o comportamento agora. Os documentos afirmam explicitamente The check state is not updated until after the ItemCheck event occurs. Um evento diferente ou uma solução alternativa não arbitrária seria bom IMO.
Dunc

24

Eu tentei isso e funcionou:

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

8
Este! ... deve ser a resposta correta, o que infelizmente é. Este é um hack ridículo que funciona porque alguém na M $ se esqueceu de implementar o ItemCheckedevento e ninguém disse que ele não existia.
RLH

Embora não seja por definição um bug, eu acho que isso deve ser implementado, se você concordar, considere apoiar este relatório de bug clicando em +1: connect.microsoft.com/VisualStudio/feedback/details/1759293
SCBuergel.eth

@Sebastian - não peça correção aqui. Qualquer "conserto" disso quebraria as soluções existentes. Se houvesse dois eventos: ItemChecking, ItemChecked, então você poderia usar este último um. Mas se apenas um for implementado ( ItemCheck), ele está fazendo as coisas corretamente, ou seja, disparando o evento antes que o valor seja verificado com o novo valor e o índice fornecido como parâmetros. Quem quiser o evento "após a mudança", pode simplesmente usar o acima. Se sugerir algo à Microsoft, então sugira um novo evento ItemChecked , não alterando o existente: ver a resposta de
diimdeep

Assim, mas uma pequena alternativa que eu uso o tempo todo é apenas definir algum tipo de sinalizador de "ignorar" para que SetItemCheckState não acione novamente o mesmo evento. Ou um global simples, ou o que eu gosto de fazer é ter certeza da tag. por exemplo, envolva a ação em If myCheckListBox.Tag! = null e, em seguida, no lugar de Event Delete \ Add, apenas defina a tag como algo (até mesmo uma string vazia) e, em seguida, volte para null para ativá-la novamente.
da_jokker

10

Derivar CheckedListBoxe implementar

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

4

Embora não seja ideal, você pode calcular os CheckedItems usando os argumentos que são passados ​​para o ItemCheckevento. Se você olhar este exemplo no MSDN , poderá descobrir se o item recém-alterado foi marcado ou desmarcado, o que o deixa em uma posição adequada para trabalhar com os itens.

Você pode até criar um novo evento que dispara depois que um item é verificado, o que lhe daria exatamente o que você queria, se desejasse.


1
Você tem alguma ideia específica de como esse novo evento pode ser criado, como posso saber quando os CheckedItems foram atualizados após o evento ItemChecke?
hultqvist

4

Após alguns testes, pude constatar que o evento SelectedIndexChanged é acionado após o evento ItemCheck. Manter a propriedade CheckOnClick True

Melhor codificação


Você está certo, essa é a maneira mais fácil. Mas ainda é algo como um hack, porque é um comportamento não documentado e inesperado. Qualquer calouro na Microsoft pode pensar: ah, bem, por que disparar o SelectedIndexChanged quando apenas o Checkstate muda. Vamos otimizar isso. E Bang vai seu código :(
Rolf

Além disso, SelectedIndexChanged não é acionado quando você altera o estado de seleção programaticamente.
Rolf de

1
E não dispara quando você altera o estado de verificação com a tecla Espaço. É errado usar isso.
Elmue

2

Isso funciona, mas não tenho certeza de como é elegante!

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

1

Não sei se isso se aplica, mas eu queria usar uma caixa de seleção para filtrar os resultados. Assim, conforme o usuário marcava e desmarcava os itens, eu queria que a lista mostrasse / ocultasse os itens.

Eu estava tendo alguns problemas que me levaram a este post. Só queria compartilhar como fiz isso, sem nada de especial.

Nota: Tenho CheckOnClick = true, mas provavelmente ainda funcionaria sem

O evento que uso é " SelectedIndexChanged "

a enumeração que uso é " .CheckedItems "

Isso dá os resultados que acho que podemos esperar. Tão simplificado se resume a ....

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

}

SelectedIndexChanged não é acionado quando o usuário altera o estado de verificação com a tecla Espaço.
Elmue

SelectedIndexChanged não é disparado ao chamar SetItemChecked para marcar ou desmarcar um item no código.
bkqc de

1

Supondo que você queira preservar os argumentos, ItemCheckmas seja notificado depois que o modelo for alterado, deve ser assim:

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

Onde CheckedItemsChangedpoderia ser:

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

0

Eu uso um Timer para resolver este problema. Habilite o cronômetro por meio do evento ItemCheck. Tome uma atitude no evento Timer's Tick.

Isso funciona se o item é verificado através de um clique do mouse ou pressionando a barra de espaço. Aproveitaremos o fato de que o item que acabou de ser marcado (ou desmarcado) é sempre o Item selecionado.

O intervalo do temporizador pode ser tão baixo quanto 1. No momento em que o evento Tick é gerado, o novo status Verificado será definido.

Este código VB.NET mostra o conceito. Existem muitas variações que você pode empregar. Você pode querer aumentar o intervalo do temporizador para permitir que o usuário altere o status de verificação em vários itens antes de agir. Em seguida, no evento Tick, faça uma passagem sequencial de todos os itens na lista ou use sua coleção CheckedItems para tomar a ação apropriada.

É por isso que primeiro desabilitamos o Timer no evento ItemCheck. Desativar e Ativar faz com que o período de intervalo seja reiniciado.

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

Obrigado por compartilhar. Por outro lado, talvez você possa aprender soluções melhores com outras respostas. Usar o Timer é relativamente complicado e, neste caso, é a ferramenta errada para o trabalho, porque na verdade você já está obtendo novos valores como parâmetros. Portanto, você pode usar esta resposta para uma solução única ou esta para uma solução sistemática. Converta-os de C # para VB usando uma das ferramentas de conversão online.
miroxlav

0

No comportamento normal, quando verificamos um item, o estado de verificação do item muda antes que o manipulador de eventos seja acionado. Mas um CheckListBox tem um comportamento diferente: o manipulador de eventos é gerado antes que o estado de verificação do item seja alterado e isso torna difícil corrigir nossos trabalhos.

Em minha opinião, para resolver esse problema, devemos adiar o manipulador de eventos.

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

Eu tentei isso e funcionou:

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

determinar pelo índice da lista.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.