Autor: | Wojciech Muła |
---|---|
Dodany: | 1.12.2006 |
Aktualizacja: | 6.12.2006 |
Treść
Dla zdarzeń ButtonPress, ButtonRelease, Enter, KeyPress, KeyRelease, Leave, Motion pole state struktury Event jest liczbą. Kolejne bity oznaczają:
Ma to bardzo miłą konsekwencję, ponieważ nie trzeba osobno tworzyć procedur obsługi zdarzeń np. dla różnych modyfikatorów, ale w jednej po prostu sprawdzić stan niektórych bitów tego pola.
Łączenie kodu Pythonowego z TCL-owym polega w przypadku Tkintera na tworzeniu specjalnych funkcji TCL-owych. Tego rodzaju funkcje powstają gdy przy tworzeniu kontrolki podana zostanie opcja command jak również przy podpinaniu funkcji metodami bind, tag_bind oraz protocol (wm_protocol). Co więcej, wszystkie Tkinterowe funkcje zwracają właśnie nazwy TCL-owe, a nie referencje to pythonowych obiektów.
Czy to problem? Wyobraźmy sobie, że chcemy dodać, a nie nadpisać, nową funkcję do kontrolki (opcja command) albo protokołu (metoda protocol). Należałoby odczytać uprzednio przypisaną funkcję, a w nowej ją wywoływać... jakoś. Wystarczy użyć metody widget.tk.call i jako argument podać nazwę funkcji, np. metoda invoke (dostępna w kilku kontrolkach) może zostać zrealizowana następująco:
button.tk.call( button['command'] )
A takie bardziej praktyczne zastosowanie to dołączenie się do łańcucha funkcji obsługujących zdarzenia wysyłane przez menadżery okien:
from Tkinter import * def foo(): print "WM_DELETE_WINDOW" master.tk.call(prot) master = Tk() prot = master.protocol('WM_DELETE_WINDOW') print prot # --> '-1210926108destroy' or similar master.protocol('WM_DELETE_WINDOW', foo) master.mainloop()
W zasadzie nigdy nie spotkałem się z tą informacją w dokumentacji do Tkintera, ale może coś przeoczyłem.
Argument większości funkcji, tagOrId, wcale nie musi być pojedynczym tagiem — można łączyć wiele tagów i identyfikatorów za pomocą wyrażeń logicznych: && (and), || (or), ! (not) oraz ^ (xor), oraz podwyrażeń w nawiasach. Na przykład:
canvas.delete('red && !squere')
Usunie wszystkie obiekty z tagiem 'red' nie zawierające jednocześnie taga 'square'.
Tk umożliwia uzyskanie odpowiedzi, czy kursor znajduje się nad jakimś obiektem (ze statusem normal) — obiekt taki dostaje automatycznie tag 'current' (w Tkinterze stała CURRENT), wówczas wystarczy użyć metody find_withtag.
Ale dla obiektów takich jak wielokąty i łamane może istnieć potrzeba dowiedzenie się, który wierzchołek leży najbliżej kursora. (Wiem, taka funkcja jest trywialna, ale po co dublować funkcjonalność?).
Umożliwia to metoda index — można ją stosować dla wielokątów i łamanych, tj. obiektów tworzonych metodami create_polygon i create_line. Jeśli zostaną podane współrzędne punktu jako "@x,y" wówczas zostanie zwrócony indeks najbliższego wierzchołka:
index = canvas.index(tagOrId, "@%f,%f" % (x, y))
Uwaga, jest to indeks w liście Tk, która jest „spłaszczona” — jeśli pamiętacie wierzchołki jako pary, trzeba wynik podzielić przez 2.
Kształt strzałki jest podawany w argumencie arrowshape i jest definiowany przez trzy liczby:
canvas.create_line(arrow=BOTH, arrowshape(d1, d2, h))
Grot strzałki jest czworokątem — jeśli przyjąć, że odcinek jest poziomy i kończy się w punkcie (0,0), to kolejne wierzchołki mają następujące współrzędne:
W przypadku łamanych i wielokątów istnieje możliwość edycji pojedynczych wierzchołków istniejącego obiektu. I w niektórych przypadkach może być to wygodniejsze; chociaż ja przeważnie podmieniam hurtem wszystkie wierzchołki metodą canvas.coords.
Uwaga co do indeksów: w Tkinterze te obiekty są reprezentowane jako lista „spłaszczona”, tj. [x0, y0, x1, y1, ...], a nie [(x0,y0), (x1,y1)]. Czyli jeśli chcemy zmienić i-ty element, należy podawać argument 2*i.
Jeśli podany zostanie niewłaściwy indeks (nieparzysty, spoza zakresu) Tkinter poradzi sobie; indeksy spoza zakresu zostaną odpowiednio przycięte, a nieparzyste zmniejszone o 1.
Służy do tego metoda dchars. Jeśli podany zostanie tylko jeden argument, wówczas usuwany jest pojedynczy wierzchołek. Jeśli podane zostaną dwa argumenty, wówczas zostaną usunięte wierzchołki z zakresu przez nie określonego.
line = c.create_line(...) c.dchars(line, index) c.dchars(line, index1, index2) c.dchars(line, index, 'end')
Wartość 'end' jest wartością specjalna, odnosi się do zakońcowego indeksu (nie do ostatniego, ale ostatniego plus 1).
Służy do tego metoda insert. Nowy wierzchołek wstawiany jest przed podany indeks. Jeśli indeks ma wartość 'end' wówczas wierzchołek zostanie doklejony na koniec.
line = c.create_line(...) c.insert(line, index, (x, y)) c.insert(line, 'end', (x, y))
Zmiana współrzędnych wierzchołka
line = c.create_line(...) c.insert(line, index, (x, y)) c.dchars(line, index+2)
Pisałem już o tym kilka dni temu, więc tylko przypomnę:
line = c.create_line(...) index = c.index(line, "@%f,%f" % (x, y))
Ku pamięci:
def tag_raise_top(canvas, tagOrId): canvas.tag_raise(tagOrId, 'all') def tag_lower_bottom(canvas, tagOrId): canvas.tag_lower(tagOrId, 'all')
demo:
- LBM na jasnoszarym kwadracie — tag_raise_top
- RBM na jasnoszarym kwadracie — tag_lower_bottom
Rzeczywisty rozmiar okna, nie tylko canvas, ale każdego innego trzeba odczytywać za pomocą metod winfo_width oraz winfo_height; można również użyć winfo_geometry, ale przeważnie potrzebujemy znać tylko wysokość i szerokość kontrolki, a poza tym akurat ta metoda zwraca łańcuch znaków, więc to niespecjalnie wygodne.
Jednak w przypadku canvas wysokość i szerokość nie mówią jeszcze o rzeczywistym widocznym obszarze. Należy odjąć jeszcze szerokość obramowania (borderwidth) oraz szerokość ramki wskazującej, czy kontrolka ma focus, czy nie (highlightthickness):
def canvas_dimensions(canvas): w = canvas.winfo_width() h = canvas.winfo_height() l = int(canvas['highlightthickness']) b = int(canvas['borderwidth']) return (max(w-(l+b), 0.0), max(h-(l+b), 0.0))
Funkcja która zwróci współrzędne wyświetlanego prostokąta jest bardzo podobna:
def canvas_viewport(canvas): w = canvas.winfo_width() h = canvas.winfo_height() l = int(canvas['highlightthickness']) b = int(canvas['borderwidth']) x1 = canvas.canvasx(l+b) y1 = canvas.canvasy(l+b) x2 = canvas.canvasx(w - (l+b+1)) y2 = canvas.canvasy(h - (l+b+1)) return x1, y1, x2, y2
Metody scan_mark i scan_dragto są używane wspólnie i służą do zmiany widocznego obszaru (widoku, okna) canvas.
Jak to działa: za pomocą scan_mark zaznaczany jest jakiś punkt Pm, natomiast metodą scan_dragto wybierany jest drugi punkt Pd i całe okno przesuwa się o różnicę: (Pd − Pm) ⋅ a. Czynnik a to przyspieszenie; radzę nie przesadzać z jego wartością.
Na początek dwie uwagi:
Główne przeznaczenie obu metod to realizacja interaktywnego przesuwania zawartości płótna. Na ogół scan_mark woła się raz po kliknięciu myszką, a scan_dragto w funkcji obsługującej zdarzenie z rodzaju <Motion>.
Ale funkcje te mogą być użyte do realizacji funkcji see — funkcji, która tak przesuwa widok, aby wskazany obiekt stał się widoczny. Napisałem to w taki sposób, żeby obiekt znalazł się dokładnie na środku okna:
def see(canvas, item): x1, y1, x2, y2 = canvas.bbox(item) cx = (x1+x2)/2 cy = (y1+y2)/2 xo = canvas.canvasx(0) yo = canvas.canvasy(0) w = canvas.winfo_width() h = canvas.winfo_height() canvas.scan_mark(int(cx), int(cy)) canvas.scan_dragto(int(xo + w/2), int(yo + h/2), 1)
Na początku wyznaczany jest środek obiektu (na podstawie pudełka otaczającego), potem środek okna, a na końcu płótno jest przesuwane, tak by oba punkty się pokryły.
demo:
- LBM + ciągnięcie — przesuwanie zawartości płótna
- LBM + (Ctrl+ciągnięcie) — j.w., większa wartość przyspieszenia
- LBM na kwadracie — przesunięcie widoku tak, by wybrany kwadrat znalazł się na środku okna (zastosowanie funkcji see)
Obiekty umieszczone na canvas mogą znajdować się w czterech stanach:
Stany normal, disabled, hidden mogą być ustawiana przez użytkownika poprzez przypisanie tych łańcuchów znaków do opcji state (uwaga: pusty łańcuch oznacza również stan normal). Natomiast o przypisanie stanu active dba Tk i jest to niezależne od użytkownika
Optyczne sprzężenie zwrotne — Tkinter umożliwia bardzo łatwe wyróżnianie obiektu w zależności od jego stanu, wyręczając użytkownika od wielu zbędnych czynności.
Wyobraźmy sobie, że chcemy, aby aktywny obiekt zmieniał swój kolor, albo grubość linii. Wydawałoby się, że tym celu należałoby coś kombinować ze zdarzeniami (może <Enter>/<Leave>, prędzej <Motion>). Nic z tych rzeczy! Dla opcji: fill, outline, dash, width, stipple, image istnieją odpowiedniki activefill, activeoutline (itd.) oraz disabledfill, disabledoutline (itd.).
Jeśli obiekt jest w stanie normal to używane są zwykłe opcje, jeśli zostanie aktywny, to automatycznie użyte zostaną (o ile są ustawione) opcje active..., a jeśli użytkownik „wyłączy” obiekt, użyte zostaną opcje disabled....
Na początek fragment kodu, dla zilustrowania problemu:
text = "sample text with spaces" id = canvas.create_text(text=text) text2 = cavnas.itemconfigure(id, 'text') print text2
Na konsoli nieoczekiwanie pojawi się:
('sample', 'text', 'with', 'spaces')
Metoda nie zwraca przypisanego łańcucha, ale krotkę — wynik działania: tuple(string.split())! Problem pojawia się po stronie Tkintera.
Rozwiązanie jest dosyć proste, ale wymaga ominięcia Tkintera i odwołania się bezpośrednio do interpretera Tcl-a:
from Tkinter import TclError def canvas_get_text(canvas, text_id): tk = canvas.tk try: result = tk.call(str(canvas), 'itemconfigure', text_id, '-text') return tk.splitlist(result)[-1] except TclError: return ”
Łamane i wielokąty mogą być „wygładzone” — należy ustawić opcję smooth na 1 lub bezier. (Można również definiować własne funkcje wygładzające, ale jest to tylko możliwe na poziomie języka C).
Metoda canvas.bbox zwraca pudełko otaczające wybranego obiektu, czy grupy obiektów; dokumentacja stwierdza, że The return value may overestimate the actual bounding box by a few pixels, czyli nie tak źle. Jednak dla obiektów wygładzonych zwracane jest pudełko punktów zwyczajnego, niewygładzonego obiektu, a nie krzywej, którą można podziwiać na ekranie.
Pudełko otaczające krzywej można jednak dość łatwo wyznaczyć.
Wbudowana metoda wygładzania opiera się, jak można się domyśleć po wartości bezier, na krzywych Beziera, a gdy spojrzeć w dokumentację lub źródła okazuje się, że chodzi o wielomianowe krzywe B-sklejane drugiego stopnia z równomiernym rozkładem węzłów wewnętrznych. A mówiąc po ludzku jest to ciąg krzywych Beziera danych za pomocą trzech punktów kontrolnych.
Aby otrzymać pudełko otaczające taką krzywą wystarczy wyznaczyć pudełka otaczające każdej z krzywych Beziera i na końcu je wszystkie ze sobą połączyć.
Oznaczmy punkty kontrolne krzywej przez Pa, Pb, Pc.
Pudełko otaczające zależy od punktów: