Tkinter - dlaczego w opcji "bind" musi być argument ?

0

Witam. Mam takie jedno pytanie dotyczące "bind". W tym momencie uczę się dopiero pierwszego języka i dlatego niektóre rzeczy są dla mnie niezrozumiałe i możliwe że mało merytorycznie opiszę swój "problem".

Mam jedną z linijek kodu:

"butt_oblicz.bind('<Button-1>', xxx)"

Ma ona na celu przypięcie funkcji do tego przycisku. Wcześniej oczywiście mam zdefiniowaną funkcję "xxx". Nie mogę zrozumieć jednak dlaczego muszę podawać dla niej "positional arguments" skoro i tak go nie używam. Dla mnie logiczne powinno być, że napiszę tak:

def xxx():
treść funkcji

a tak naprawdę kod działa tylko w sytuacji gdy jest napisane:

def xxx(nazwa_argumentu_ktory_nie_ma_totalnie_znaczenia):
treść funkcji

0

argument to obiekt event, z którego możesz wyciągnąć więcej informacji o przyczynie wywołania twojej funkcji. Jak widgetem jest np. Canvas zamiast Button, to tam się może przydać info o pozycji kursora myszy gdy zaszło wydarzenie np. <Button-1>. Tak to API wygląda i musisz stosować się to kontraktu. Zatem musisz pisać funkcję z jednym argumentem, który po prostu sobie ignorujesz:

def obsluga(event):
    pass

jak chcesz bez parametrów to przypisz funkcję bezparametrową w momencie tworzenia buttona:

def click():
    pass
#ver1
bt1 = tk.Button(master=root, text="Click me", command=click)
##ver2
bt1 = tk.Button(master=root, text="Click me")
bt1["command"] = click
0

Każdy argument ma znaczenie. Chociaż ja osobiście tkinter strasznie nie lubie. Już wolę pisać w Lua na silniku LOVE2D jakieś programy.

0

Jeżeli chodzi o to wspomniane położenie kursora to wychodzi mi tak:


def dzialanie_przycisku(zmienna_bez_znaczenia):
    print("Hello")
    print(zmienna_bez_znaczenia)


def okno_aplikacji():
    import tkinter
    okno_glowne = tkinter.Tk()
    butt_oblicz = tkinter.Button(text="Wykonaj działanie")
    butt_oblicz.grid(row=1, column=1)
    butt_oblicz.bind('<Button-1>', dzialanie_przycisku)
    okno_glowne.mainloop()

okno_aplikacji()

Z tego uzyskuję takie informacje:

Hello
<ButtonPress event state=Mod1 num=1 x=69 y=13>

Czyli pozycję kursora o której wspomniałeś.

Jednak powiedzmy, że chciałbym skorzystać z jakiegoś argumentu i piszę już tego typu kod:

def dzialanie_przycisku(imie):
   print(f'Hello {imie}')
   print(imie)

def okno_aplikacji():
   import tkinter
   okno_glowne = tkinter.Tk()
   butt_oblicz = tkinter.Button(text="Wykonaj działanie")
   butt_oblicz.grid(row=1, column=1)
   imie = "Mateusz"
   butt_oblicz.bind('<Button-1>', dzialanie_przycisku(imie))
   okno_glowne.mainloop()


okno_aplikacji()

Co w konsekwencji otrzymuję:

Hello Mateusz
Mateusz

Co oznacza, że wykorzystując ten niepotrzebny argument do własnych celów nie jestem w stanie już uzyskać informacji na temat kliknięcia. Chociaż przyznam, że w tym momencie nie widzę i tak zastosowania dla tego rozwiązania.

0

tutaj jest błąd:

butt_oblicz.bind('<Button-1>', dzialanie_przycisku(imie))

funkcja bind jako drugi parametr oczekuje zmienną typu funkcyjnego, bo później w momencie naciśnięcia przycisku będzie tą twoją funkcję wywoływać. Ty natomiast wstawiłeś wywołanie funkcji, która nie ma instrukcji return. Jak nie ma to znaczy, że taka funkcja w pythonie zwraca None. A skoro bind dostał w podarunku None to nic nie będzie wywołane. Tak jest poprawnie:

butt_oblicz.bind('<Button-1>', dzialanie_przycisku)

Jak chcesz np. przekazać dodatkowy parametr do tego callback-a, żeby np. ta sama funkcja była wywołana przez kilka przycisków z możliwością rozpoznania który z nich został wywołany to można stosować domknięcia (closures), czyli funkcje, które zwracają funkcje. Tutaj mały przykład:

def dzialanie_przycisku(imie, event):
    print(f'Hello {imie}')
    print(imie, event)

def okno_aplikacji():
    import tkinter
    from functools import partial
    okno_glowne = tkinter.Tk()
    butt_oblicz = tkinter.Button(text="Wykonaj działanie")
    butt_oblicz.grid(row=1, column=1)
    butt2_oblicz = tkinter.Button(text="Wykonaj działanie 2")
    butt2_oblicz.grid(row=2, column=1)
    imie = "Mateusz"
    imie2 = "Wojtek"
    butt_oblicz.bind('<Button-1>', partial(dzialanie_przycisku, imie))
    butt2_oblicz.bind('<Button-1>', partial(dzialanie_przycisku, imie2))
    okno_glowne.mainloop()

okno_aplikacji()

0

Jeszcze jedno, jak chcesz koniecznie mieć obsługę przycisku funkcją bezparametrową to możesz zastosować wrapper:

def wrapper(fun):
    def dzialanie(event):
        fun() # ignorujemy event
    return dzialanie

def dzialanie_przycisku():
    print(f'Hello bez parametru')

def okno_aplikacji():
    import tkinter
    from functools import partial
    okno_glowne = tkinter.Tk()
    butt_oblicz = tkinter.Button(text="Wykonaj działanie")
    butt_oblicz.grid(row=1, column=1)
    butt_oblicz.bind('<Button-1>', wrapper(dzialanie_przycisku))
    okno_glowne.mainloop()

okno_aplikacji()
0
jvoytech napisał(a):

funkcja bind jako drugi parametr oczekuje zmienną typu funkcyjnego, bo później w momencie naciśnięcia przycisku będzie tą twoją funkcję wywoływać. Ty natomiast wstawiłeś wywołanie funkcji, która nie ma instrukcji return. Jak nie ma to znaczy, że taka funkcja w pythonie zwraca None. A skoro bind dostał w podarunku None to nic nie będzie wywołane.

Przyznam, że nie do końca rozumiem. Nie mam tam return w definicji funkcji ale przecież bez problemu zwraca mi to co powinno. Jestem dopiero żółtodziobem w tym temacie ale return chyba stosuje się tam gdzie wykorzystuje się jakieś obliczenia, których chciałbym dalej skorzystać a jeżeli mam zwykłe "print" to ten return jest zbędny.

PRZYKŁAD 1

def moja_funkcja(imie):
    print(f'Witaj {imie} !')

moja_funkcja("Stefan")

Co daje:

Witaj Stefan !

PRZYKŁAD 2

def square(e1):
    e1*e1
result = square(2)
print(result)

Co daje

NONE

PRZYKŁAD 3

def square(e1):
    return e1*e1
result = square(2)
print(result)

Co daje w tym wypadku

4
0
sad_madman napisał(a):
jvoytech napisał(a):

funkcja bind jako drugi parametr oczekuje zmienną typu funkcyjnego, bo później w momencie naciśnięcia przycisku będzie tą twoją funkcję wywoływać. Ty natomiast wstawiłeś wywołanie funkcji, która nie ma instrukcji return. Jak nie ma to znaczy, że taka funkcja w pythonie zwraca None. A skoro bind dostał w podarunku None to nic nie będzie wywołane.

Przyznam, że nie do końca rozumiem. Nie mam tam return w definicji funkcji ale przecież bez problemu zwraca mi to co powinno. Jestem dopiero żółtodziobem w tym temacie ale return chyba stosuje się tam gdzie wykorzystuje się jakieś obliczenia, których chciałbym dalej skorzystać a jeżeli mam zwykłe "print" to ten return jest zbędny.

Chodzi o to, że jak wywołujesz

button.bind('<Button-1>', obsluga_przycisku())

to tak jakbyś napisał:

obsluga_przycisku()
button.bind('<Button-1>', None)

czyli przed wywołaniem bind wywołujesz funkcję obsługa_przycisku() a to jest bez sensu. Ta funkcja ma być wywołana za każdym razem jak się ten przycisk naciśnie. W uproszczeniu to mniej więcej wewnętrznie działa tak:

class Widget:
    def bind(self, event, callback):
        self.cb = event, callback
    def run(self, event):
        if self.callback != None:
            self.calback(event)

później metoda run() będzie wewnętrznie wywołana jak nastąpi zdarzenie Button-1. W tej metodzie będzie wołany callback o ile go dostarczysz, tzn. musi to być obiekt funkcyjny z jednym parametrem wejściowym

0

Ja mogę przekazać zmienną w takim przypadku:


def dzialanie_przycisku(imie, event):
    print(f'Hello {imie}')
    print(imie, event)

def dzialanie_przycisku_2(event):
    imie3 = wpisz_imie.get()
    print(f'Hello {imie3, event}')


import tkinter
from functools import partial
okno_glowne = tkinter.Tk()
butt_oblicz = tkinter.Button(text="Drukuje imie1")
butt_oblicz.grid(row=1, column=1)
butt2_oblicz = tkinter.Button(text="Drukuj imie2")
butt2_oblicz.grid(row=2, column=1)
butt3_oblicz = tkinter.Button(text="Drukuj wpisane imie")
butt3_oblicz.grid(row=3, column=1)

wpisz_imie = tkinter.Entry(okno_glowne)
wpisz_imie.grid(row=4, column=1)

imie = "Mateusz"
imie2 = "Wojtek"

butt_oblicz.bind('<Button-1>', partial(dzialanie_przycisku, imie))
butt2_oblicz.bind('<Button-1>', partial(dzialanie_przycisku, imie2))
butt3_oblicz.bind('<Button-1>', dzialanie_przycisku_2)
okno_glowne.mainloop()

Ten kod mi działa ponieważ zmienna "wpisz_imie" jest globalna. Jeżeli jednak chciałbym kod ująć w funkcji albo przerzucić kod "eventu" do oddzielnego modułu to już nie mam do niej dostępu.

0

ok, no to wersja z kodem obsługi w innym module:

# moj_modul.py

def kod_eventu(entry, event):
    imie3 = entry.get()
    print(f'Hello {imie3, event}')
# tkapp.py
import tkinter
from functools import partial

from moj_modul import kod_eventu

okno_glowne = tkinter.Tk()

butt3_oblicz = tkinter.Button(text="Drukuj wpisane imie")
butt3_oblicz.grid(row=3, column=1)

wpisz_imie = tkinter.Entry(okno_glowne)
wpisz_imie.grid(row=4, column=1)

butt3_oblicz.bind('<Button-1>', partial(kod_eventu, wpisz_imie))

okno_glowne.mainloop()
0

Dziękuję. Kod działa ale przyznam, że jest to dla mnie czarna magia. Będę musiał sobie na spokojnie te kody przeanalizować lub ewentualnie wrócić do nich jak moja wiedza z zakresu Pythona się zwiększy.
Nawet kurcze dopisałem sobie kolejny przycisk wraz z polem i podpiąłem także pod funkcję "kod_eventu" i wszystko działa jak powinno.

1 użytkowników online, w tym zalogowanych: 0, gości: 1