Które zdarzenie CheckedListBox jest wyzwalane po sprawdzeniu elementu?

96

Mam CheckedListBox, w którym chcę zdarzenie po sprawdzeniu elementu, aby móc używać CheckedItems z nowym stanem.

Ponieważ ItemChecked jest uruchamiany przed aktualizacją CheckedItems, nie będzie działać po wyjęciu z pudełka.

Jakiego rodzaju metody lub zdarzenia mogę użyć, aby otrzymać powiadomienie o aktualizacji CheckedItems?

hultqvist
źródło

Odpowiedzi:

89

Możesz skorzystać ze ItemCheckzdarzenia, jeśli sprawdzisz również nowy stan klikniętego elementu. Jest to dostępne w args zdarzenia, jak e.NewValue. Jeśli NewValuejest zaznaczone, dołącz bieżący element wraz z odpowiednią kolekcją do swojej logiki:

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

Jako inny przykład, aby określić, czy kolekcja będzie pusta po (nie) zaznaczeniu tego elementu:

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
        ...
}
Branimira
źródło
3
w pierwszym dla każdego, być może będziemy musieli dodać jeden, jeśli warunek ..if not item = checkedListBox1.Items[e.Index].ToString()
Lenin Raj Rajasekaran
8
Problem polega na tym, że zdarzenie ItemCheck jest wyzwalane przed przetworzeniem sprawdzenia. Twoje rozwiązanie wymagałoby zachowania własnej listy, zasadniczo powielającej standardowy kod. Pierwsza sugestia Dunca (opóźnione wykonanie na ItemCheck) jest imo najczystszą odpowiedzią na pytanie o phq, ponieważ nie wymaga dodatkowej obsługi.
Berend Engelbrecht
35

Jest wiele powiązanych postów StackOverflow na ten temat ... Oprócz rozwiązania Branimira , oto dwa prostsze:

Opóźnione wykonanie w ItemCheck (również tutaj ):

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

Korzystanie ze zdarzenia MouseUp :

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

Wolę pierwszą opcję, ponieważ druga skutkowałaby fałszywymi alarmami (tj. Zbyt częstym odpalaniem).

Dunc
źródło
13
Druga metoda również pomijałaby sprawdzanie lub odznaczanie elementów za pomocą klawiatury.
1
BeginInvoke było dokładnie tym, czego potrzebowałem, ponieważ moje zdarzenie w rzeczywistości wywoływało interfejs, który nie miał pojęcia, z jakim rodzajem kontroli ma do czynienia. Zaakceptowana odpowiedź działa tylko w przypadkach, gdy logika może być wykonywana w module obsługi zdarzeń lub wywoływana bezpośrednio z programu obsługi zdarzeń. W moim przypadku tak nie było. Dzięki za to niesamowite, ale proste rozwiązanie.
Jesse,
Dzięki, pierwsza opcja z BeginInvoke działa dla mnie. Może głupi komentarz ludzie ... ale dlaczego ten BŁĄD zgłoszony w temacie rozpoczętym w 2010 roku nie został rozwiązany w 2018?
Goodies
1
@Goodies Zgoda, chociaż myślę, że mogłoby to zepsuć dużo kodu, gdyby Microsoft zmienił teraz zachowanie. W docs wyraźnie stwierdzić The check state is not updated until after the ItemCheck event occurs. Inne wydarzenie lub niearbitralne obejście byłoby fajne IMO.
Dunc
24

Spróbowałem tego i zadziałało:

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();        
}
softburger
źródło
8
To! ... powinna być poprawną odpowiedzią, niestety. To śmieszny hack, który działa, ponieważ ktoś w M $ zapomniał zaimplementować ItemCheckedwydarzenie i nikt nigdy nie wspomniał, że nie istnieje.
RLH
Chociaż z definicji nie jest to błąd, myślę, że powinno to zostać zaimplementowane, jeśli zgadzasz się, rozważ wsparcie tego zgłoszenia błędu, klikając +1: connect.microsoft.com/VisualStudio/feedback/details/1759293
SCBuergel.eth
@Sebastian - nie pytaj tutaj o poprawkę. Każde „rozwiązanie” tego problemu mogłoby zepsuć istniejące rozwiązania. Gdyby były dwa wydarzenia: ItemChecking, ItemChecked, a następnie można użyć drugiego z nich. Ale jeśli zaimplementowano tylko jeden ( ItemCheck), robi to poprawnie, tj. Uruchamia zdarzenie, zanim wartość zostanie sprawdzona z nową wartością i indeksem podanym jako parametry. Kto chce wydarzenia „po zmianie”, może po prostu skorzystać z powyższego. Jeśli zasugerujesz coś Microsoftowi, zasugeruj nowe wydarzenie ItemChecked , nie zmieniając istniejącego: zobacz odpowiedź
diimdeep
W ten sposób, ale jedną drobną alternatywą, której używam cały czas, jest po prostu ustawienie jakiejś flagi „pomiń”, aby SetItemCheckState nie wyzwalał ponownie tego samego zdarzenia. Albo prosty globalny, albo to, co lubię robić, to upewnić się, że tag. na przykład zawiń akcję w If myCheckListBox.Tag! = null, a następnie zamiast zdarzenia Usuń \ Dodaj, po prostu ustaw znacznik na coś (nawet pusty ciąg), a następnie z powrotem na null, aby go ponownie włączyć.
da_jokker
10

Wyprowadzaj CheckedListBoxi wdrażaj

/// <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
źródło
To dużo dodatkowego kodu, który możesz pominąć, korzystając BeginInvokez rozwiązania z drugiej odpowiedzi.
Łukasƨ Fronczyk
4

Chociaż nie jest to idealne rozwiązanie, możesz obliczyć CheckedItems przy użyciu argumentów, które są przekazywane do ItemCheckzdarzenia. Jeśli spojrzysz na ten przykład w witrynie MSDN , możesz ustalić, czy nowo zmieniony element został zaznaczony, czy niezaznaczony, co pozostawia Cię w odpowiedniej pozycji do pracy z elementami.

Możesz nawet utworzyć nowe wydarzenie, które będzie uruchamiane po sprawdzeniu przedmiotu, co da ci dokładnie to, czego chciałeś, jeśli chcesz.

Iain Ward
źródło
1
Czy masz jakiś konkretny pomysł na to, jak można utworzyć to nowe zdarzenie, skąd mogę wiedzieć, kiedy CheckedItems zostały zaktualizowane po zdarzeniu ItemChecke?
hultqvist
4

Po kilku testach mogłem zobaczyć, że zdarzenie SelectedIndexChanged jest wyzwalane po zdarzeniu ItemCheck. Zachowaj właściwość CheckOnClick True

Najlepsze kodowanie

Antonio Leite
źródło
Masz rację, to najłatwiejszy sposób. Ale nadal jest to coś w rodzaju włamania, ponieważ jest to nieudokumentowane i NIESPODZIEWANE zachowanie. Każdy student pierwszego roku w firmie Microsoft może pomyśleć: no cóż, po co odpalać SelectedIndexChanged, gdy zmienia się tylko Checkstate. Zoptymalizujmy to. A Bang idzie twój kod :(
Rolf
Ponadto SelectedIndexChanged nie jest uruchamiany po programowej zmianie stanu sprawdzania.
Rolf
1
I nie uruchamia się, gdy zmienisz stan sprawdzania klawiszem spacji. Używanie tego jest niewłaściwe.
Elmue
2

To działa, ale nie jestem pewien, jak eleganckie jest!

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
źródło
1

Nie wiem, czy to dotyczy, ale chciałem użyć pola listy kontrolnej do filtrowania wyników. Ponieważ użytkownik sprawdzał i odznaczał pozycje, chciałem, aby lista pokazywała \ ukrywała pozycje.

Miałem pewne problemy, które doprowadziły mnie do tego postu. Chciałem tylko podzielić się tym, jak to zrobiłem bez niczego specjalnego.

Uwaga: mam CheckOnClick = true, ale prawdopodobnie nadal działałoby bez

Zdarzenie, którego używam, to „ SelectedIndexChanged

wyliczenie, którego używam, to „ .CheckedItems

To daje rezultaty, których myślę, że możemy się spodziewać. Tak uproszczony, sprowadza się do ...

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
źródło
SelectedIndexChanged nie jest uruchamiany, gdy użytkownik zmieni stan sprawdzania za pomocą klawisza spacji.
Elmue
SelectedIndexChanged nie jest uruchamiany, gdy wywołuje SetItemChecked w celu sprawdzenia lub usunięcia zaznaczenia elementu w kodzie.
bkqc
1

Zakładając, że chcesz zachować argumenty z, ItemCheckale otrzymasz powiadomienie po zmianie modelu, powinno to wyglądać tak:

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

Gdzie CheckedItemsChangedmoże być:

private void CheckedItemsChanged(object sender, EventArgs e)
{
    DoYourThing();
}
Slion
źródło
0

Spróbowałem tego i zadziałało:

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

określić według indeksu listy.

Seon Hyun KIM
źródło
-1

Aby rozwiązać ten problem, używam timera. Włącz licznik czasu za pomocą zdarzenia ItemCheck. Podejmij działanie w wydarzeniu Timer's Tick.

Działa to niezależnie od tego, czy element jest zaznaczony kliknięciem myszy, czy naciśnięciem spacji. Skorzystamy z faktu, że pozycja właśnie zaznaczona (lub odznaczona) jest zawsze pozycją Wybraną.

Przedział czasowy może wynosić zaledwie 1. Zanim zdarzenie Tick zostanie podniesione, nowy status Checked zostanie ustawiony.

Ten kod VB.NET przedstawia koncepcję. Istnieje wiele odmian, które możesz zastosować. Możesz chcieć zwiększyć Interwał licznika czasu, aby umożliwić użytkownikowi zmianę stanu sprawdzania kilku elementów przed podjęciem działania. Następnie w zdarzeniu Tick wykonaj sekwencyjne przekazanie wszystkich elementów na liście lub użyj jego kolekcji CheckedItems, aby podjąć odpowiednią akcję.

Dlatego najpierw wyłączamy Timer w zdarzeniu ItemCheck. Wyłącz, a następnie Włącz powoduje ponowne rozpoczęcie okresu interwału.

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
źródło
1
Dziękuję za udostępnienie. Z drugiej strony, być może możesz dowiedzieć się o lepszych rozwiązaniach z innych odpowiedzi. Korzystanie z timera jest stosunkowo skomplikowane iw tym przypadku jest to niewłaściwe narzędzie do pracy, ponieważ w rzeczywistości otrzymujesz już nowe wartości jako parametry. Możesz więc użyć tej odpowiedzi do rozwiązania jednorazowego lub tej do rozwiązania systemowego. Przekonwertuj je z C # na VB za pomocą jednego z narzędzi do konwersji online.
miroxlav
-1

W normalnym zachowaniu, gdy sprawdzamy jeden element, stan sprawdzenia elementu zmieni się przed wywołaniem procedury obsługi zdarzenia. Ale CheckListBox ma inne zachowanie: program obsługi zdarzeń jest wywoływany przed zmianą stanu sprawdzania elementu, co utrudnia poprawianie naszych zadań.

Moim zdaniem, aby rozwiązać ten problem, powinniśmy odroczyć obsługę zdarzeń.

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
źródło