canvas und button

Fragen zu Tkinter.
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

jetzt bastle ich seit 2 stunden :

]

Code: Alles auswählen

from tkinter import *

bild = Tk()
bild2 = Canvas(bild,width=700,height=300)

stop1=bild2.create_rectangle(150,80,160,100,fill = 'grey')
stop1_kolben=bild2.create_rectangle(153,80,157,100,fill = 'grey')
stop1_pos=1000
kreis= bild2.create_oval(50,100,70,120, fill="blue")
kreis_pos=70
bild2.create_line(50,100,600,100)
bild2.create_line(50,120,600,120)

bild2.grid(row=0, column=0)

def stop1_zu():
        bild2.move(stop1_kolben,0,20)
        stop1_pos=153
b1=Button(bild, text="Stop1_zu",command=stop1_zu).grid(row=1, column=0)

def stop1_auf():
        bild2.move(stop1_kolben,0,-20)
        stop1_pos=1000
b2=Button(bild, text="Stop1_auf",command=stop1_auf).grid(row=2, column=0)

while kreis_pos < stop1_pos:
    bild2.move(kreis,1,0)
    kreis_pos=kreis_pos+1
    Label(bild, text=kreis_pos).grid(row=3,column=0)
    Label(bild, text=stop1_pos).grid(row=4,column=0)
    bild2.update()
    bild2.after(50)
Das verdammte Label geht, der Zähler läuft.
Der bild2.move geht auch (!), also läuft die stop1_zu (z.b.)
Aber : stop1_pos wird nicht 153, sondern bleibt unverändert !?!

... was hab ich da nicht verstanden am Konzept ??
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

Label geht ganz und gar nicht. Du verzeihst davon immer wieder neue. While-Schleifen darf es bei GUI-Programmen nicht geben. Die letzte Zeile muß `bild.mainloop()` heißen und alle Animation muß darüber laufen. Jede nicht-tiviale GUI braucht Klassendefinitionen, um z.B. sowas wie startpos realisieren zu können.
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

workaround : ich verwende die koordinate aus der grafik als übergabe wert, dann gehts.
übler murks, ich weiß ... aber woran liegt es prinzipiell, daß die stopkoodinate aus der button-routine in der schleife nicht übergeben wird ??

b) wieso darf ich die schleife nicht selber schreiben .. es geht doch ?!

Code: Alles auswählen

from tkinter import *

bild = Tk()
bild2 = Canvas(bild,width=700,height=300)

stop1=bild2.create_rectangle(150,80,160,100,fill = 'grey')
stop1_kolben=bild2.create_rectangle(153,80,157,100,fill = 'grey')
stop1_pos=1000
kreis_pos=1
kreis= bild2.create_oval(50,100,70,120, fill="blue")
bild2.create_line(50,100,600,100)
bild2.create_line(50,120,600,120)

bild2.grid(row=0, column=0)

def stop1_zu():
        bild2.move(stop1_kolben,0,20)
b1=Button(bild, text="Stop1_zu",command=stop1_zu).grid(row=1, column=0)

def stop1_auf():
        bild2.move(stop1_kolben,0,-20)
b2=Button(bild, text="Stop1_auf",command=stop1_auf).grid(row=2, column=0)

while 1==1:

    kolbenpos=bild2.coords(stop1_kolben)[1]
    teilpos=bild2.coords(kreis)[0]

    if kolbenpos == 100:
         stopstelle = 133
    else:
         stopstelle = 1000
                
    if teilpos < stopstelle or teilpos > 153:
         bild2.move(kreis,1,0)

    Label(bild, text=("Stopstelle: ",stopstelle)).grid(row=3,column=0)
    Label(bild, text=("teil: ",teilpos)).grid(row=4,column=0)
    bild2.update()
    bild2.after(50)
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@reinerdoll: Man schreibt die Schleife nicht selbst. Und bei Tk kann es auch Probleme geben wenn man selbst `update()` verwendet. Lass es einfach und mach es gleich richtig bevor es Probleme gibt, die Du *dann* nicht lösen kannst ohne die reguläre Hauptschleife zu verwenden.

Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` Unmengen an Namen ins Modul die nicht gebraucht werden, es besteht die Gefahr von Namenskollisionen, und man importiert auch Sachen die gar nicht in dem Modul definiert, sondern seinerseits dort importiert werden.

Nummerierte Namen sind in aller Regel ein Zeichen das man sich entweder vernüngtige Namen ausdenken möchte, oder eine Datenstruktur statt einzelner Werte verwenden möchte. Oft ist das eine Liste.

Bei `bild2` sollte man sich für das Hauptfenster einen besseren Namen ausdenken, damit das eigentliche Bild nicht anders benannt werden muss. Man kann auch den konventionellen Namen `root` für das Hauptfenster verwenden.

Abkürzungen sollte man auch nicht verwenden. Wofür sollen `b1` und `b2` stehen? Oder anders gefragt: Warum bindest Du jeweils den Wert `None` an diese beiden Namen? Das ist nämlich der Rückgabewert der `grid()`-Methode.

Die 1 beim den `stop*`-Namen ist einfach nur sinnfrei.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Funktionen und Methoden sollten alles was sie ausser Konstanten benötigen als Argumente übergeben bekommen. Daraus folgt das man mindestens `functools.partial()` braucht, besser aber wie von Sirius3 schon erwähnt objektorientierte Programmierung (OOP).

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial
import tkinter as tk


def on_stop_zu(canvas, stop_kolben, stop_position_var):
    canvas.move(stop_kolben, 0, 20)
    stop_position_var.set(153)


def on_stop_auf(canvas, stop_kolben, stop_position_var):
    canvas.move(stop_kolben, 0, -20)
    stop_position_var.set(1000)


def do_animation_step(canvas, kreis, kreis_position_var, stop_position_var):
    if kreis_position_var.get() < stop_position_var.get():
        canvas.move(kreis, 1, 0)
        kreis_position_var.set(kreis_position_var.get() + 1)
        canvas.after(
            50,
            do_animation_step,
            canvas,
            kreis,
            kreis_position_var,
            stop_position_var,
        )


def main():
    root = tk.Tk()
    canvas = tk.Canvas(root, width=700, height=300)
    canvas.grid(row=0, column=0)

    canvas.create_rectangle(150, 80, 160, 100, fill="grey")
    stop_kolben = canvas.create_rectangle(153, 80, 157, 100, fill="grey")
    stop_position_var = tk.IntVar(value=1000)
    kreis = canvas.create_oval(50, 100, 70, 120, fill="blue")
    kreis_position_var = tk.IntVar(value=70)
    canvas.create_line(50, 100, 600, 100)
    canvas.create_line(50, 120, 600, 120)

    tk.Button(
        root,
        text="Stop zu",
        command=partial(on_stop_zu, canvas, stop_kolben, stop_position_var),
    ).grid(row=1, column=0)
    tk.Button(
        root,
        text="Stop auf",
        command=partial(on_stop_auf, canvas, stop_kolben, stop_position_var),
    ).grid(row=2, column=0)
    tk.Label(root, textvariable=kreis_position_var).grid(row=3, column=0)
    tk.Label(root, textvariable=stop_position_var).grid(row=4, column=0)

    do_animation_step(canvas, kreis, kreis_position_var, stop_position_var)
    root.mainloop()


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

danke erstmal für die vielen tipps !!
ich werd ne weile brauchen, das alles nachzuvollziehen.
ich bin halt absoluter python-beginner ..

eine frage noch : wenn ich nicht bloß diesen einen kreis, sondern mehrere (anzahl zunächst unbekannt) auf die reise schicken möchte, wäre ein array für die kreise fein.
das geht aber wohl nicht.

irgendein tipp, wie ich elegant mehrere kreise handle (nach rechts bewegen, an mehreren stoppern anhalten, bei stops dann warteschlangen bilden..) ?
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@reinerdoll: Dann müsste man die IDs für die Kreise in eine Liste stecken und entsprechenden Code schreiben der das macht was man damit machen will.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Oder Tags vergeben, das kann canvas.
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Das klingt aber eher danach das die einzeln ansprechbar sein müssen, denn während welche an einem Stop halten, müssen andere ja weiterbewegt werden, wenn diese die Stelle bereits passiert haben.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Schließt sich das aus? AFAIK kann man doch über die getaggten Items iterieren. Außer es bedarf zusätzlicher Information die man speichern muss. Aber eine Liste von IDs alleine reicht da ja auch nicht.
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Wenn ich Wahl habe die IDs in einer Python-Liste zu speichern oder auf Tk-Seite unter einem Tag zusammen zu fassen, würde ich die Python-Liste bevorzugen. Tags sehe ich eher dazu um mehrere IDs zu einer ID/Tag zusammen zu fassen wenn man die als *ein* ”Objekt” behandeln möchte.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

könnte jemand einem blassen neuling das kurz an einem beispiel erklären ?
wie kann ich objekte (meine kreise) zu tags zusammenfassen, und doch mit verschiedenen bedingungen bewegen oder eben nicht ??
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

@__blackjack__ :

diese struktur :

Code: Alles auswählen

def main():
    root = tk.Tk()
    canvas = tk.Canvas(root, width=700, height=300)
    canvas.grid(row=0, column=0)
	.
	.
	.
    root.mainloop()
a)ruft sich doch innerhalb der schleife immer wieder selber auf, oder ? das schaut aus wie ein rekursiver aufruf ..
ich verstehe die syntax falsch, oder ?

b)führt bei jedem aufruf die definition von root und canvas aufs neue durch. is das nötig ? reicht es nicht, dieses einmal außerhalb über der schleife zu machen ?


(verzeih die dämlichen fragen, ich will nur verstehen was du da schreibst ;-)
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

a) wo siehst Du da den Aufruf von sich selbst? `main` wird nur einmal im ganzen Programm aufgerufen.
b) ja, da hast Du recht, man sollte und darf das Fenster nur einmal erzeugen. Da aber a) nicht stimmt, ist b) irrelevant.

Tags sind z.B. hier erklärt.
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

Code: Alles auswählen

def main():
	..
	root.mainloop
habe ich so verstanden, daß die methode mainloop die main() immer wieder (innerhalb main)aufruft. eine routine, die sich selbst aufruft, ist rekursiv, oder ?

wie gesagt, ich verstehe wohl den code falsch, oder ?

ich hätte gedacht, es müsse so aussehen :

Code: Alles auswählen

def main():
	..
root.mainloop
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

`mainloop` ist die Hauptschleife von TkInter. `main` dagegen die erste Funktion, die von Deinem Skript aufgerufen wird. Und innerhalb dieser `main`-Funktion muß dann `mainloop` aufgerufen werden, damit die GUI überhaupt auf Ereignisse reagieren kann.

Dass in beiden Funktionsnamen `main` vorkommt, hat nur insoweit was miteinander zu tun, dass beide eine Art von Hauptaufgabe haben, `main` alles was das Skript tut, anzustoßen und `mainloop` so lange die GUI existiert, Ereignisse abzuarbeiten.
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

wäre das eine alternative (nur ein beispiel : erzeugen eines kreises) :

Code: Alles auswählen

class bewegung:
        def Teil1(self):
                self.teil_1= anlage.create_oval(50,100,70,120, fill="blue")
                teil.set(1)

def main():
        tk.Button(root, text="Teil 1",command=bewegung.Teil1).grid(row=4, column=1)
soll also einen kreis machen und einen zähler (teil) auf 1 setzen. dieser wurde ganz oben als teil = tk.IntVat(value=0) erzeugt
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

Eine Alternative zu was? Was willst Du erreichen? Was willst Du mit dem Setzen von `teil` auf 1 bezwecken?

Die Klassen-"Definition" ist quatsch. Du erzeugst kein Exemplar von `bewegung`, die Klasse hat keinen Zustand und Du verwendest munter globale Variablen, die nicht existieren sollten.
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

@blackjack :

ich hab jetzt mal "exemplarisch" in deine richtung programmiert, also versucht, nach und nach deine verbesserungen aufzunehmen.
an einer stelle hänge ich jetzt, mir fehlt das verständnis, warum sich die variable "teil" nicht wie gewünscht beschreiben und lesen läßt :

Code: Alles auswählen

import tkinter as tk

root = tk.Tk()
anlage = tk.Canvas(root,width=700,height=300)

teil = tk.IntVar(value=0)

anlage.create_line(50, 100, 600, 100)
anlage.create_line(50, 120, 600, 120)
stopper_1 = anlage.create_rectangle(150, 80, 160, 100, fill="grey")
kolben_1 = anlage.create_rectangle(153, 80, 157, 100, fill="grey")
anlage.grid(row=1, column=0)

def stop1_zu():
        anlage.move(kolben_1,0,20)
        stop1 = 'zu'

def stop1_auf():
        anlage.move(kolben_1,0,-20)
        stop1 = 'auf'

def Teil1():
        teil1= anlage.create_oval(50,100,70,120, fill="blue")
        teil.set(1)			# <-------------hier setze ich sie doch auf 1, oder ?

def Teil2():
        teil2= anlage.create_oval(50,100,70,120, fill="blue")
        teil.set(2)

def Schritt():
        
        tk.Label(root, text=("Teile: ",teil.get())).grid(row=6,column=0)	# <-----------------aber  hier kommt immer 0 ...
        if teil.get() > 0:
                anlage.move(teil1,1,0)
        if teil.get() > 1:
                anlage.move(teil2,1,0)

def main():
        tk.Button(root, text="Stop1_zu",command=stop1_zu).grid(row=2, column=1)
        tk.Button(root, text="Stop1_auf",command=stop1_auf).grid(row=3, column=1)
        tk.Button(root, text="Teil 1",command=Teil1).grid(row=4, column=1)
        tk.Button(root, text="Teil 2",command=Teil2).grid(row=5, column=1)
        Schritt()
        root.mainloop()

main()
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht 8. Auch die ersten 10 Zeilen nach den Imports gehören in `main`.
Funktionsnamen werden wie Variablennamen klein_mit_unterstrich geschrieben. Funktionsnamen sollten Tätigkeiten ausdrücken, Teil1, Teil2 und Schritt tun das nicht. Alles was eine Funktion braucht, sollte sie über ihre Argumente bekommen. Damit sind auch Teil1 und Teil2 identisch und können zu einer Funktion zusammengefasst werden. Ein Fenster sollte nach der Erstellung komplett sein, immer weider neue Kreise oder Labels zu erzeugen, macht man nicht.
Jedes nicht-triviale GUI-Programm braucht Klassen. Dass statt dessen ein IntVar mißbrauchst, macht den Code nur unübersichlicher.
Du bist definitiv schon über "trivial" hinaus.

Vieles was Du da programmiert hast, verstehe ich nicht. Z.B. warum es ein stop1 oder kolben1 gibt, aber kein stop2, und warum man stop1 mehrfach zumachen kann. Und was das mit den Teilen auf sich hat.
Daher hier der Code, ohne den Sinn verstanden zu haben:

Code: Alles auswählen

import tkinter as tk
from functools import partial

class Anlage(tk.Tk):
    def __init__(self):
        super().__init__()
        self.anlage = tk.Canvas(self, width=700, height=300)
        self.anlage.grid(row=1, column=0)
        self.anlage.create_line(50, 100, 600, 100)
        self.anlage.create_line(50, 120, 600, 120)
        self.stopper_1 = self.anlage.create_rectangle(150, 80, 160, 100, fill="grey")
        self.kolben_1 = self.anlage.create_rectangle(153, 80, 157, 100, fill="grey")
        tk.Button(self, text="Stop1_zu", command=self.stop1_zu).grid(row=2, column=1)
        tk.Button(self, text="Stop1_auf", command=self.stop1_auf).grid(row=3, column=1)
        tk.Button(self, text="Teil 1", command=partial(self.erzeuge_teil, 1)).grid(row=4, column=1)
        tk.Button(self, text="Teil 2", command=partial(self.erzeuge_teil, 2)).grid(row=5, column=1)
        self.teil_label = tk.Label(self, text="Teile: -")
        self.teil_label.grid(row=6,column=0)
        self.teile = {}

    def stop1_zu(self):
        self.anlage.move(self.kolben_1,0,20)

    def stop1_auf(self):
        self.anlage.move(self.kolben_1,0,-20)

    def erzeuge_teil(self, nr):
        if nr in self.teile:
            # existiert schon. Was Tun?
            return
        self.teile[nr] = self.anlage.create_oval(50,100,70,120, fill="blue")
        self.teil_label['text'] = "Teile: {}".format(nr)

def main():
    anlage = Anlage()
    anlage.mainloop()

if __name__ == '__main__':
    main()
reinerdoll
User
Beiträge: 72
Registriert: Samstag 15. Juli 2017, 18:47

es soll eine animierte grafik entstehen, die sich wie eine kleine fertigungsanlage verhält.
an drei unabhängigen fertigungsmodulen werden teile (das sind die kreise) auf ein gemeinsames transportband (die striche) geschoben.
die stopper (es sind insgesamt rund 10) sollen die teile auf dem band (das 'dauernd läuft') an einigen stellen anhalten können.
dabei bilden sich dann staus usw.

in vb funktioniert das alles schon lange (die fertigungsmodule arbeiten da als threads, werden über eine opc-schnittstelle angesteuert), ich möchte mich aber in python umschauen.
ich kann das nicht theoretisch, ich brauche eine aufgabe/anwendung. und diese grafik schien mir geeignet.

also nichts wirklich wichtiges oder dringendes, eher eine turnübung.

eure tipps haben mir da viel geholfen bisher, wenn auch manche details echte herausforderungen dartellen. (das super() zum beispiel ;-)
Antworten