Eine Grafik soll immer sichtbar sein, auch während der Berechnungen

Fragen zu Tkinter.
Antworten
Kurt_Schwaz
User
Beiträge: 5
Registriert: Donnerstag 4. August 2016, 18:11

Ich bin Anfänger und arbeite mit DrPython unter Linux.

Mein problem ist, dass mit mainloop() eine Grafik nach der Berechnung am Bildschirm dargestellt wird. Ich möchte jedoch, dass die Grafik schon während der Berechnung sichtbar ist und ich so die Zeichnungsschritte sehen kann.
Wie kann ich mainloop() abändern, dass dies möglich ist.

Ich importiere Tkinter.

Danke!
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Könntest du vielleicht den Code zeigen?
Unter mainloop() kann ich mir jetzt nicht so viel vorstellen.
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Kurt_Schwaz
User
Beiträge: 5
Registriert: Donnerstag 4. August 2016, 18:11

Ich schicke hier den Code dazu. Das Programm soll eine analoge Uhr werden. Dazu muss allerdings das Ziffernblatt immer sichtbar sein und das bekomme ich nicht her. Mainloop stellt die fertige Grafik dann dar. Aber das möchte ich nicht. Danke für die Unterstützung!

Hier der Code:

Code: Alles auswählen

from Tkinter import *
from math import *
from time import *
master = Tk()

canvas_width = 1290
canvas_height = 780

w = Canvas(master,
        width=canvas_width,
        height=canvas_height)
w.pack()

def Kreis(canvas,x,y,r):
    id = canvas.create_oval(x-r,y-r,x+r,y+r,fill = 'yellow')
    return id

def Zeiger(canvas,lx,ly,llx,lly):
    id = canvas.create_line(lx,ly,llx,lly,fill = 'red')
    return id

def Zeiger_loeschen(canvas,lx,ly,llx,lly):
    id = canvas.create_line(lx,ly,llx,lly,fill = 'yellow')
    return id

def Uhreinteilung(canvas,lx,ly,r):
    laenge = 40
    w.create_line(lx,ly-r+laenge/2,lx,ly-r-laenge/2,fill = 'blue')
    w.create_line(lx+r-laenge/2,ly,lx+r+laenge/2,ly,fill = 'blue')
    w.create_line(lx,ly+r-laenge/2,lx,ly+r+laenge/2,fill = 'blue')
    w.create_line(lx-r-laenge/2,ly,lx-r+laenge/2,ly,fill = 'blue')
  
def Uhreinteilung_fein(canvas,lx,ly,r):
    laenge = 20
    for i in range(0,360,30):
        rand_x_1 = lx+(r+laenge/2)*cos(pi/180*i)
        rand_y_1 = ly+(r+laenge/2)*sin(pi/180*i)
        rand_x_2 = lx+(r-laenge/2)*cos(pi/180*i)
        rand_y_2 = ly+(r-laenge/2)*sin(pi/180*i)
        w.create_line(rand_x_1,rand_y_1,rand_x_2,rand_y_2,fill ='blue')
        
    

a = canvas_width/2
b = canvas_height/2
c = 300

Kreis(w,a,b,c)

Uhreinteilung(w,a,b,c)

Uhreinteilung_fein(w,a,b,c)

zeigerlaenge = 250

for i in range(0,360,10):
    a_neu = a + zeigerlaenge*cos(pi/180*i)
    b_neu = b + zeigerlaenge*sin(pi/180*i)
    Zeiger(w,a,b,a_neu,b_neu)
    sleep(1.0)
    Zeiger_loeschen(w,a,b,a_neu,b_neu)
    
mainloop()
Zuletzt geändert von Anonymous am Freitag 26. August 2016, 10:13, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@Kurt_Schwaz: GUI-Programmierung funktioniert anders. Da hat man selbst nicht die (volle) Kontrolle über den Programmablauf, in dem man ein Programm schreibt das linear so ausgeführt wird wie es im Quelltext steht, sondern der Programmablauf wird von der GUI-Hauptschleife gesteuert und voran getrieben. Man schreibt Code der die GUI aufsetzt und registriert dann Rückruffunktionen/-methoden die bei bestimmten Ereignissen von der GUI-Hauptschleife aufgerufen werden, kurz etwas machen, und dann die Kontrolle wieder an diese Schleife zurück geben.

Hier müsstest Du anstelle der Schleife zum Beispiel mit der `after()`-Methode auf Widgets arbeiten, mit der man eine Zeit in Millisekunden, eine Funktion, und optional Argumente für diese Funktion, für einen späteren Aufruf registrieren kann.

Ausserdem enthält das Programm von Ansatz her noch einen Fehler: `Canvas` ist Vektorgrafik, das heisst man zeichnet da tatsächlich geometrische Figuren die als Objekte im Speicher angelegt und bei Änderungen immer wieder übereinandergemalt werden. Wenn das Programm abgelaufen ist, dann existieren in der Grafik 72 Linien für den Zeiger. Statt also für's löschen eine neue Linie über die alten Zeigerlinien zu zeichnen, sollte man nur *eine* Linie für den Zeiger verwenden und die entweder tatsächlich löschen, oder aber deren Koordinaten anpassen/verändern um den Zeiger weiter zu rücken.

Sonstige Anmerkungen: Lass das mit den Sternchen-Importen bleiben. Damit holst Du dir im Falle von `Tkinter` ca. 190 Namen in das Modul. Module machen keinen Sinn wenn man am Ende doch alles per *-Import wieder in einem Namensraum zusammen wirft. Das Programm ist dann schwerer zu verstehen, weil man nicht sieht welcher Name woher kommt. Und es besteht die Gefahr von Namenskollisionen.

Auf Modulebene gehören nur Definitionen von Konstanten, Funktionen, und Klassen. Variablen und anderer Code gehören in Funktionen und/oder Klassen. Wenn man dann auch noch das Hauptprogramm auf Modulebene mit Funktionsdefinitionen vermischt wird es noch unübersichtlicher. Das Hauptprogramm sollte auch deswegen in einer Funktion stehen, damit man das Modul so schreiben kann, das man es importieren kann, ohne dass das Hauptprogramm ausgeführt wird. Nur so kann man einzelne Funktionen/Klassen interaktiv oder automatisiert testen und auch einige Werkzeuge zur statischen Analyse oder Dokumentationserzeugung erwarten das so.

Die Namensschreibweise hält sich nicht an den Style Guide for Python Code. Konstanten werden komplett in Grossbuchstaben geschrieben. Funktionsnamen komplett klein. Einbuchstabige Namen sind in der Regel keine guten Namen. Funktionsnamen beschreiben üblicherweise eine Tätigkeit weil die etwas tun, und um sie von eher passiven Werten unterscheiden zu können.

Nach Kommas und um binäre Operatoren machen sich aus Gründen der Lesbarkeit Leerzeichen ganz gut.

Es macht wenig Sinn einen Wert an einen Namen zu binden, wenn der Name nur dazu verwendet wird den Wert gleich in der nächsten Zeile an den Aufrufer zurück zu geben.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Kurt_Schwaz hat geschrieben:Ich möchte jedoch, dass die Grafik schon während der Berechnung sichtbar ist und ich so die Zeichnungsschritte sehen kann.
Eigentlich geht das doch normalerweise so schnell, das man den Aufbau eh nicht sieht, oder?

Ansonsten mußt du halt sagen, das die Oberfläche aktualisiert werden soll. bsp: root.update()

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

@Kurt_Schwaz: Sternchenimporte solltest Du vermeiden, da nicht kontrolliert werden kann, was denn da alles importiert wird. Anweisungen und Definitionen sollten nicht gemischt werden, da das unleserlich ist. Zudem sollten eigentlich alle Anweisungen in einer Funktion stehen. Es sollte keine globalen Variablen geben, zudem Du canvas zwar übergibst, aber das globale w verwendest. Apropos w. Variablennamen sollten sprechend sein. Der Zeiger hat aus gutem Grund eine ID. TK-Canvas ist eine Vektorgraphik. Du erzeugst also hunderte rote Zeiger, die gleich danach wieder von gelben Zeiger übermalt werden. Statt dessen solltest Du einen Zeiger erzeugen und dessen Koordinaten einfach immer aktualisieren.

Zum Aktualisieren kennt Tk die after-Methode, die nach einer vorgegebenen Zeit aufgerufen wird. Schleifen funktionieren ja, wie Du bereits gemerkt hast, nicht.

Code: Alles auswählen

import Tkinter as tk
from math import cos, sin, pi

canvas_width = 1290
canvas_height = 780
zeigerlaenge = 250

def Kreis(canvas,x,y,r):
    return canvas.create_oval(x-r,y-r,x+r,y+r,fill = 'yellow')

def Zeiger(canvas,lx,ly,llx,lly):
    return canvas.create_line(lx,ly,llx,lly,fill = 'red')

def Uhreinteilung(canvas,lx,ly,r):
    laenge = 40
    canvas.create_line(lx,ly-r+laenge/2,lx,ly-r-laenge/2,fill = 'blue')
    canvas.create_line(lx+r-laenge/2,ly,lx+r+laenge/2,ly,fill = 'blue')
    canvas.create_line(lx,ly+r-laenge/2,lx,ly+r+laenge/2,fill = 'blue')
    canvas.create_line(lx-r-laenge/2,ly,lx-r+laenge/2,ly,fill = 'blue')

def Uhreinteilung_fein(canvas,lx,ly,r):
    laenge = 20
    for i in range(0,360,30):
        rand_x_1 = lx+(r+laenge/2)*cos(pi/180*i)
        rand_y_1 = ly+(r+laenge/2)*sin(pi/180*i)
        rand_x_2 = lx+(r-laenge/2)*cos(pi/180*i)
        rand_y_2 = ly+(r-laenge/2)*sin(pi/180*i)
        canvas.create_line(rand_x_1,rand_y_1,rand_x_2,rand_y_2,fill ='blue')

def update_clock(canvas, zeiger_id, i):
    a = canvas_width/2
    b = canvas_height/2
    a_neu = a + zeigerlaenge*cos(pi/180*i)
    b_neu = b + zeigerlaenge*sin(pi/180*i)
    canvas.coords(zeiger_id, a, b, a_neu, b_neu)
    canvas.after(1000, update_clock, canvas, zeiger_id, i+1)

def main():
    master = tk.Tk()

    canvas = tk.Canvas(master, width=canvas_width, height=canvas_height)
    canvas.pack()

    a = canvas_width/2
    b = canvas_height/2
    c = 300

    Kreis(canvas, a, b, c)
    Uhreinteilung(canvas, a, b, c)
    Uhreinteilung_fein(canvas, a, b, c)
    zeiger_id = Zeiger(canvas, a, b, a, b)
    update_clock(canvas, zeiger_id, 0)

    master.mainloop()

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

Ich hatte mich da auch mal dran ausgetobt. :-)

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import Tkinter as tk
from math import cos, pi as PI, sin

CANVAS_WIDTH = 1290
CANVAS_HEIGHT = 780


def zeichne_kreis(canvas, x, y, radius):
    return canvas.create_oval(
        x - radius, y - radius, x + radius, y + radius, fill='yellow'
    )


def zeichne_zeiger(canvas, x_1, y_1, x_2, y_2):
    return canvas.create_line(x_1, y_1, x_2, y_2, fill='red')


def loesche_zeiger(canvas, zeiger_id):
    canvas.delete(zeiger_id)


def zeichne_uhreinteilung(canvas, x, y, radius, laenge=20):
    canvas.create_line(
        x, y - radius + laenge, x, y - radius - laenge, fill='blue'
    )
    canvas.create_line(
        x + radius - laenge, y, x + radius + laenge, y, fill='blue'
    )
    canvas.create_line(
        x, y + radius - laenge, x, y + radius + laenge, fill='blue'
    )
    canvas.create_line(
        x - radius - laenge, y, x - radius + laenge, y, fill='blue'
    )


def zeichne_uhreinteilung_fein(canvas, x, y, radius, laenge=10):
    for winkel in xrange(0, 360, 30):
        canvas.create_line(
            x + (radius + laenge) * cos(PI / 180 * winkel),
            y + (radius + laenge) * sin(PI / 180 * winkel),
            x + (radius - laenge) * cos(PI / 180 * winkel),
            y + (radius - laenge) * sin(PI / 180 * winkel),
            fill='blue'
        )


def aktualisiere_zeiger(canvas, center_x, center_y, winkel, zeiger_id=None):
    if zeiger_id:
        loesche_zeiger(canvas, zeiger_id)

    if winkel < 360:
        zeigerlaenge = 250
        canvas.after(
            1000,
            aktualisiere_zeiger,
            canvas,
            center_x,
            center_y,
            winkel + 10,
            zeichne_zeiger(
                canvas,
                center_x,
                center_y,
                center_x + zeigerlaenge * cos(PI / 180 * winkel),
                center_y + zeigerlaenge * sin(PI / 180 * winkel)
            )
        )
        

def main():
    root = tk.Tk()
    canvas = tk.Canvas(root, width=CANVAS_WIDTH, height=CANVAS_HEIGHT)
    canvas.pack()
    center_x = CANVAS_WIDTH / 2
    center_y = CANVAS_HEIGHT / 2
    radius = 300
    zeichne_kreis(canvas, center_x, center_y, radius)
    zeichne_uhreinteilung(canvas, center_x, center_y, radius)
    zeichne_uhreinteilung_fein(canvas, center_x, center_y, radius)
    aktualisiere_zeiger(canvas, center_x, center_y, 0)
    root.mainloop()


if __name__ == '__main__':
    main()
Kurt_Schwaz
User
Beiträge: 5
Registriert: Donnerstag 4. August 2016, 18:11

Danke, Sirius3, für Deine ausführliche Lösung und Beschreibung! Ich probiere es aus.

Danke!
Kurt_Schwaz
User
Beiträge: 5
Registriert: Donnerstag 4. August 2016, 18:11

Danke für die ausführlichen Hilfen! Ich sehe nun auch den Sinn!
Danke!!

Ciao

Kurt
coethen25
User
Beiträge: 13
Registriert: Freitag 5. August 2016, 13:21

@Sirius3 Deine Uhr läuft prima mit zwei kleinen Änderungen

Code: Alles auswählen

def update_clock(canvas, zeiger_id, i):
    a = canvas_width/2
    b = canvas_height/2
    a_neu = a + zeigerlaenge*cos(pi/180*i)
    b_neu = b + zeigerlaenge*sin(pi/180*i)
    canvas.coords(zeiger_id, a, b, a_neu, b_neu)
    canvas.after(1000, update_clock, canvas, zeiger_id, i+1)
    # hier i+6, dann rückt sie im Sekundentakt vor 
Und in der main-Funktion

Code: Alles auswählen

 update_clock(canvas, zeiger_id, 0)
# hier anstelle 0, -90 als Startpunkt, dann beginnt sie bei 12 bzw. 0 Uhr
Kurt_Schwaz
User
Beiträge: 5
Registriert: Donnerstag 4. August 2016, 18:11

Herzlichen Dank!
Antworten