niedziela, 19 marca 2017

Eventy i dezaktywacja przycisków.

Lubię pisać dobry kod, a przede wszystkim czytelny oraz oparty na jednorodnych rozwiązaniach. Nie mogłem zrozumieć dlaczego dezaktywację przycisków zaprojektowano w taki właśnie sposób i dlaczego zmiana atrybutu "state" na "disabled" nie powoduje zatrzymania wywoływania przypisywanej metody. Postanowiłem zbadać sprawę i znalazłem wytłumaczenie.

W Tkinter istnieją dwa sposoby na przypisanie metody do przycisku (a tak na prawdę do każdego widget'u). Można to zrobić za pomocą metody "bind" oraz za pomocą atrybutu "command" będącego w klasie samego widget'u. Czyli odpowiednio:

self.button = ttk.Button(root, text='Press to activate', 
    command=self.method_to_execute)
self.button.place(x=0, y=0)

lub

self.button = ttk.Button(root, text='Press to activate')
self.button.place(x=0, y=0)
self.button.bind('<Button-1>', self.method_to_execute)

Otóż zmiana atrybutu "state" na wartość "disabled" powoduje dezaktywację przycisku jeśli przypisano mu metodę bezpośrednio do atrybutu "command". Jeśli do przypisania metody użyje się ".bind" to mimo zmiany wyglądu przycisku na nieaktywny, po jego naciśnięciu przypisana metoda zostanie wykonana. Jedynym sposobem na zablokowanie wykonywania tej metody jest użycie metody "unbind" na instancji klasy. Co będzie wyglądało następująco:

self.button.unbind('<Button-1>')

Do metody "unbind" można jedynie przekazać event, który ma zostać odłączony, jeśli wraz z event'em zostanie przekazana metoda, kompilator wywali błąd.
Po zmianie statusu przycisku na aktywny, potrzebne jest ponowne użycie metody "bind", co wiąże się również z przekazywaniem metody, która ma zostać po naciśnięciu przycisku wykonana. Nie jest to zbyt dobre rozwiązanie, ponieważ ilość kodu rośnie, a wraz z nim możliwość powstania błędów. Jednak ma tą zaletę nad "command", że pozwala na przypisywanie różnych event'ów, "command" jest wykonywane po naciśnięciu lewego przycisku myszy. Chociaż nie mogę powiedzieć, że klikanie innym przyciskiem nie działa, ponieważ sam nigdy tego nie próbowałem :)

Niedogodności związane z koniecznością używania "bind" oraz "unbind" można w pewien sposób złagodzić, umieszczając instancje przycisków w słowniku. W ten sposób używając odpowiednich kluczy oraz instrukcji warunkowych można zaimplementować nie najgorsze rozwiązanie, jednak jeśli do przycisk korzysta z więcej niż jednego event'u, to rozwiązanie to zaczyna się komplikować. Dodatkowo jeśli wraz z event'em przekazujemy do metody pewne zmienne to złożoność dodatkowo rośnie. Przykładowa implementacja nie wygląda zbyt ładnie:

for k, v in self.buttons.items():
    if k == 'button_a':
        v.bind('<Button-1>', lambda event, var_1=(1, 2, 3, 5, 7),
            block=self.block_size:self.process_method(event, var_1, block))
        v.bind('<Button-2>', lambda event,
            text_index=1:self.show_tip(event, text_index))
        v.bind('<Button-3>', self.rest_process_settings)

Podany przykład dotyczy tylko jednego przycisku, a powstała konstrukcja już jest mało czytelna. Oczywiście można wypchnąć to do zewnętrznej klasy, w ten sposób nie będzie zmniejszało czytelności głównej klasy okna, ale nie zmienia to faktu, że ten mało czytelny kod trzeba będzie gdzieś umieścić.

Dość prostym rozwiązaniem jest użycie "command" oraz "bind" w jednej instancji przycisku, jednak nie jestem fanem takiego typu rozwiązań.

Jeśli chcesz dowiedzieć się więcej o eventach zajrzyj np. tu.

Brak komentarzy:

Prześlij komentarz