Frage zu Tkinter und threading

Fragen zu Tkinter.
Antworten
Jannis
User
Beiträge: 2
Registriert: Montag 30. November 2015, 10:29

Hallo, was Python angeht, bin ich wohl noch ein Anfänger. Momentan versuche ich in Python 2.7 die "OOP" und das "multithreading" zu verstehen. Mein Code ist bestimmt noch nicht besonders elegant strukturiert, das mögt Ihr mir bitte nachsehen.
Ich habe eine Class Lock definiert und von ihr die Objekte lock1 und lock2 abgeleitet. Mittels dem Modul threading würde ich nun gerne lock1 und lock2 gleichzeitig in Bewegung setzen. Letzteres gelingt mir jedoch überhaupt nicht, es setzt sich immmer nur eine der beiden Locks in Bewegung. :(
Wie bekomme ich es hin beide Locks gleichzeitig in Bewegung zu setzen?
Es währe echt toll von Euch, wenn Ihr mir helfen könntet!!!
Vielen Dank schon mal im Voraus.

Code: Alles auswählen

from Tkinter import *
from threading import Thread
import pygame,sys,random,time

fenster = Tk()
fenster.geometry("1600x1000")
zeichner = Canvas(fenster, width=1600, height=800) 
zeichner.pack() 
zeichner.place(x=100,y=300)

pygame.init()                                           
pygame.mixer.init()                                     
screen = pygame.display.set_mode([100,50])             
pygame.time.delay(500)

class Lock(Thread):
    __koordinate_fh_1 = 0          
    __koordinate_fh_2 = 0
    __koordinate_fh_3 = 0
    __koordinate_fh_4 = 0
    __koordinate_sa_1 = 0
    __koordinate_sa_2 = 0
    __koordinate_sa_3 = 0
    __koordinate_sa_4 = 0
    __koordinate_sa_5 = 0
    __koordinate_sa_6 = 0
    __koordinate_s_1 = 0
    __koordinate_s_2 = 0
    __koordinate_s_3 = 0
    __koordinate_s_4 = 0
    __koordinate_lk_1 = 0
    __koordinate_lk_2 = 0
    __koordinate_lk_3 = 0
    __koordinate_lk_4 = 0
    __koordinate_vr_1 = 0
    __koordinate_vr_2 = 0
    __koordinate_vr_3 = 0
    __koordinate_vr_4 = 0
    __koordinate_hr_1 = 0
    __koordinate_hr_2 = 0
    __koordinate_hr_3 = 0
    __koordinate_hr_4 = 0
    __lockfarbe = ""
    __radfarbe = ""
           
    def __init__(self,koordinate_fh_1,koordinate_fh_2,koordinate_fh_3,koordinate_fh_4,koordinate_sa_1,koordinate_sa_2,koordinate_sa_3, \
                 koordinate_sa_4,koordinate_sa_5,koordinate_sa_6,koordinate_s_1,koordinate_s_2,koordinate_s_3,koordinate_s_4,koordinate_lk_1, \
                 koordinate_lk_2,koordinate_lk_3,koordinate_lk_4,koordinate_vr_1,koordinate_vr_2,koordinate_vr_3,koordinate_vr_4,koordinate_hr_1, \
                 koordinate_hr_2,koordinate_hr_3,koordinate_hr_4,lockfarbe,radfarbe):

        Thread.__init__(self)
        self.__koordinate_fh_1 = koordinate_fh_1          
        self.__koordinate_fh_2 = koordinate_fh_2
        self.__koordinate_fh_3 = koordinate_fh_3
        self.__koordinate_fh_4 = koordinate_fh_4
        self.__koordinate_sa_1 = koordinate_sa_1
        self.__koordinate_sa_2 = koordinate_sa_2
        self.__koordinate_sa_3 = koordinate_sa_3
        self.__koordinate_sa_4 = koordinate_sa_4
        self.__koordinate_sa_5 = koordinate_sa_5
        self.__koordinate_sa_6 = koordinate_sa_6
        self.__koordinate_s_1 = koordinate_s_1
        self.__koordinate_s_2 = koordinate_s_2
        self.__koordinate_s_3 = koordinate_s_3
        self.__koordinate_s_4 = koordinate_s_4
        self.__koordinate_lk_1 = koordinate_lk_1
        self.__koordinate_lk_2 = koordinate_lk_2
        self.__koordinate_lk_3 = koordinate_lk_3
        self.__koordinate_lk_4 = koordinate_lk_4
        self.__koordinate_vr_1 = koordinate_vr_1
        self.__koordinate_vr_2 = koordinate_vr_2
        self.__koordinate_vr_3 = koordinate_vr_3
        self.__koordinate_vr_4 = koordinate_vr_4
        self.__koordinate_hr_1 = koordinate_hr_1
        self.__koordinate_hr_2 = koordinate_hr_2
        self.__koordinate_hr_3 = koordinate_hr_3
        self.__koordinate_hr_4 = koordinate_hr_4
        self.__lockfarbe = lockfarbe
        self.__radfarbe = radfarbe
        
        self.fuehrerhaeuschen = zeichner.create_rectangle(koordinate_fh_1,koordinate_fh_2,koordinate_fh_3,\
        koordinate_fh_4,fill=lockfarbe,width=1)
        self.schlotausgang = zeichner.create_polygon(koordinate_sa_1,koordinate_sa_2,koordinate_sa_3,\
        koordinate_sa_4,koordinate_sa_5,koordinate_sa_6,fill=lockfarbe,outline="black",width=1)
        self.schlot = zeichner.create_rectangle(koordinate_s_1,koordinate_s_2,koordinate_s_3,\
        koordinate_s_4,fill=lockfarbe,width=1)
        self.lockkoerper = zeichner.create_rectangle(koordinate_lk_1,koordinate_lk_2,koordinate_lk_3,koordinate_lk_4,fill=lockfarbe,width=1)
        self.vorderrad = zeichner.create_oval(koordinate_vr_1,koordinate_vr_2,koordinate_vr_3,\
        koordinate_vr_4,fill=radfarbe,width=2)
        self.hinterrad = zeichner.create_oval(koordinate_hr_1,koordinate_hr_2,koordinate_hr_3,\
        koordinate_hr_4,fill=radfarbe,width=2)

    def run(self):
        self.fahren()

    def fahren(self):
        a = range(1,170)
        for i in a:
            zeichner.move(1,7,0)
            zeichner.move(2,7,0)
            zeichner.move(3,7,0)
            zeichner.move(4,7,0)
            zeichner.move(5,7,0)
            zeichner.move(6,7,0)
            fenster.update()
            time.sleep(0.2)
            

lock1 = Lock(80,89,140,150,188,93,231,93,210,130,220,109,200,200,80,149,250,219,200,200,250,250,80,200,130,250,"red","yellow")
lock2 = Lock(260, 269, 320, 330, 368, 273, 411, 273, 390, 310, 400, 289, 380, 380, 260, 329, 430, 399, 380, 380, 430, 430, 260, 380, 310, 430,"blue","yellow")

lock1.start()
lock2.start()

mainloop()  
BlackJack

@Jannis: Warum denkst Du denn das mehr als eine Lok(omitive) (ohne 'c'!) bewegt werden sollte von dem Code? Mich hätte jetzt nicht einmal überrascht wenn gar nichts oder nur Teile bewegt würden.

Die Leerzeichensetzung entspricht nicht dem Style Guide for Python Code. Alles so zusammen zu quetschen ist schlecht lesbar. Du machst ja auch im Text des Beitrags aus dem gleichen Grund nach Kommas und Satzzeichen ein Leerzeichen.

Sternchenimporte sollte man vermeiden. Gerade bei `Tkinter` holt man sich damit Unmengen von Namen in das Modul die man gar nicht braucht.

Die Module `random` und `sys` werden importiert, aber gar nicht verwendet.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Das hat dann auch zur Folge das Du `zeichner` nicht einfach so in den Methoden der `Lock`-Klasse (sic!) verwenden kannst, sondern das Objekt auch als Argument beim erstellen angeben musst und es an das Objekt binden musst, falls es in anderen Methoden verwendet werden soll. Bei der Vermischung auf Modulebene fällt hier ausserdem noch negativ auf, dass das Hauptprogramm nicht an einem Stück im Quelltext steht sondern von der Klassendefinition unterbrochen wird.

Die Grösse des Fensters sollte man hier nicht vorgeben müssen weil die sich aus der Grösse des `Canvas`-Exemplars automatisch ergibt. Sofern man nicht nach dem `pack()` noch mal ein unsinniges `place()` macht. `place()` ist in aller Regel kein sinvolles Mittel um portierbare und benutzbare GUI-Layouts zu erstellen.

Die ganzen Klassenattribute sind vollkommener Unsinn. Ich glaube ich habe so etwas hier schon mal gesehen, auch bei jemandem der ein `Canvas`-Exemplar als `zeichner` benannt hat, was auch irgendwie falsch ist, denn `Canvas` heisst auf Deutsch „Leinwand“. Kommt das aus einem Buch oder Tutorial? Auf jeden Fall weg mit den Klassenattributen!

Auch bei den Exemplaren macht es keinen Sinn diese ganzen Werte an das Objekt zu binden. Auch da können die ganzen Zeilen dafür weg.

Den \ am Zeilenende braucht man nur wenn es keine offenen Klammern mehr gibt, an denen der Compiler erkennt, dass die ”logische” Zeile noch nicht zuende sein kann.

Die Übergabe von dermassen vielen Argumenten ist unübersichtlich. Gerade beim Aufruf sieht man doch überhaupt nicht mehr welche Zahl eigentlich zu welchem Teil der Grafik gehört. Das macht Aufgaben wie das verschieben der Koordinaten sehr fehleranfällig. Da hast Du sowieso eine tolle Leistung vollbracht das für beide Lokomotiven für die Startposition auszurechnen. Aber eigentlich sind Programmierer faul und überlassen so etwas lieber dem Rechner. Da würde man eher die Koordinaten für eine Lok im Koordinatenursprung hin schreiben und dann beide Loks erzeugen und dann vom Rechner an die gewünschte Startposition verschieben lassen. Eine Methode zum Verschieben kann man führ das fahren ja schon ganz gut gebrauchen.

Von Threads würde man eher keinen gebrauch machen. GUIs und Threads vertragen sich nicht immer ohne das man spezielle Sachen berücksichtigen muss. GUI-Rahmenwerke haben aber in der Regel eine Möglichkeit asynchrone Aufrufe in die GUI-Hauptschleife zu schleusen. So auch `Tkinter` mit der `after()`-Methode.

Ich lande dann als Zwischenergebnis hier:

Code: Alles auswählen

import Tkinter as tk
import time


class Lok(object):
    # 
    # TODO Zu viele Argumente mit zu kryptischen Namen.  Das sollte irgendwie
    #   sinnvoll in eine Datenstruktur oder vielleicht sogar eigene Datentypen
    #   gesteckt werden.
    # 
    def __init__(
        self, canvas, koordinate_fh_1, koordinate_fh_2, koordinate_fh_3,
        koordinate_fh_4, koordinate_sa_1, koordinate_sa_2, koordinate_sa_3,
        koordinate_sa_4, koordinate_sa_5, koordinate_sa_6, koordinate_s_1,
        koordinate_s_2, koordinate_s_3, koordinate_s_4, koordinate_lk_1,
        koordinate_lk_2, koordinate_lk_3, koordinate_lk_4, koordinate_vr_1,
        koordinate_vr_2, koordinate_vr_3, koordinate_vr_4, koordinate_hr_1,
        koordinate_hr_2, koordinate_hr_3, koordinate_hr_4, lokfarbe, radfarbe
    ):
        self.canvas = canvas
        fuehrerhaeuschen = self.canvas.create_rectangle(
            koordinate_fh_1, koordinate_fh_2, koordinate_fh_3, koordinate_fh_4,
            fill=lokfarbe, width=1
        )
        schlotausgang = self.canvas.create_polygon(
            koordinate_sa_1, koordinate_sa_2, koordinate_sa_3, koordinate_sa_4,
            koordinate_sa_5, koordinate_sa_6,
            fill=lokfarbe, outline='black', width=1
        )
        schlot = self.canvas.create_rectangle(
            koordinate_s_1, koordinate_s_2, koordinate_s_3, koordinate_s_4,
            fill=lokfarbe, width=1
        )
        lok_koerper = self.canvas.create_rectangle(
            koordinate_lk_1, koordinate_lk_2, koordinate_lk_3, koordinate_lk_4,
            fill=lokfarbe, width=1
        )
        vorderrad = self.canvas.create_oval(
            koordinate_vr_1, koordinate_vr_2, koordinate_vr_3, koordinate_vr_4,
            fill=radfarbe, width=2
        )
        hinterrad = self.canvas.create_oval(
            koordinate_hr_1, koordinate_hr_2, koordinate_hr_3, koordinate_hr_4,
            fill=radfarbe, width=2
        )
        self.parts = [
            fuehrerhaeuschen, schlotausgang, schlot, lok_koerper,
            vorderrad, hinterrad,
        ]

    def move(self, x, y):
        for part in self.parts:
            self.canvas.move(part, x, y)

    def fahren(self, steps, step_distance, delay=200):
        if steps:
            self.move(step_distance, 0)
            self.canvas.after(delay, self.fahren, steps - 1, delay)


def main():
    root = tk.Tk()
    canvas = tk.Canvas(root, width=1000, height=600)
    canvas.pack(fill=tk.X)
    # 
    # TODO Statt die ganzen Koordinaten für beide Lokomotiven auszurechnen,
    #   sollte man das nur einmal tun.  Für eine Lokomotive die im
    #   Koordinatenursprung steht.  Und die kann man dann für beide Loks
    #   unterschiedlich verschieben.
    # 
    lok_a = Lok(
        canvas, 80, 89, 140, 150, 188, 93, 231, 93, 210, 130, 220, 109, 200,
        200, 80, 149, 250, 219, 200, 200, 250, 250, 80, 200, 130, 250,
        'red', 'yellow'
    )
    lok_b = Lok(
        canvas, 260, 269, 320, 330, 368, 273, 411, 273, 390, 310, 400, 289,
        380, 380, 260, 329, 430, 399, 380, 380, 430, 430, 260, 380, 310, 430,
        'blue', 'yellow'
    )
    for lok in [lok_a, lok_b]:
        lok.fahren(170, 7)

    root.mainloop() 


if __name__ == '__main__':
    main()
Jannis
User
Beiträge: 2
Registriert: Montag 30. November 2015, 10:29

Hallo BlackJack, vielen Dank für Deine ausführliche Antwort und Deine sehr hilfreichen Kommentare !!!
Ich habe zwar jetzt nicht alles von Deinem Code übernommen, aber dennoch fahren nun auch bei mir die beiden Loks.
Ich habe es sogar hinbekommen, die zwei Loks unterschiedlich schnell fahren zu lassen. :D

Hier mal mein Code:

Code: Alles auswählen

from thread import *
from Tkinter import *
import  time

class Lok():           
    def __init__(self, canvas, koordinate_fh_1, koordinate_fh_2, koordinate_fh_3, koordinate_fh_4,
                 koordinate_sa_1, koordinate_sa_2, koordinate_sa_3, koordinate_sa_4, koordinate_sa_5, koordinate_sa_6,
                 koordinate_s_1,koordinate_s_2, koordinate_s_3, koordinate_s_4,
                 koordinate_lk_1, koordinate_lk_2, koordinate_lk_3, koordinate_lk_4,
                 koordinate_vr_1, koordinate_vr_2, koordinate_vr_3, koordinate_vr_4,
                 koordinate_hr_1, koordinate_hr_2, koordinate_hr_3, koordinate_hr_4, lokfarbe, radfarbe):

        self.canvas = canvas

        self.koordinate_fh_1 = koordinate_fh_1          
        self.koordinate_fh_2 = koordinate_fh_2
        self.koordinate_fh_3 = koordinate_fh_3
        self.koordinate_fh_4 = koordinate_fh_4
        
        self.koordinate_sa_1 = koordinate_sa_1
        self.koordinate_sa_2 = koordinate_sa_2
        self.koordinate_sa_3 = koordinate_sa_3
        self.koordinate_sa_4 = koordinate_sa_4
        self.koordinate_sa_5 = koordinate_sa_5
        self.koordinate_sa_6 = koordinate_sa_6
        
        self.koordinate_s_1 = koordinate_s_1
        self.koordinate_s_2 = koordinate_s_2
        self.koordinate_s_3 = koordinate_s_3
        self.koordinate_s_4 = koordinate_s_4
        
        self.koordinate_lk_1 = koordinate_lk_1
        self.koordinate_lk_2 = koordinate_lk_2
        self.koordinate_lk_3 = koordinate_lk_3
        self.koordinate_lk_4 = koordinate_lk_4
        
        self.koordinate_vr_1 = koordinate_vr_1
        self.koordinate_vr_2 = koordinate_vr_2
        self.koordinate_vr_3 = koordinate_vr_3
        self.koordinate_vr_4 = koordinate_vr_4
        
        self.koordinate_hr_1 = koordinate_hr_1
        self.koordinate_hr_2 = koordinate_hr_2
        self.koordinate_hr_3 = koordinate_hr_3
        self.koordinate_hr_4 = koordinate_hr_4

        self.lokfarbe = lokfarbe
        self.radfarbe = radfarbe
        
        self.fuehrerhaeuschen = self.canvas.create_rectangle(koordinate_fh_1, koordinate_fh_2, koordinate_fh_3, koordinate_fh_4, 
        fill=lokfarbe, width=1) self.schlotausgang = self.canvas.create_polygon(koordinate_sa_1,koordinate_sa_2, koordinate_sa_3, 	
        koordinate_sa_4, koordinate_sa_5, koordinate_sa_6, fill=lokfarbe, outline="black", width=1)self.schlot = 
        self.canvas.create_rectangle(koordinate_s_1, koordinate_s_2, koordinate_s_3, koordinate_s_4,fill=lokfarbe, width=1)
        self.lok_koerper = self.canvas.create_rectangle(koordinate_lk_1, koordinate_lk_2, koordinate_lk_3, koordinate_lk_4, fill=lokfarbe, width=1)
        self.vorderrad = self.canvas.create_oval(koordinate_vr_1, koordinate_vr_2,koordinate_vr_3, koordinate_vr_4, fill=radfarbe, width=2)
        self.hinterrad = self.canvas.create_oval(koordinate_hr_1, koordinate_hr_2, koordinate_hr_3, koordinate_hr_4, fill=radfarbe, width=2)


    def langsam_fahren(self):
        x = 0
        while x < 150:
            x += 1
            for i in range(1,7):
                self.canvas.move(i,7,0)
            time.sleep(0.04)

    def schnell_fahren(self):
        x = 0
        while x < 150:
            x += 1
            for i in range(1,7):
                self.canvas.move(i,7,0)
            time.sleep(0.01)
            
def main():
    
    root = Tk()
    root.geometry("1600x900")
    canvas1 = Canvas(root, width=1600, height=250) 
    canvas1.pack()

    canvas2 = Canvas(root, width=1800, height=450) 
    canvas2.pack()

    lok_1 = Lok(canvas1, 80,89,140,150,188,93,231,93,210,130,220,109,200,200,80,149,250,219,200,200,250,250,80,200,130,250,"red","yellow")
    start_new_thread(lok_1.schnell_fahren,())
    
    lok_2 = Lok(canvas2, 260, 269, 320, 330, 368, 273, 411, 273, 390, 310, 400, 289, 380, 380, 260, 329, 430, 399, 380, 380, 430, 430, 260, 380, 310, 430,"blue","yellow")
    start_new_thread(lok_2.langsam_fahren,())

    root.mainloop() 

main() 

BlackJack

@Jannis: Wieso bist Du denn jetzt von `threading` auf `thread` gewechselt?  Das Modul soll man schon seit Jahren nicht mehr verwenden, das ist von `threading` abgelöst worden und `thread` gehört in Python 3 auch nicht mehr zur öffentlichen API. Letztlich sollte man Threads aber überhaupt nicht verwenden um die GUI zu manipulieren.

Statt den Sternchenimport weg zu lassen, hast Du jetzt zwei davon.

Es werden immer noch die ganzen Koordinaten an das `Lok`-Exemplar gebunden ohne dass das Sinn machen würde.

Die beiden Methoden zum Fahren sehen fast identisch aus. Das sollte nur *eine* sein, der man die Geschwindigkeit/Verzögerung als Argument übergeben kann. Immer wenn man etwas längeres abschreibst oder per kopieren und einfügen und dann leicht verändern programmiert, macht man in der Regel etwas falsch. Weder Code noch Daten sollten sich im Quelltext wiederholen. Sonst muss man bei Veränderungen und Fehlerbehebungen immer daran denken die gleichen Änderungen an verschiedenen Stellen und auch immer auf die gleiche Art durchzuführen. Das ist eine unnötige Fehlerquelle.

Warum hast Du die (richtige) ``for``-Schleife durch eine hier falsch angewendete ``while``-Schleife ersetzt?

Den eigentlichen Fehler hast Du auch nicht wirklich behoben. Du benutzt für `move()` falsche Werte, beziehungsweise welche die anscheinend zufällig richtig sind. Wie kommst Du darauf hart kodiert die Zahlen von 1 bis 6 zu verwenden? Woher weisst Du das die für die Teile der Lok stehen? Warum nimmst Du nicht die Werte die Du Dir ja *gemerkt* hast um die Lokteile zu bewegen?
BlackJack

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
import Tkinter as tk


class Locomotive(object):

    def __init__(self, canvas, body_color, wheel_color, (x, y)):
        self.canvas = canvas
        self.parts = list()
        for shape_name, coords, options in [
            ('rectangle', [0, 0, 60, 61], {'fill': body_color}),
            (
                'polygon',
                [108, 4, 151, 4, 130, 41],
                {'fill': body_color, 'outline': 'black'}
            ),
            ('rectangle', [140, 20, 120, 111], {'fill': body_color}),
            ('rectangle', [0, 60, 170, 130], {'fill': body_color}),
            ('oval', [120, 111, 170, 161], {'fill': wheel_color, 'width': 2}),
            ('oval', [0, 111, 50, 161], {'fill': wheel_color, 'width': 2}),
        ]:
            self.parts.append(
                getattr(self.canvas, 'create_' + shape_name)(*coords, **options)
            )
        self.move(x, y)

    def move(self, x, y):
        for part in self.parts:
            self.canvas.move(part, x, y)

    def fahren(self, step_count, step_distance, delay=200):
        if step_count:
            self.move(step_distance, 0)
            self.canvas.after(
                delay, self.fahren, step_count - 1, step_distance, delay
            )


def main():
    root = tk.Tk()
    canvas = tk.Canvas(root, width=1000, height=600)
    canvas.pack(fill=tk.X)
    locomotive_a = Locomotive(canvas, 'red', 'yellow', (80, 89))
    locomotive_b = Locomotive(canvas, 'blue', 'yellow', (260, 269))
    for locomotive, delay in [(locomotive_a, 160), (locomotive_b, 40)]:
        locomotive.fahren(170, 7, delay)
    root.mainloop()


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