Malen einer kleinen rot,grün,gelben Animation funzt nicht

Fragen zu Tkinter.
Antworten
caiusjuliuscaesar
User
Beiträge: 7
Registriert: Sonntag 3. September 2017, 12:00

Liebes Forum,

das Malen einer kleinen Animation bereitet mir Schwierigkeiten:

1. Frage:
Ich würde gerne einer Klasse eine "Grafikressource" mit meiner Methode setCan(self, can) (siehe unten) zuweisen, in die diese Klasse mit einer Methode reinmalt. Ich vermute, dass diese Grafikressouce ein Canvas ist. [richtig?/falsch?]

2. Frage
Allerdings möchte ich das Canvas an anderer Stelle (außerhalb) gerne definieren. Die Methode show() (siehe unten) soll dann in das übergebene Canvas can reinmalen, aber jeweilig angepaßt an die Maße des Canvas. Aber wie komme ich innerhalb von show() an die Maße des Canvas heran? Ich habe es mit

Code: Alles auswählen

height = self.__can.width # funzt nicht
width = self.__can.height # funzt nicht
versucht, das wirft eine Fehlermeldung.

3. Ich möchte einen ausgefüllten Kreis blinken lassen. Also male ich einen gelb-ausgefüllten Kreis, warte 1s, dann der rot-ausgefüllte Kreis, warte 1s und zuletzt der grün-ausgefüllte. So mein Plan. Aber auch das funktioniert nicht. Es wird nur der letzte Kreis gemalt.
Wie kann ich das nacheinander einblenden? Muß ich eine Art flush()-Methode aufrufen, um die Grafik auch unmittelbar zeichnen zu lassen?

Code: Alles auswählen

 
         self.__can.create_oval(x0,y0,width,height, fill='yellow')
        time.sleep(1)
        self.__can.create_oval(x0,y0,width,height, fill='red')
        time.sleep(1)
        self.__can.create_oval(x0,y0,width,height, fill='green') # nur dieser Kreis wird gemalt
Hier der Gesamtzusammenhang:

Code: Alles auswählen

  def setCan(self, can):
     self.__can = can

  def show(self, dauer):
        x0 = 15
        y0 = 15
        height = 135 # self.__can.width funzt nicht
        width = 135 # self.__can.height funzt nicht

        self.__can.create_oval(x0,y0,width,height, fill='yellow')
        time.sleep(1)
        self.__can.create_oval(x0,y0,width,height, fill='red')
        time.sleep(1)
        self.__can.create_oval(x0,y0,width,height, fill='green')
        time.sleep(1)
Viele Grüße,

CJC
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bitte gewoehn dir gleich wieder ab die doppelten Unterstriche vor Methoden und Attributen zu benutzen. Die bedeuten in Python etwas anderes als was du glaubst, und koennen subtile Fehler produzieren, wo du es nicht erwartest. Ein einfacher Unterstrich genuegt.

Desweiteren sind setter und getter verpoent. Ein einfaches "ding.canvas = canvas" reicht. Womit auch ein weiterer Punkt angesprochen wird: aussagekraeftige Variablennamen. can heisst Dose auf Englisch, und so habe ich das erst auch verstanden, bis ich irgendwann den groesseren Kontext gesehen habe. Eine vernuenftige Bennenung mit nur 3 Buchstaben mehr, und alles waerer tutti gewesen.

Zum Design deiner Klasse ebenfalls noch eine Anmerkung: es ist ueblich solche Abhaengigkeiten wie den Canvas entweder bei Konstruktion (meistens malt man das Ding ja immer in den selben) oder aber in der show Methode direkt zu uebergeben. Es ist ungewoehnlich das mit einer extra Methode zu machen, die du vergessen kannst zwischendurch aufzurufen.

Last but not least endlich zu deinen angefragten Problemen ;)

Laut Doku findest du Breite und Hoehe in winfo_width()/winfo_height().

Und Animationen bzw die Unvertraeglichkeit von langen Berechnungen (dazu zaehlen auch sleeps) und GUIs ist eines der haeufigst diskutierten Themen hier. Du musst dazu die after-Methode benutzen, mittels derer du einen timer starten kannst. Wenn der triggert, dann setzt du deine naechste Farbe, und startest einen neuen Timer. Such mal hier im Forum, da hat's wirklich hunderte Beitraege zu after. Am besten mit google site search, die eingebaute Suchfunktion ist eher Mist.
caiusjuliuscaesar
User
Beiträge: 7
Registriert: Sonntag 3. September 2017, 12:00

1. Danke für die ehrliche und informative Antwort!!!
2. Man muß aber trotzdem noch ein update() einfügen, sonst funktioniert es auch mit after nicht (ich habe das can noch mal dringelassen:

Code: Alles auswählen

        self.__can.create_oval(x0,y0,width,height, fill='yellow')
        self.__can.update()
        self.__can.after(1000)
        self.__can.create_oval(x0,y0,width,height, fill='red')
        self.__can.update()
        self.__can.after(1000)
        self.__can.create_oval(x0,y0,width,height, fill='green')
        self.__can.update()
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist so nicht richtig. Du benutzt after gar nicht. after erwartet ein callable, das dann ausgefuehrt wird. Ich weiss nicht, was es ohne argument macht, aber das update ist der entscheidende Unterschied zu vorher.

Nur skaliert das nicht. Stell dir mal vor du willst zwei Dinge animieren. Wie soll das gehen?

Da musst du noch mal ran, und wirklich richtig after benutzen, wie zB hier: viewtopic.php?t=38282

Ich habe in dem Fall zwar fuer pygame votiert, aber das Beispiel von wuf zeigt die Technik in tkinter.
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und noch ein Nachtrag: Doppelunterstrich und schlechte Namensgebung sowie after mal beiseite gelassen funktioniert dein Ansatz so auch nicht auf Dauer.

Denn du malst immer neue Canvas-Objekte. Das fliegt dir irgendwann um die Ohren, denn der entfernt nix. Der malt einfach alle neu.

Stattdessen entweder entfernen und ein neues einfuegen, oder wenn wie bei dir die Form gleich bleibt das item fuer das du eine ID durch das create bekommst einfach umkonfigurieren. Sonst haengt die Nummer irgendwann.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Unten den sich drehenden Würfel von BlackJack hatte ich mir mal gespeichert, der ist mit Canvas und after, nur nicht in Python-Scripttags, deshalb hier noch einmal:

viewtopic.php?t=38217

Code: Alles auswählen

from __future__ import absolute_import, division, print_function
import tkinter as tk
from collections import namedtuple
from math import cos, sin
from random import random


class Point(namedtuple('Point', 'x y z')):

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y, self.z + other.z)

    def __mul__(self, number):
        return Point(self.x * number, self.y * number, self.z * number)

    def rotated(self, alpha=0, beta=0, gamma=0):
        return Point(
            (
                self.x * cos(gamma) * cos(beta)
                + self.y * (
                    -sin(gamma) * cos(alpha)
                    + cos(gamma) * sin(beta) * sin(alpha)
                )
                + self.z * (
                    sin(gamma) * sin(alpha)
                    + cos(gamma) * sin(beta) * cos(alpha)
                )
            ),
            (
                self.x * sin(gamma) * cos(beta)
                + self.y * (
                    cos(gamma) * cos(alpha)
                    + sin(gamma) * sin(beta) * sin(alpha)
                )
                + self.z * (
                    -cos(gamma) * sin(alpha)
                    + sin(gamma) * sin(beta) * cos(alpha)
                )
            ),
            (
                self.x * -sin(beta)
                + self.y * cos(beta) * sin(alpha)
                + self.z * cos(beta) * cos(alpha)
            )
        )

    def projected(self, distance):
        quotient = 1 - self.z / distance
        return Point(self.x / quotient, self.y / quotient, 0)


class Mesh(object):

    def __init__(self, vertices, edges):
        self.vertices = vertices
        self.edges = edges
        self.tag = 'mesh{0}'.format(id(self))

    def __add__(self, point):
        return Mesh([v + point for v in self.vertices], self.edges)

    def __mul__(self, number):
        return Mesh([v * number for v in self.vertices], self.edges)

    def rotated(self, alpha, beta, gamma):
        return Mesh(
            [v.rotated(alpha, beta, gamma) for v in self.vertices], self.edges
        )

    def projected(self, distance):
        return Mesh([v.projected(distance) for v in self.vertices], self.edges)

    def draw(self, canvas):
        for start_index, end_index in self.edges:
            start_point = self.vertices[start_index]
            end_point = self.vertices[end_index]
            canvas.create_line(
                start_point.x, start_point.y, end_point.x, end_point.y,
                tag=self.tag
            )

    def delete(self, canvas):
        canvas.delete(self.tag)


class MeshUI(tk.Frame):

    def __init__(self, parent, mesh, size):
        tk.Frame.__init__(self, parent)
        self.mesh = mesh
        self.displayed_mesh = None
        self.size = size
        self.zoom_factor = size / 4
        self.distance = self.size * 2
        self.offset = Point(self.size / 2, self.size / 2, 0)
        self.alpha, self.beta, self.gamma = 0, 0, 0
        self.alpha_delta = random() * 0.025
        self.beta_delta = random() * 0.025
        self.gamma_delta = random() * 0.025
        self.canvas = tk.Canvas(self, width=self.size, height=self.size)
        self.canvas.pack(side=tk.TOP)
        self.step()

    def update_display(self):
        if self.displayed_mesh:
            self.displayed_mesh.delete(self.canvas)
        self.displayed_mesh = (
            (
                self.mesh.rotated(self.alpha, self.beta, self.gamma)
                * self.zoom_factor
            ).projected(self.distance) + self.offset
        )
        self.displayed_mesh.draw(self.canvas)

    def step(self):
        self.alpha += self.alpha_delta
        self.beta += self.beta_delta
        self.gamma += self.gamma_delta
        self.update_display()
        self.after(50, self.step)


def main():
    cube = Mesh(
        [
            Point(1, 1, 1),
            Point(-1, 1, 1),
            Point(1, -1, 1),
            Point(1, 1, -1),
            Point(1, -1, -1),
            Point(-1, -1, 1),
            Point(-1, 1, -1),
            Point(-1, -1, -1),
        ],
        [
            (0, 1),
            (0, 2),
            (0, 3),
            (1, 5),
            (1, 6),
            (2, 4),
            (2, 5),
            (3, 4),
            (3, 6),
            (4, 7),
            (5, 7),
            (6, 7),
        ]
    )

    root = tk.Tk()
    root.title('Rotating Cube')
    mesh_ui = MeshUI(root, cube, 500)
    mesh_ui.pack()
    root.mainloop()


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