2013-07-09 16 views
5

Tło:Ustal, kto oddał wydarzenie

W moim WinForm formie, mam sprawdzone ListView i "master" pole o nazwie checkBoxAll. Zachowanie pana brzmi następująco:

  • Jeżeli Master jest zaznaczone lub odznaczone, wszystkie ListViewItems należy odpowiednio zmienić.

  • Jeśli użytkownik odznaczy element ListViewItem, wzorzec musi się odpowiednio zmienić.

  • Jeśli użytkownik sprawdzi element ListViewItem i wszystkie inne elementy ListViewItems zostaną również sprawdzone, wzorzec musi się odpowiednio zmienić.

Napisałem następujący kod naśladować to zachowanie:

private bool byProgram = false; //Flag to determine the caller of the code. True for program, false for user. 

private void checkBoxAll_CheckedChanged(object sender, EventArgs e) 
{ 
    //Check if the user raised this event. 
    if (!byProgram) 
    { 
     //Event was raised by user! 

     //If checkBoxAll is checked, all listviewitems must be checked too and vice versa. 

     //Check if there are any items to (un)check. 
     if (myListView.Items.Count > 0) 
     { 
      byProgram = true; //Raise flag. 

      //(Un)check every item. 
      foreach (ListViewItem lvi in myListView.Items) 
      { 
       lvi.Checked = checkBoxAll.Checked; 
      } 

      byProgram = false; //Lower flag. 
     } 
    } 
} 

private void myListView_ItemChecked(object sender, ItemCheckedEventArgs e) 
{ 
    //Get the appropiate ListView that raised this event 
    var listView = sender as ListView; 

    //Check if the user raised this event. 
    if (!byProgram) 
    { 
     //Event was raised by user! 

     //If all items are checked, set checkBoxAll checked, else: uncheck him! 

     bool allChecked = true; //This boolean will be used to set the value of checkBoxAll 

     //This event was raised by an ListViewItem so we don't have to check if any exist. 
     //Check all items untill one is not checked. 
     foreach (ListViewItem lvi in listView.Items) 
     { 
      allChecked = lvi.Checked; 
      if (!allChecked) break; 
     } 

     byProgram = true; //Raise flag. 

     //Set the checkBoxAll according to the value determined for allChecked. 
     checkBoxAll.Checked = allChecked; 

     byProgram = false; //Lower flag. 
    } 
} 

W tym przykładzie używam flagę (byProgram), aby upewnić się, że zdarzenie zostało spowodowane przez użytkownika lub nie , zapobiegając w ten sposób nieskończonej pętli (jedno zdarzenie może wystrzelić inną, która może wystrzelić pierwszą itd. itd.). IMHO, to jest zepsute rozwiązanie. Szukałem, ale nie mogłem znaleźć udokumentowanej metody MSDN w celu ustalenia, czy Zdarzenie Kontroli Użytkownika zostało bezpośrednio uruchomione dzięki użytkownikowi. Co mnie zaskakuje (znowu, IMHO).

Wiem, że FormClosingEventArgs ma pole, którego możemy użyć do określenia, czy użytkownik zamyka formularz, czy nie. Ale o ile wiem, jest to jedyny EventArg który zapewnia tego rodzaju funkcjonalność ...

Tak w skrócie:

Czy istnieje sposób (inne niż moim przykładzie), aby ustalić, czy zdarzenie było zwolniony bezpośrednio przez użytkownika?

Uwaga: Nie mam na myśli nadawcy wydarzenia! Nie ma znaczenia, czy zakodowuję someCheckBox.Checked = true; lub ręcznie ustawić someCheckBox, nadawcą zdarzenia zawsze będzie someCheckBox. Chcę się dowiedzieć, czy możliwe jest ustalenie, czy było to przez użytkownika (kliknięcie), czy przez program (.Checked = true).

Aaand: 30% czasu potrzebnego na napisanie tego pytania było prawidłowe sformułowanie pytania i tytułu. Nadal nie masz pewności, czy jest to 100% czyste, więc edytuj, jeśli myślisz, że możesz zrobić to lepiej :)

+0

Po prostu dzikie domysły, ale naprawdę powinno być coś w EventArgs. Czy sprawdziłeś je w czasie wykonywania (debugowanie)? –

+0

W zdarzeniu kliknięcia ustawia flagę, aby wiedzieć, że to połączenie pochodzi z kliknięcia, a nie według kodu? – MEYWD

+0

Użyj 'nadawcy'? – DGibbs

Odpowiedz

2

To jest coś natknąć się dość dużo i co staram się próbować zrobić nie jest podzielić ją między interakcji użytkownika vs programu interakcja - Używam bardziej ogólnego kodu, tzn. interfejs użytkownika jest aktualizowany i nie wymaga żadnych zdarzeń do obsłużenia. Zwykle pakuję to poprzez metody np. BeginUpdate/EndUpdate

private int updates = 0; 

public bool Updating { get { return updates > 0; } } 

public void BeginUpdate() 
{ 
    updates++; 
} 

public void EndUpdate() 
{ 
    updates--; 
} 

public void IndividualCheckBoxChanged(...) 
{ 
    if (!Updating) 
    { 
     // run code 
    } 
} 

public void CheckAllChanged(...) 
{ 
    BeginUpdate(); 
    try 
    { 
     // run code 
    } 
    finally 
    { 
     EndUpdate(); 
    } 
} 
+1

+1, wciąż flaga, ale zamierzam użyć tej konstrukcji, gdy następnym razem będę musiał podnieść flagę. – Jordy

+0

@Jordy to nie jest * kolejna * flaga, to ulepszenie istniejącej flagi poprzez dodanie jej struktury i znaczenia. Wzorzec 'BeginUpdate' /' EndUpdate' jest dobrze przyjętym i przyjętym podejściem do tego rodzaju problemów na różnych platformach (włącznie .NET). Pomysł polega na tym, że reagujesz na "stan" niezależnie od tego, czy jakieś zdarzenie jest dołączone, czy nie, dzięki temu uzyskujemy o wiele bardziej czytelny kod. – James

+0

Rozumiem ... Prawdopodobnie nie powinienem opisywać tego jako "nadal flaga". Ale z jakiegoś powodu nie mogę edytować mojego komentarza, gdy jest za stary lub coś w tym rodzaju: \ – Jordy

4

Zamiast korzystać ze zmienionego wydarzenia, możesz użyć klikniętego wydarzenia, aby przenieść kaskadę zmiany do odpowiednich kontrolek. Byłoby to w odpowiedzi na kliknięcie użytkownika, a nie na zmianę wartości programowo.

+0

+1 za udzielenie rozwiązania, które nie wymaga dodatkowych flag. Ale czy wiesz, że nieruchomość rzeczywiście się zmieniła? To wymagałoby przechowywania starych wartości w porządku? – Jordy

+1

Jeśli dostanę zdarzenie kliknięte w polu wyboru wszystkich, wówczas mogę ustawić wartości wszystkich pól wyboru. Jeśli dostanę zdarzenie kliknięte na jednym z elementów opcji, zaktualizowałbym tylko pole wyboru zaznacz wszystko w zależności od stanu opcji. –

+0

Masz rację! Nie obchodzi mnie, czy wartość nie zmieniła się, czy nie. Mogę po prostu spojrzeć na wartość, którą obecnie posiada. – Jordy

5

Nie, nie ma praktycznego sposobu na ustalenie, czy zmiana nastąpiła z GUI, czy została wykonana przez program (w rzeczywistości można było analizować stację wywoławczą - ale nie jest to zalecane, ponieważ jest bardzo powolna i podatna na błędy).

BTW, jest jeszcze jedna rzecz, którą można zrobić zamiast ustawić byProgram.Można usunąć i dodać obsługi zdarzeń przed lub po odpowiednio zmienić swoje kontrole:

checkBoxAll.CheckedChanged -= checkBoxAll_CheckedChanged; 
// do something 
checkBoxAll.CheckedChanged += checkBoxAll_CheckedChanged; 
+0

To jest to, co regularnie robię. – dotNET

+0

+1 za udzielenie rozwiązania, które nie wiąże się z dodatkowymi zdarzeniami lub flagami! – Jordy

+1

Twierdzę, że nie jest to świetny pomysł na odłączanie/ponowne dołączanie programów obsługi zdarzeń w ten sposób. Poza * niewielkim * obciążeniem, co jeśli masz 20 programów obsługi zdarzeń? Uważam, że kod blokujący jest lepszy, ponieważ reagujesz na "stan", a nie na to, czy zdarzenie jest połączone, czy nie, ponieważ zawsze jest szansa, że ​​zapomnisz o ponownym dołączeniu lub oderwaniu. – James