Quale evento CheckedListBox si attiva dopo che un elemento è stato controllato?


96

Ho un CheckedListBox in cui desidero un evento dopo che un elemento è stato controllato in modo da poter utilizzare CheckedItems con il nuovo stato.

Poiché ItemChecked viene attivato prima che CheckedItems venga aggiornato, non funzionerà immediatamente.

Che tipo di metodo o evento posso utilizzare per essere avvisato quando CheckedItems viene aggiornato?

Risposte:


88

È possibile utilizzare l' ItemCheckevento, se si controlla anche il nuovo stato dell'elemento su cui si fa clic. Questo è disponibile nell'evento args, come e.NewValue. Se NewValueè selezionato, includi l'elemento corrente insieme alla raccolta corretta nella tua logica:

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

Come altro esempio, per determinare se la collezione sarà vuota dopo che questo elemento è stato (deselezionato):

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
nel primo per ciascuno, potremmo dover aggiungere una condizione if ..if not item = checkedListBox1.Items[e.Index].ToString()
Lenin Raj Rajasekaran

8
Il problema è che l'evento ItemCheck viene generato prima dell'elaborazione del controllo. La tua soluzione comporterebbe mantenere il tuo elenco, essenzialmente duplicando il codice standard. Il primo suggerimento di Dunc (esecuzione ritardata su ItemCheck) è imo la risposta più pulita alla domanda di phq, perché non richiede alcuna gestione aggiuntiva.
Berend Engelbrecht

34

Ci sono molti post StackOverflow correlati su questo ... Oltre alla soluzione di Branimir , eccone altri due semplici:

Esecuzione ritardata su ItemCheck (anche qui ):

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

Utilizzo dell'evento MouseUp :

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

Preferisco la prima opzione, poiché la seconda comporterebbe falsi positivi (cioè sparando troppo spesso).


13
Il secondo metodo mancherebbe anche gli elementi controllati o deselezionati tramite la tastiera.

1
BeginInvoke era esattamente ciò di cui avevo bisogno poiché il mio evento stava effettivamente chiamando un'interfaccia, che non aveva idea del tipo di controllo con cui aveva a che fare. La risposta accettata funziona solo nei casi in cui la logica può essere eseguita all'interno del gestore di eventi o qualcosa chiamato direttamente dal gestore di eventi. Questo non è stato il caso per me. Grazie per questa fantastica ma semplice soluzione.
Jesse

Grazie, la prima opzione con BeginInvoke funziona per me. Forse un commento sciocco gente .. ma perché questo BUG riportato in un argomento iniziato nel 2010 non è stato risolto nel 2018 ??
Goodies

1
@Goodies d'accordo, anche se immagino che potrebbe rompere un sacco di codice se Microsoft cambiasse il comportamento ora. I documenti affermano esplicitamente The check state is not updated until after the ItemCheck event occurs. Un evento diverso o una soluzione alternativa non arbitraria sarebbe bello IMO.
Dunc

24

Ho provato questo e ha funzionato:

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
Questo! ... dovrebbe essere la risposta corretta, che purtroppo lo è. Questo è un trucco ridicolo che funziona perché qualcuno a M $ ha dimenticato di implementare l' ItemCheckedevento e nessuno ha mai detto che non esisteva.
RLH

Sebbene non sia per definizione un bug, penso che dovrebbe essere implementato, se sei d'accordo considera di supportare questo bug report facendo clic su +1: connect.microsoft.com/VisualStudio/feedback/details/1759293
SCBuergel.eth

@Sebastian - non chiedere correzione qui. Qualsiasi "correzione" di questo romperebbe le soluzioni in uscita. Se ci sono stati due eventi: ItemChecking, ItemChecked, allora si potrebbe utilizzare quest'ultimo. Ma se solo uno è implementato ( ItemCheck) sta facendo le cose correttamente, cioè attivando l'evento prima che il valore venga controllato con il nuovo valore e l'indice forniti come parametri. Chiunque desideri l'evento "after change", può semplicemente utilizzare quanto sopra. Se suggerisci qualcosa a Microsoft, allora suggerisci un nuovo evento ItemChecked , senza cambiare quello esistente: vedi la risposta di
diimdeep

In questo modo, ma una piccola alternativa che uso sempre è solo quella di impostare una sorta di flag "skip" in modo che SetItemCheckState non riattivi lo stesso evento. O un semplice globale, o quello che mi piace fare è assicurarmi del tag. ad esempio, racchiudi l'azione in If myCheckListBox.Tag! = null, quindi al posto di Event Delete \ Add, imposta semplicemente il tag su qualcosa (anche una stringa vuota) e poi di nuovo su null per riattivarlo.
da_jokker

10

Deriva CheckedListBoxe implementa

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

Sebbene non sia l'ideale, è possibile calcolare i CheckedItems utilizzando gli argomenti passati ItemCheckall'evento. Se guardi questo esempio su MSDN , puoi capire se l'elemento appena modificato è stato selezionato o deselezionato, il che ti lascia in una posizione adatta per lavorare con gli elementi.

Potresti persino creare un nuovo evento che si attiva dopo che un elemento è stato controllato, che ti darebbe esattamente quello che volevi se lo desideri.


1
Hai un'idea precisa di come potrebbe essere creato questo nuovo evento, come posso sapere quando CheckedItems è stato aggiornato dopo l'evento ItemChecke?
hultqvist

4

Dopo alcuni test, ho potuto vedere che l'evento SelectedIndexChanged viene attivato dopo l'evento ItemCheck. Mantieni la proprietà CheckOnClick True

Miglior codifica


Hai ragione, questo è il modo più semplice. Ma è ancora qualcosa come un hack, perché è un comportamento non documentato e inASPETTATO. Qualsiasi matricola in Microsoft potrebbe pensare: oh, va bene, perché sparare SelectedIndexChanged quando cambia solo Checkstate. Ottimizziamolo. E Bang fa il tuo codice :(
Rolf

Inoltre, SelectedIndexChanged non viene attivato quando si modifica lo stato di controllo a livello di codice.
Rolf

1
E non si attiva quando si modifica lo stato di controllo con la barra spaziatrice. È sbagliato usare questo.
Elmue

2

Funziona, non sono sicuro di quanto sia elegante però!

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

Non so se si applica, ma volevo utilizzare una casella di riepilogo per filtrare i risultati. Quindi, poiché l'utente ha selezionato e deselezionato gli elementi, volevo che l'elenco mostrasse \ nascondi gli elementi.

Ho riscontrato alcuni problemi che mi hanno portato a questo post. Volevo solo condividere come l'ho fatto senza niente di speciale.

Nota: ho CheckOnClick = true ma probabilmente funzionerebbe ancora senza

L'evento che utilizzo è " SelectedIndexChanged "

l'enumerazione che uso è " .CheckedItems "

Questo dà i risultati che penso possiamo aspettarci. Così semplificato si riduce 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 non viene attivato quando l'utente modifica lo stato di controllo con la barra spaziatrice.
Elmue

SelectedIndexChanged non si attiva quando si chiama SetItemChecked per selezionare o deselezionare un elemento nel codice.
bkqc

1

Supponendo che tu voglia preservare gli argomenti da ItemCheckma ricevere una notifica dopo che il modello è stato modificato, dovrebbe apparire così:

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

Dove CheckedItemsChangedpotrebbe essere:

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

0

Uso un timer per risolvere questo problema. Abilita il timer tramite l'evento ItemCheck. Agisci nell'evento Timer's Tick.

Funziona sia che l'elemento sia controllato tramite un clic del mouse o premendo la barra spaziatrice. Approfitteremo del fatto che l'elemento appena selezionato (o deselezionato) è sempre l'elemento selezionato.

L'intervallo del timer può essere minimo 1. Quando viene generato l'evento Tick, verrà impostato il nuovo stato Checked.

Questo codice VB.NET mostra il concetto. Ci sono molte varianti che puoi utilizzare. È possibile aumentare l'intervallo del timer per consentire all'utente di modificare lo stato di controllo su diversi elementi prima di eseguire un'azione. Quindi, nell'evento Tick, eseguire un passaggio sequenziale di tutti gli elementi nell'elenco o utilizzare la relativa raccolta CheckedItems per eseguire l'azione appropriata.

Ecco perché prima disabilitiamo il timer nell'evento ItemCheck. Disabilita quindi Abilita fa riavviare il periodo di intervallo.

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

Grazie per la condivisione. D'altra parte, forse puoi imparare soluzioni migliori da altre risposte. L'uso del timer è relativamente complicato e in questo caso è uno strumento sbagliato per il lavoro, perché in realtà stai già ottenendo nuovi valori come parametri. Quindi puoi usare questa risposta per una soluzione una tantum o questa per una soluzione sistematica. Convertirli da C # a VB utilizzando uno degli strumenti di conversione online.
miroxlav

0

Nel comportamento normale, quando controlliamo un elemento, lo stato di controllo dell'elemento cambierà prima che venga sollevato il gestore di eventi. Ma un CheckListBox ha un comportamento diverso: il gestore eventi viene sollevato prima che lo stato di controllo dell'elemento cambi e questo rende difficile correggere i nostri lavori.

A mio parere, per risolvere questo problema, dovremmo rimandare il gestore di eventi.

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

Ho provato questo e ha funzionato:

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

determinare per indice della lista.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.