Mausevent soll so lange aktiv bleiben bis unnötig

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

Hallo,

ich habe mir ein Programm mit tkinter erstellt. Dort ist ein Button und so lange wie der Button gedrückt ist, soll ein Ereignis ausgeführt werden.
Um das hinzukriegen, werte ich zwei Ereignisse aus - Maustaste gedrückt und Maustaste losgelassen. Der Status wird in einer Statusvariable gespeichert.

Maustaste gedrückt ruft einen Thread auf, in dem die Schleife läuft. Zuerst hatte ich die Schleife noch in der Funktion, die auf Maustaste gedrückt reagiert. Das Event Maustaste losgelassen reagierte dann nicht mehr drauf - darum habe ich die while-Schleife in einen separaten Thread ausgelagert. Es wird vorher geprüft, ob so ein Thread schon läuft, damit nicht zwei Threads aufgerufen werden koennen.

Ist das so einigermaßen brauchbar gelöst oder gibt es (bessere) Varianten?

Code: Alles auswählen

import tkinter
from time import sleep as Schlafen
from threading import Thread

class State():
    def __init__(self):
        self._state = False
    def set_state(self, state):
        self._state = state
    def get_state(self):
        return self._state

mystate = State()
print (mystate.get_state())
master = tkinter.Tk()
myCanvas = tkinter.Canvas(master)

rect1 = myCanvas.create_rectangle(1,1,100,100, fill="blue")
rect2 = myCanvas.create_rectangle(1,100,100,200, fill="green")

def mouse_button_1_pressed(event):
    print ("Mausbutton gedrückt")
    if mystate.get_state() == False:
        mystate.set_state(True)
        t = Thread(target=while_Schleife)
        t.start()

def mouse_button_1_released(event):
    print ("Mausbutton losgelassen")
    mystate.set_state(False)

def while_Schleife():
    while mystate.get_state() == True:
        print ("Maustaste immer noch gedrückt")
        x2=myCanvas.coords(rect1)[2]
        myCanvas.coords(rect1, 1,1,x2+3,100)
        Schlafen(0.5)


myCanvas.pack()
mybutton = tkinter.Button (master, text="Druecke Taste")
mybutton.bind("<Button-1>", mouse_button_1_pressed)
mybutton.bind("<ButtonRelease-1>", mouse_button_1_released)

mybutton.pack()

master.mainloop()
Zuletzt geändert von Papp Nase am Dienstag 12. August 2014, 14:46, insgesamt 1-mal geändert.
BlackJack

@Papp Nase: Die `State`-Klasse ist „unpythonisch” mit den trivialen Get/Set-Methoden. Da kann man auch direkt auf das Attribut zugreifen. Letztendlich ist das hier aber nur ein leicht verschleiertes ``global``. Man soll nicht nur das Schlüsselwort ``global`` vermeiden, sondern globalen Zustand dann auch nicht mit anderen Mitteln zusammenbasteln. Wenn man schon eine Klasse schreibt, dann kann man auch gleich die Funktionen und den Zustand in einem Objekt zusammenfassen und nicht die Funktionen ein anderes Objekt als globale Variable missbrauchen lassen.

Das geht auch ohne Thread mit der `after()`-Methode auf Widgets. Statt des Flags könnte man sich dann die ID vom Timer oder `None` merken wenn kein `after()` aussteht.

Der Namenspräfix `my` ist ein „code smell”. Warum nicht einfach `state`, was bringt dieses `my` an zusätzlicher Information?
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

BlackJack hat geschrieben:@Papp Nase: Die `State`-Klasse ist „unpythonisch” mit den trivialen Get/Set-Methoden. Da kann man auch direkt auf das Attribut zugreifen. Letztendlich ist das hier aber nur ein leicht verschleiertes ``global``. Man soll nicht nur das Schlüsselwort ``global`` vermeiden, sondern globalen Zustand dann auch nicht mit anderen Mitteln zusammenbasteln. Wenn man schon eine Klasse schreibt, dann kann man auch gleich die Funktionen und den Zustand in einem Objekt zusammenfassen und nicht die Funktionen ein anderes Objekt als globale Variable missbrauchen lassen.

Das geht auch ohne Thread mit der `after()`-Methode auf Widgets. Statt des Flags könnte man sich dann die ID vom Timer oder `None` merken wenn kein `after()` aussteht.

Der Namenspräfix `my` ist ein „code smell”. Warum nicht einfach `state`, was bringt dieses `my` an zusätzlicher Information?
Vielen Dank für Deine Antwort, ich habe mich sehr drüber gefreut. Ja - man kann es im nächsten Schritt auch in einer großen Klasse zusammen fassen. Aber ich bin doch noch Anfänger. Ich wollte jetzt erstmal, dass das mit der Maus überhaupt so klappt, dass ich jetzt den Rest auch in eine Klasse einfüge, das kommt dann halt als nächster Schritt.

Mir ging es hier um die Frage, ob
- die Sache mit dem Thread so ok ist
- das Setzen eines Statusflags, um auf die Mausevents zu reagieren
Papp Nase hat geschrieben:Das geht auch ohne Thread mit der `after()`-Methode auf Widgets. Statt des Flags könnte man sich dann die ID vom Timer oder `None` merken wenn kein `after()` aussteht.
Dass probiere ich auch gleich mal aus.
BlackJack

@Papp Nase: Das Statusflag ist ein wenig redundant. Man könnte dafür `None` und das Thread-Objekt verwenden.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Also ich würde solche GUI Dinge direkt in eine Klasse kapseln. Dann kann man auch gleicht eine status variable definieren.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Papp Nase: Auf 'True' und 'False' wird normalerweise nicht explizit geprüft, jede Bedingung wird immer auf einen Wahrheitswert geprüft und 'x == False' kann man über 'not x' eleganter schreiben.
Den Zustand von GUI-Frameworks sollte man nicht in einem seperaten Thread ändern, weil das zu unkontrolliertem Verhalten führen kann. Die after-Methode ist hier eindeutig die richtige Lösung.
Die State-Klasse hat keinen Vorteil gegenüber einer einfachen boolschen Variable.
BlackJack

Das ganze mal ohne Thread:

Code: Alles auswählen

# coding: utf8
import Tkinter as tk


class Application(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.canvas = tk.Canvas(self)
        self.bar_id = self.canvas.create_rectangle(1, 1, 100, 100, fill='blue')
        self.canvas.create_rectangle(1, 100, 100, 200, fill='green')
        self.canvas.pack()
        button = tk.Button(self, text=u'Drück mich')
        button.pack()
        button.bind('<Button-1>', self.start_movement)
        button.bind('<ButtonRelease-1>', self.stop_movement)
        self.timer_id = None

    def _move(self):
        x_1, y_1, x_2, y_2 = self.canvas.coords(self.bar_id)
        self.canvas.coords(self.bar_id, x_1, y_1, x_2 + 3, y_2)
        self.timer_id = self.after(500, self._move)

    def start_movement(self, _event=None):
        if self.timer_id is None:
            self._move()

    def stop_movement(self, _event=None):
        self.after_cancel(self.timer_id)
        self.timer_id = None


def main():
    root = tk.Tk()
    application = Application(root)
    application.pack()
    root.mainloop()


if __name__ == '__main__':
    main()
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

BlackJack hat geschrieben:Das ganze mal ohne Thread:
Das find ich total lieb von Dir, dass Du mir ein Beispiel mit der after-Funktion erstellt hast. Ich versuche grade, das zu verstehen und in mein Programm einzubauen.

Das habe ich jetzt mal fabriziert:

Code: Alles auswählen

try:
    import tkinter as tkinter
except:
    import Tkinter as tkinter

import time
from threading import Thread
from functools import partial

class Balkenanzeige():
    def __init__(self, canvas=tkinter.Canvas,  minpos=0, maxpos=300):
        self._minpos=minpos
        self._maxpos=maxpos
        self._pos=0
        self._canvas=canvas
        self._objectno=0

        self.__create()

    def __create(self):
        self._objectno = self._canvas.create_rectangle(self._minpos, 0, self._maxpos, 280, fill="green")

    def set_pos(self, newpos):
        self._pos=newpos
        if self._pos > 250: self._pos=250
        if self._pos < 5: self._pos=5
        self._canvas.coords(self._objectno, self._minpos, 0, self._pos, 280)

    def get_pos(self):
        return self._pos

class Schieberegler():
    def __init__(self, canvas=tkinter.Canvas, inkrement=5, keydelay_ms=100):
        self.__canvas = canvas
        self.__balkenanzeigen_canvas = tkinter.Canvas()
        self.__inkrement = inkrement
        self.__after_timer_id = None
        self.__keydelay_ms = keydelay_ms

        self.__create()

    def __create(self):
        # Objekte definieren
        self.__balkenanzeige = Balkenanzeige(self.__balkenanzeigen_canvas)
        self.__inc_button = tkinter.Button(self.__canvas, text="  INC  ", width=10)
        self.__dec_button = tkinter.Button(self.__canvas, text="  DEC  ", width=10)

        # Events definieren
        self.__dec_button.bind('<Button-1>', self.__dec_button_event_start)
        self.__dec_button.bind('<ButtonRelease-1>', self.__dec_button_event_stop)
        self.__inc_button.bind('<Button-1>', self.__inc_button_event_start)
        self.__inc_button.bind('<ButtonRelease-1>', self.__inc_button_event_stop)

        # Objekte anordnen
        self.__balkenanzeigen_canvas.pack()
        self.__inc_button.pack()
        self.__dec_button.pack()

    def __inc_button_event_start(self, _event=None):
        if self.__after_timer_id is None:
            self.__move_balkenanzeige(self.__inkrement)

    def __inc_button_event_stop(self, _event=None):
        self.__canvas.after_cancel(self.__after_timer_id)
        self.__after_timer_id = None

    def __dec_button_event_start(self, _event=None):
        if self.__after_timer_id is None:
            self.__move_balkenanzeige(self.__inkrement*(-1))

    def __dec_button_event_stop(self, _event=None):
        self.__canvas.after_cancel(self.__after_timer_id)
        self.__after_timer_id = None

    def __move_balkenanzeige(self, value):
        self.__balkenanzeige.set_pos(self.__balkenanzeige.get_pos()+value)
        self.__after_timer_id = self.__canvas.after(self.__keydelay_ms, partial(self.__move_balkenanzeige, value))

def main():
    root = tkinter.Tk()
    root.title ("Balkenanzeige")
    canvas_obj = tkinter.Canvas(root)
    Regleranzeige = Schieberegler(canvas_obj)
    canvas_obj.pack()

    root.mainloop()


if __name__ == "__main__":
    main()
Antworten