Tkinter-Rechteckobjekte mittels for-Schleife erstellen

Fragen zu Tkinter.
Antworten
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Hallo

Ich würde gerne Tkinter-Rechteckobjekte mittels for-Schleife erstellen und den Zähler der for-Schleife für die Benennung der Rechteckobjekte und außerdem als Multiplikator für die Position/Größe der Rechtecke verwenden. Mit Google komme ich da nicht weiter.

Mein aktueller Code sieht so aus:

Code: Alles auswählen

import tkinter as tk


x1 = 50
y1 = 0
x2 = 150
y2 = 50

class Application:
    def __init__(self, app_win):
        self.app_win = app_win
        self.width = app_win.winfo_screenwidth()
        self.height = app_win.winfo_screenheight()
        self.center_x = self.width / 2
        self.center_y = self.height / 2


        self.window = tk.Canvas(app_win, width=self.width, height=self.height,
                                bg='black')
        self.window.pack()


class Rectangle():
    def __init__(self, app_win, x1, y1, x2, y2):
        self.app_win = app_win
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.rectangle = self.window.create_rectangle(self.x1, self.y1, self.x2, self.y2, # Problem 1
                                                      fill='red')
        self.window.pack()


def main():
    app_win = tk.Tk()
    app_win.attributes("-fullscreen", True)
    app_win.bind("<Escape>", lambda e: app_win.quit())

    app = Application(app_win)

    rectangle_list = []

    for i in range(0,5):
        rectangle{i} = Rectangle(app_win, x1*i, y1, x2*i, y2*i) # Problem 2
        rectangle_list.append(rectangle{i})

    app_win.mainloop()


if __name__ == '__main__':
    main()
  1. Das erste Problem ist, dass ich nicht in Erfahrung bringen konnte, wie ich der Klasse `Rectangle` den Canvas `window` des Objekts `app` übergeben kann.
  2. Das zweite Problem ist dann die Erzeugung der Rechteckobjekte mittels for-Schleife und deren Speicherung in einer Liste.
  3. Enthält mein Code noch weitere Probleme?
Gruß
Atalanttore
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das mit dem {i} ist Murks und kann ersatzlos gestrichen werden. Was du im Rectangle window nennst ist ein Cnavas, und sollte auch so heißen. Und dann muss es auch ein canvas sein, den du erzeugst. Und darauf für jedes Rechteck pack aufzurufen ist bestenfalls überflüssig, schlimmstenfalls ein Bug. Da machst du an der Stelle wo du den Canvas erzeugst genau ein mal. Du hat’s doch Code von wuf bekommen, der einen canvas erzeugt? Wo ist das denn alles hin?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: 1. Der `Canvas` ist als Attribut auf Deinem `Application`-Objekt. Den kannst Du da ganz normal per ``.``-Operator abgfragen.

2. Dieses {i} nach einem Namen ist kein gülitiges. Wenn Du das an beiden Stellen einfach entfernst, funktionierts.

3. Grunddatentypen gehören nicht in Namen. Also `rectangles` statt `rectangle_list`. Während der Entwicklung kommt es häufiger vor das man mal Datentypen durch passendere ersetzt und dann hat man entweder irreführende falsche Namen im Programm, oder muss das ganze Programm durchgehen und die anpassen.

Die letzte Zeile in `Rectangle.__init__()` würde nicht einmal Sinn machen wenn es `self.window` geben würde. Das `Canvas`-Objekt ist ja bereits angezeigt.

Wozu muss ein `Rectangle` das Hauptfenster kennen?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Atalanttore

Hier zur Klärung wieder etwas Code:

Code: Alles auswählen

import tkinter as tk


class Application:
    def __init__(self, app_win):
        self.app_win = app_win
        self.width = app_win.winfo_screenwidth()
        self.height = app_win.winfo_screenheight()
        self.center_x = self.width / 2
        self.center_y = self.height / 2


        self.canvas = tk.Canvas(app_win, width=self.width, height=self.height,
            highlightthickness=0, bg='black')
        self.canvas.pack()

        self.rectangles = []

        x1 = 50
        y1 = 50
        x2 = 150
        y2 = 100        
        for nr in range(5):
            rectangle = self.canvas.create_rectangle(
                x1*nr, y1*nr, x2*nr,y2*nr, fill='red')
                
            self.rectangles.append(rectangle)


def main():
    app_win = tk.Tk()
    app_win.attributes("-fullscreen", True)
    app_win.bind("<Escape>", lambda e: app_win.quit())

    app = Application(app_win)

    app_win.mainloop()


if __name__ == '__main__':
    main()
Gruss wuf ;-)
Take it easy Mates!
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich hab nich prompt verlesen weil,alles window heißt, auch wenn es Canvas ist. Den Teil meiner Bemerkung kannst du also ignorieren. Nur musst du eben den Canvas reinreichen.
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Danke für die Erklärungen. Eine Kombination aus Buchstaben und einer fortlaufenden Zahl als Objektname funktioniert dann wohl nicht.

@wuf: Danke für den Code. Ich habe die Klasse `Application` noch um folgende Methode zum Ändern der Füllfarbe erweitert.

Code: Alles auswählen

    def change_color(self, number, color):
        self.canvas.itemconfig(self.rectangles[number], fill=color)
Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Die Kombination aus Buchstaben und einer fortlaufenden Zahl als Objektname ist eine Liste. Die hat einen Namen und die Objekte sind über eine fortlaufende Nummer über den Namen und den Indexoperator ansprechbar. Wobei man das eher selten braucht, weil der Index für eine Schleife über die Objekte ja beispielsweise nicht nötig ist.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: Mit einer Kombination aus Buchstaben und einer fortlaufenden Zahl als Objektname habe ich gemeint, dass am Ende so eine Liste mit Objekten entsteht:

Code: Alles auswählen

rectangles = [rectangle_1, rectangle_2, rectangle_3, rectangle_4, rectangle_5, rectangle_6, rectangle_7, rectangle_8, rectangle_9, rectangle_10]
Aber eigentlich ist das auch gar nicht so wichtig, weil bei einer Schleife über die Objekte in der Liste die Objektnamen keine Rolle spielen.



Ich habe nun mit einer for-Schleife versucht die Füllfarbe der Rechteckobjekte in der Liste nacheinander mit jeweils 1 Sekunde Verzögerung auf grün zu ändern. Leider klappt das nicht nacheinander, sondern nach 1 Sekunde Verzögerung wird die Farbe aller Rechtecke in grün geändert.
Ich habe den Verdacht, dass die for-Schleife die Anweisung `app_win.after(1000, app.change_color, i, 'green')` ohne nennenswerte Verzögerung 10 mal nacheinander aufruft und deshalb nach 1 Sekunde die 10 Methoden `app.change_color(i, 'green')` der einzelnen Rechteckobjekte gleichzeitig aufgerufen werden.

Der Code sieht so aus:

Code: Alles auswählen

import tkinter as tk


class Application:
    def __init__(self, app_win):
        self.app_win = app_win
        self.width = app_win.winfo_screenwidth()
        self.height = app_win.winfo_screenheight()
        self.center_x = self.width / 2
        self.center_y = self.height / 2

        self.canvas = tk.Canvas(app_win, width=self.width, height=self.height,
                                highlightthickness=0, bg='black')
        self.canvas.pack()

        self.rectangles = []

        x1 = 50
        y1 = 50
        x2 = 150
        y2 = 100
        for nr in range(10):
            rectangle = self.canvas.create_rectangle(
                x1 * nr, y1 * nr, x2 * nr, y2 * nr, fill='red')

            self.rectangles.append(rectangle)

    def change_color(self, number, color):
        self.canvas.itemconfig(self.rectangles[number], fill=color)


def main():
    app_win = tk.Tk()
    app_win.attributes("-fullscreen", True)
    app_win.bind("<Escape>", lambda e: app_win.quit())

    app = Application(app_win)

    for i in app.rectangles:                              # Funktioniert
        app_win.after(1000, app.change_color, i, 'green') # so nicht

    app_win.mainloop()


if __name__ == '__main__':
    main()
Gruß
Atalanttore
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

`i` ist auch ein schlechter Name für eine Canvas-ID, die auch nicht als Index in die rectangles-Liste taugt. Wenn Du jedes Rechteck mit 1 Sekunde abstand umschalten willst, dann muß das erste change_color nach 1000 ms, das zweite nach 2000 ms usw. aufgerufen werden.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Atalanttore

Hier eine mögliche Lösungsvariante:

Code: Alles auswählen

import tkinter as tk


class Application:
    def __init__(self, app_win):
        self.app_win = app_win
        self.width = app_win.winfo_screenwidth()
        self.height = app_win.winfo_screenheight()
        self.center_x = self.width / 2
        self.center_y = self.height / 2

        self.canvas = tk.Canvas(app_win, width=self.width, height=self.height,
                                highlightthickness=0, bg='black')
        self.canvas.pack()

        self.rectangles = []

        x1 = 50
        y1 = 50
        x2 = 150
        y2 = 100
        for nr in range(10):
            rectangle = self.canvas.create_rectangle(
                x1 * nr, y1 * nr, x2 * nr, y2 * nr, fill='red')

            self.rectangles.append(rectangle)

    def change_color(self, color, index=0):
        try:
            rectangle = self.rectangles[index]
        except IndexError:
            return
        self.canvas.itemconfig(rectangle, fill=color)
        index += 1
        
        self.app_win.after(1000, self.change_color, color, index)
 
        
def main():
    app_win = tk.Tk()
    app_win.attributes("-fullscreen", True)
    app_win.bind("<Escape>", lambda e: app_win.quit())

    app = Application(app_win)
    app.change_color('green')

    app_win.mainloop()


if __name__ == '__main__':
    main()
Gruss wuf ;-)
Take it easy Mates!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ganze ohne Index:

Code: Alles auswählen

import tkinter as tk

class Application(tk.Tk):
    RECT_X1 = 50
    RECT_Y1 = 50
    RECT_X2 = 150
    RECT_Y2 = 100

    def __init__(self):
        super().__init__()
        self.width = self.winfo_screenwidth()
        self.height = self.winfo_screenheight()
        self.canvas = tk.Canvas(self, width=self.width, height=self.height,
                                highlightthickness=0, bg='black')
        self.canvas.pack()
        self.bind("<Escape>", lambda _: self.quit())

        self.rectangles = [
            self.canvas.create_rectangle(
                self.RECT_X1 * nr, self.RECT_Y1 * nr, self.RECT_X2 * nr, self.RECT_Y2 * nr, fill='red')
            for nr in range(10)
        ]

    def change_next_rectangle(self, rectangles, color):
        rectangle = next(rectangles, None)
        if rectangle:
            self.canvas.itemconfig(rectangle, fill=color)
            self.after(1000, self.change_next_rectangle, rectangles, color)

    def start_change_colors(self, color):
        self.change_next_rectangle(iter(self.rectangles), color)


def main():
    app = Application()
    app.attributes("-fullscreen", True)
    app.start_change_colors('green')
    app.mainloop()


if __name__ == '__main__':
    main()
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Danke für die beiden Codebeispiele, aber welche von beiden Lösungen mit und ohne Index ist die bessere/elegantere?

Gruß
Atalanttore
Antworten