¿Qué evento CheckedListBox se activa después de que se marca un elemento?

96

Tengo un CheckedListBox donde quiero un evento después de que se verifique un elemento para poder usar CheckedItems con el nuevo estado.

Dado que ItemChecked se activa antes de que se actualice CheckedItems, no funcionará de inmediato.

¿Qué tipo de método o evento puedo utilizar para recibir una notificación cuando se actualice CheckedItems?

hultqvist
fuente

Respuestas:

89

Puede usar el ItemCheckevento, si también verifica el nuevo estado del elemento en el que se hace clic. Está disponible en los argumentos del evento, como e.NewValue. Si NewValueestá marcado, incluya el elemento actual junto con la colección adecuada en su 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 otro ejemplo, para determinar si la colección estará vacía después de que este elemento sea (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
        ...
}
Branimir
fuente
3
en el primero para cada uno, es posible que debamos agregar una condición if ..if not item = checkedListBox1.Items[e.Index].ToString()
Lenin Raj Rajasekaran
8
El problema es que el evento ItemCheck se activa antes de que se procese el cheque. Su solución implicaría mantener su propia lista, esencialmente duplicando el código estándar. La primera sugerencia de Dunc (ejecución retrasada en ItemCheck) es, en mi opinión, la respuesta más limpia a la pregunta de phq, porque no requiere ningún manejo adicional.
Berend Engelbrecht
35

Hay muchas publicaciones de StackOverflow relacionadas sobre esto ... Además de la solución de Branimir , aquí hay dos más simples:

Ejecución retrasada en ItemCheck (también aquí ):

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

Usando el evento MouseUp :

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

Prefiero la primera opción, ya que la segunda daría lugar a falsos positivos (es decir, disparar con demasiada frecuencia).

Dunc
fuente
13
El segundo método también omitiría elementos marcados o desmarcados a través del teclado.
1
BeginInvoke era exactamente lo que necesitaba, ya que mi evento estaba llamando a una interfaz, que no tenía idea de qué tipo de control estaba tratando. La respuesta aceptada solo funciona en los casos en que la lógica se puede realizar dentro del controlador de eventos o algo llamado directamente desde el controlador de eventos. Este no fue mi caso. Gracias por esta increíble pero sencilla solución.
Jesse
Gracias, la primera opción con BeginInvoke funciona para mí. Tal vez un comentario tonto amigos ... pero ¿por qué se informa este ERROR en un tema que comenzó en 2010 y no se resolvió en 2018?
Goodies
1
@Goodies De acuerdo, aunque supongo que podría romper mucho código si Microsoft cambiara el comportamiento ahora. Los documentos declaran explícitamente The check state is not updated until after the ItemCheck event occurs. Un evento diferente o una solución alternativa no arbitraria sería bueno en mi opinión.
Dunc
24

Probé esto y funcionó:

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();        
}
hamburguesa blanda
fuente
8
¡Esta! ... debería ser la respuesta correcta, que es la más lamentable. Este es un truco ridículo que funciona porque alguien en M $ se olvidó de implementar el ItemCheckedevento y nadie dijo que no existía.
RLH
Aunque por definición no es un error, creo que esto debería implementarse, si está de acuerdo, considere respaldar este informe de error haciendo clic en +1: connect.microsoft.com/VisualStudio/feedback/details/1759293
SCBuergel.eth
@Sebastian - no pidas arreglo aquí. Cualquier "corrección" de esto rompería las soluciones existentes. Si había dos eventos: ItemChecking, ItemChecked, entonces se podría utilizar último. Pero si solo se implementa uno ( ItemCheck), está haciendo las cosas correctamente, es decir, disparando el evento antes de que se verifique el valor con el nuevo valor y el índice proporcionado como parámetros. Quien quiera el evento "después del cambio", simplemente puede usar el anterior. Si sugiere algo a Microsoft, sugiera un nuevo evento ItemChecked , que no cambie el existente: vea la respuesta de
diimdeep
Así, pero una pequeña alternativa que uso todo el tiempo es simplemente establecer algún tipo de indicador de "omisión" para que SetItemCheckState no vuelva a activar el mismo evento. O un simple global, o lo que me gusta hacer es asegurarme de la etiqueta. por ejemplo, envuelva la acción en If myCheckListBox.Tag! = null, y luego, en lugar de Event Delete \ Add, simplemente establezca la etiqueta en algo (incluso una cadena vacía) y luego vuelva a null para volver a activarla.
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);
}
diimdeep
fuente
Eso es mucho código adicional que puede omitir al usar la BeginInvokesolución de la segunda respuesta.
Łukasƨ Fronczyk
4

Aunque no es lo ideal, puede calcular CheckedItems utilizando los argumentos que se pasan al ItemCheckevento. Si observa este ejemplo en MSDN , puede averiguar si el elemento recién cambiado se ha marcado o desmarcado, lo que lo deja en una posición adecuada para trabajar con los elementos.

Incluso podría crear un nuevo evento que se active después de que se verifique un elemento, lo que le daría exactamente lo que deseaba si lo deseaba.

Iain Ward
fuente
1
¿Tiene alguna idea específica sobre cómo se podría crear este nuevo evento? ¿Cómo puedo saber cuándo se han actualizado CheckedItems después del evento ItemChecke?
hultqvist
4

Después de algunas pruebas, pude ver que el evento SelectedIndexChanged se activa después del evento ItemCheck. Mantenga la propiedad CheckOnClick True

Mejor codificación

Antonio Leite
fuente
Tienes razón, esta es la forma más sencilla. Pero sigue siendo algo así como un truco, porque es un comportamiento indocumentado y NO ESPERADO. Cualquier estudiante de primer año en Microsoft podría pensar: bueno, ¿por qué despedir SelectedIndexChanged cuando solo cambia Checkstate? Optimicemos eso. Y Bang dice tu código :(
Rolf
Además, SelectedIndexChanged no se activa cuando cambia el estado de verificación mediante programación.
Rolf
1
Y no se dispara cuando cambia el estado de verificación con la tecla Espacio. Está mal usar esto.
Elmue
2

¡Esto funciona, aunque no estoy seguro de lo elegante que es!

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
FireMatt
fuente
1

No sé si esto se aplica, pero quería usar una casilla de verificación para filtrar los resultados. Entonces, mientras el usuario marcaba y desmarcaba los elementos, quería que la lista mostrara / ocultara los elementos.

Estaba teniendo algunos problemas que me llevaron a esta publicación. Solo quería compartir cómo lo hice sin nada especial.

Nota: tengo CheckOnClick = true pero probablemente aún funcionaría sin

El evento que uso es " SelectedIndexChanged "

la enumeración que utilizo es " .CheckedItems "

Esto da los resultados que creo que podemos esperar. Tan simplificado que se reduce 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
        }
   }

}
da_jokker
fuente
SelectedIndexChanged no se activa cuando el usuario cambia el estado de verificación con la tecla Espacio.
Elmue
SelectedIndexChanged no se activa cuando se llama a SetItemChecked para marcar o desmarcar un elemento en el código.
bkqc
1

Suponiendo que desea conservar los argumentos de ItemCheckpero recibir una notificación después de que se haya cambiado el modelo, debería verse así:

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

Dónde CheckedItemsChangedpodría estar:

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

Probé esto y funcionó:

    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 por índice de la lista.

Seon Hyun KIM
fuente
-1

Utilizo un temporizador para resolver este problema. Habilite el temporizador a través del evento ItemCheck. Actúa en el evento Timer's Tick.

Esto funciona ya sea que el elemento se verifique mediante un clic del mouse o presionando la barra espaciadora. Aprovecharemos el hecho de que el elemento recién marcado (o desmarcado) es siempre el elemento seleccionado.

El intervalo del temporizador puede ser tan bajo como 1. Para cuando se active el evento Tick, se establecerá el nuevo estado Checked.

Este código VB.NET muestra el concepto. Hay muchas variaciones que puede emplear. Es posible que desee aumentar el intervalo del temporizador para permitir que el usuario cambie el estado de verificación en varios elementos antes de tomar medidas. Luego, en el evento Tick, haga un pase secuencial de todos los elementos de la lista o use su colección CheckedItems para tomar la acción apropiada.

Es por eso que primero deshabilitamos el temporizador en el evento ItemCheck. Desactivar y luego Activar hace que el período de intervalo se reinicie.

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
Bob Ashcraft
fuente
1
Gracias por compartir. Por otro lado, quizás pueda aprender mejores soluciones a partir de otras respuestas. Usar el temporizador es relativamente complicado y en este caso es una herramienta incorrecta para el trabajo, porque en realidad ya está obteniendo nuevos valores como parámetros. Entonces, puede usar esta respuesta para una solución única o esta para una solución sistemática. Conviértelos de C # a VB usando una de las herramientas de conversión en línea.
miroxlav
-1

En el comportamiento normal, cuando verificamos un elemento, el estado de verificación del elemento cambiará antes de que se active el controlador de eventos. Pero un CheckListBox tiene un comportamiento diferente: el controlador de eventos se genera antes de que cambie el estado de verificación del elemento y eso dificulta la corrección de nuestros trabajos.

En mi opinión, para resolver este problema, deberíamos aplazar el controlador 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());}
Thinh Vu
fuente