errechnete Werte in Canvas anzeigen

Fragen zu Tkinter.
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hi, ich bin nicht so faul...wollte nur "rasch" einen PNG-File im selben Script erzeugen...was aber nicht so recht funktioniert hat.
@ Sirius3 Hintergrundthread ist leicht gesagt, aber man muß es auch verstehen...ich gebe mir etwas Zeit!
Danke Allen! Gruß Rainer
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Erzeugen ist eigentlich auch kein Problem mit Image.new. Das hätte dann statt so

Code: Alles auswählen

        self.leinwand_image = Image.open(self.leinwand)
        self.hintergrung = ImageTk.PhotoImage(self.leinwand_image)
so ausgesehen:

Code: Alles auswählen

        self.leinwand_image = Image.new("RGB", (500, 400), (246, 230, 190))
        self.hintergrung = ImageTk.PhotoImage(self.leinwand_image)
Bei dem kleinen 4 x 4 halt ähnlich, so dass nicht unbedingt echte Images geladen werden müssen. Hatte nur kein Beispiel zu liegen, weil ich nur mit Animationen für Videos experimentiert hatte und da dann, falls es einmal fertig werden sollte, echte Images als Hintergrundmotive verwendet werden sollen und keine leeren Flächen.
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hallo Melewo, habe dein Beispiel jetzt nachvollziehen können, danke. Dir ist aber schon klar, dass das nicht mein Problem löst...
@__deets__, habe mal versucht, rauszukriegen, wie man die berechneten Werte aus einem zweiten Thread zum Eventhandler in der "root.mainloop()" rüberschaffen könnte. Im Tkinter-Tutorial empfehlen sie, die Threads über globale Variable zu verbinden. Leider wird das aber in nur wenigen Zeilen abgehandelt und es ist ja überall zu lesen, dass globale Variablen schlechter Programmierstil ist....weiterhin habe ich versucht zu verstehen, wie die Pipes funktionieren und da bin ich noch nicht durchgestiegen. Was mir jedoch klar wird, ist, dass ich in der "after-Schleife" auf "irgendwas" pollen kann/muß. D.h. also, der Eventhandler lauert auf seine Events und meine Schleife lauert auf "meinen Event". Beschreibt das in etwa die Situation, auf der ich dann aufbauen kann??
Vielen Dank, Rainer
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Eine Queue ist erstmal nichts anderes als eine Liste, an der man am einen Ende was reinstopft, und am anderen Ende was rausholt.

Und in tkinter schreibt man sich nun eine after-Methode, welche

- prueft, ob etwas in der Queue ist.
- wenn ja, es rausholt und dann ein GUI-update anstoesst.

Die muss halt im gewuenschten Interval aufgerufen werden.

Ein anderer Thread kann die Queue dann mit Daten bestuecken.

Ein Beispiel findet sich natuerlich auch hier im Forum, zB hier: viewtopic.php?t=40146 - letzten Post anschauen.

Streng genommen ist die Queue auch dort globaler Zustand, aber das muss nicht so sein - deine Quellen haben in dieser Beziehung nicht recht. Und der Post zeigt auch, wie es gemacht wird: man muss halt die Instanz der Queue sowohl an die GUI-Klassen uebergeben, als auch an den Thread. Danach braucht niemand anderes darauf eine Referenz.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

@pyzip: Und mit queue hast Du Dich noch nicht befasst?
Liest sich für mich zumindest so, als ob Du Dir das wirklich einmal näher betrachten solltest.
The queue module implements multi-producer, multi-consumer queues. It is especially useful in threaded programming when information must be exchanged safely between multiple threads.
https://docs.python.org/3/library/queue.html
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hallo, vielen Dank für den Tip. Nein, über Queue bin ich bisher noch nicht gestolpert! Im Tutorial, dass ich hauptsächlich benutze, ist mir jedenfalls nichts aufgefallen. (https://www.python-kurs.eu/) Aber das ist doch ein Ansatz. Auch wenn ich heute Abend den Spyder3 vielleicht nicht mehr anwerfe...
Gruß Rainer
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Also ich hab' mir da mal gerade das Kapitel ueber OO angeschaut - und erschauere... https://www.python-kurs.eu/python3_klassen.php strotzt nur so von Unfug.

Und Queue/queue kommt in meinem Beispiel (bzw dem Link) zur Anwendung.
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hallo, danke für die Links. Habe eben einfach mal gegoogelt und direkt noch ein schönes Beispiel gefunden.
Und was für ein Tutorial empfehlt ihr??
Gruß Rainer
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Es gibt nicht das eine Tutorial, in dem alles ausgesprochen gut und zugleich leichtverständlich für Einsteiger erklärt wird. Zumindest kenne ich keins. Wenn, so wurden die Tutorials von https://docs.python.org/3/tutorial/index.html wiederholt empfohlen. Wer nach einem Ansatz zur Lösung eines einzelnen Problems sucht, wird oft bei stackoverflow.com fündig, nicht aber um die Grundlagen zu lernen.

Und was die Beschreibung einer Queue anbelangt, da finde ich die von __deets__ weiter oben verfasste ganz gut und halte diese für allgemein verständlich. Selbiges betrifft in diesem Zusammenhang deren Verwendung in Tkinter. Damit kann man etwas anfangen, kann erfassen, was eine Queue ist, kann sich das verlinkte Beispiel ansehen und nach weiteren Beispielen suchen.
Ich würde jetzt an Deiner Stelle einfach mit einem abgemagerten Tkinter-Beispiel beginnen, in dem weder Canvas noch sonstige Images verwendet werden, nur mit after und einer einfachen Ausgabe. Dann würde ich eine Testreihe machen, wie ich in Tkinter mit einer Queue die Daten übernehmen und erst einmal nur ausgeben könnte.
Und wenn Du das geschafft hast, könntest Du Dir in einem zweiten Schritt immer noch überlegen, wie Du die übernommenen Daten am besten visualisieren könntest.
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hallo, hat leider etwas gedauert. Aber ich habe nun das Problem prinzipiell gelöst. Allerdings steigt Spyder manchmal mit der Meldung "Tcl_AsyncDelete: async handler deleted by the wrong thread" aus. Nach jedem zweiten Start des Programms, geht es wieder weiter. Werden meine beiden Threads nicht richtig beendet? Hier das Script

[code#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Sep 28 14:31:27 2017

@author: zip
"""

import tkinter as tk
import time
from threading import Thread
import queue

q = queue.Queue()



class Diagramm(tk.Frame):


def __init__(self, master):
super().__init__(master)
self.pack()
self.master = master
self.master.title("Diagramm")
self.DIAG_X_OFFSET_L = 100
self.DIAG_X_OFFSET_R = 50
self.DIAG_Y_OFFSET_O = 20
self.DIAG_Y_OFFSET_U = 150
self.M_WIDTH = 810
self.M_HEIGHT = 610
self.C_WIDTH = self.M_WIDTH - 10
self.C_HEIGHT = self.M_HEIGHT - 10
self.DIAG_WIDTH = self.M_WIDTH - self.DIAG_X_OFFSET_L - self.DIAG_X_OFFSET_R
self.DIAG_HEIGHT = self.M_HEIGHT - self.DIAG_Y_OFFSET_O - self.DIAG_Y_OFFSET_U
self.create_widgets()
self.placeWindow()
self.create_canvas()
self.a = self.y_achse_txt(0, 1)
self.zeichne_diagramm(self.DIAG_X_OFFSET_L, self.DIAG_Y_OFFSET_O,
self.C_WIDTH - self.DIAG_X_OFFSET_R,
self.C_HEIGHT - self.DIAG_Y_OFFSET_U, self.a)
self.get_koordinate()

def placeWindow(self):

x = 100
y = 200
self.master.geometry('%dx%d+%d+%d' % (self.M_WIDTH, self.M_HEIGHT, x, y))

def create_widgets(self):
# x = 150
# y = 150

self.canvas = tk.Canvas(self, width=(self.C_WIDTH), height=(self.C_HEIGHT))
self.canvas.pack()

# self.start_button = tk.Button(self, text="start", command=self.greet)
# self.start_button.pack()
##b = Button(master, text="Help", state=DISABLED)
# self.close_button = tk.Button(self, text="Close", state="disabled",
# command=self.master.destroy)#active, disabled, or normal
# self.close_button.pack()# config(**options)

def greet(self):
print("Greetings!")
self.close_button.config(state="active")

def y_achse_txt(self, null_p, max_p):
#Y- Beschriftung berechnen von 0_p bis max_p
#gibt String-Liste zurück!
self.y_txt=[]
for i in range(null_p, (max_p * 10) + 1):
i=i/10
a='{0:.2f}'.format(i)
self.y_txt.append(a)
return self.y_txt

def create_canvas(self):
canvas = tk.Canvas(self.master, width=self.C_WIDTH,
height=self.C_WIDTH)
canvas.pack()


def zeichne_diagramm(self, x_1, y_1, x_2, y_2, y_txt):
x = int((y_2 - y_1)/10)
index=0
self.canvas.create_rectangle(x_1, y_1, x_2, y_2, fill="white",width=3)
for i in range(y_2, y_1 - x, -x):
self.canvas.create_line(x_1, i, x_2, i, fill="black", width=1)

self.canvas.create_text(x_1 - 20, i, text=y_txt[index])
index += 1

def zeichne_kreis(self, x, y, farbe="red"):
#kleiner Kreis an Pos. x, y
return self.canvas.create_oval(x - 2, y - 2, x + 2, y + 2, fill=farbe)

def zeichne_punkt(self, g):

self.zeichne_kreis((150 + (g * 10)), (150 + (g * 10)), farbe="red")

def get_koordinate(self):

while not q.empty():
a = q.get()
a = int(a)
self.zeichne_punkt(a)
time.sleep(0.1)

self.canvas.after(1, self.get_koordinate)


def counter1():

for i in range(1, 6):
q.put(i)
time.sleep(5)


#def sleeper(i):
# print("thread %d sleeps for 5 seconds" % i)
# time.sleep(5)
# print("thread %d woke up" % i)

def diagramm_fenster():
root = tk.Tk()
dia = Diagramm(master=root)
dia.mainloop()

def main():



tc = Thread(target=counter1)

tw = Thread(target=diagramm_fenster)

tw.start()
tc.start()

# eingabe = input("Ihre Eingabe? ")

if __name__ == '__main__':
main()][/code]
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es gibt hier eine Vorschau-Funktion. Die solltest du nutzen um zu verhindern, dass dein Code so falsch formatiert wird.

Was man entziffern kann ist, das du die GUI in einem neuen Thread startest. Das geht nicht, und deine Fehlermeldung ist auch ein Hinweis darauf. Das du ueberhaupt etwas zu sehen bekommst ist Zufall, und nicht garantiert.

Ich verstehe auch gar nicht, warum du den ueberhaupt startest, wenn der Main-Thread dann einfach vor sich hin wartet.

GUI darf NUR aus dem main-thread, also einfach dem Thread den es eh immer gibt, angefasst werden. Nirgends sonst.
Nimm das also raus, und spring einfach direkt dein diagramm_fenster() ein - nach start des counter-Threads

Warum du dann in get_koordinate ein time.sleep einlegst ist auch nicht ersichtlich.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@pyzip: Tk-Objekte sollten prinzipiell nur vom Hauptthread erzeugt werden. Warum erzeugst Du einen `diagramm_fenster`-Thread?

Zeile 14: Queue q sollte nicht global sein, sondern als Argument dem counter-Thread und dem Fenster übergeben werden.
Zeile 23: pack gehört hier nicht hin, sondern in die aufrufende Funktion
Zeile 26ff: Konstanten sollten auf Klassen- oder Modulebene definiert werden und nicht in __init__.
`placeWindow` sollte `place_window` heißen und muß eigentlich gar keine eigene Funktion sein. Wenn plaziert wird, gehört das in main. Warum sind `x` und `y` hier nicht als Konstanten definiert, sondern direkt als Zahlen?
Zeile 55: alle Attribute sollten in __init__ eingeführt werden. Die Funktion ist eigentlich unnötig und kann in __init__ integriert werden.
Zeile 69: in `y_achse_txt` sollte y_txt nicht definiert werden, zumal das Ergebnis in `__init__` an a (ein schlechter Name) gebunden wird. Damit ist die Funktion nicht mehr von self abhängig und könnte raus aus der Klasse.
Zeile 79: nochmal ein canvas? warum? wird nicht benutzt!
Zeile 103: Der Name get_koordinate läßt erwarten, dass irgendwas ermittelt wird; sollte besser `process_coordinates` heißen.
Zeile 107: Das was in die Queue gesteckt wird, ist schon eine Zahl. Weg damit.
Zeile 109: was soll das sleep? Das darf in GUI-Programmen nicht vorkommen!
Zeile 132ff: Leerzeilen unnötig.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Sep 28 14:31:27 2017

@author: zip
"""
import tkinter as tk
import time
from threading import Thread
import queue

DIAG_X_OFFSET_L = 100
DIAG_X_OFFSET_R = 50
DIAG_Y_OFFSET_O = 20
DIAG_Y_OFFSET_U = 150
M_LEFT = 100
M_TOP = 200
M_WIDTH = 810
M_HEIGHT = 610
C_WIDTH = M_WIDTH - 10
C_HEIGHT = M_HEIGHT - 10
DIAG_WIDTH = M_WIDTH - DIAG_X_OFFSET_L - DIAG_X_OFFSET_R
DIAG_HEIGHT = M_HEIGHT - DIAG_Y_OFFSET_O - DIAG_Y_OFFSET_U

def get_y_axis_ticks(null_p, max_p):
    #Y- Beschriftung berechnen von 0_p bis max_p
    #gibt String-Liste zurück!
    return ['{0:.2f}'.format(i/10)
        for i in range(null_p, (max_p * 10) + 1)
    ]

class Diagramm(tk.Frame):
    
    def __init__(self, queue, master):
        super().__init__(master)
        self.queue = queue
        self.master = master
        self.master.title("Diagramm")
        self.canvas = tk.Canvas(self, width=C_WIDTH, height=C_HEIGHT)
        self.canvas.pack()
        ticks = get_y_axis_ticks(0, 1)
        self.zeichne_diagramm(DIAG_X_OFFSET_L, DIAG_Y_OFFSET_O,
                         C_WIDTH - DIAG_X_OFFSET_R,
                         C_HEIGHT - DIAG_Y_OFFSET_U, ticks)
        self.process_coordinates()
        
    def zeichne_diagramm(self, x_1, y_1, x_2, y_2, ticks):
        x = int((y_2 - y_1)/10)
        self.canvas.create_rectangle(x_1, y_1, x_2, y_2, fill="white",width=3)
        for tick, i in zip(ticks, range(y_2, y_1 - x, -x)):
            self.canvas.create_line(x_1, i, x_2, i, fill="black", width=1)
            self.canvas.create_text(x_1 - 20, i, text=tick)

    def zeichne_kreis(self, x, y, farbe="red"):
        #kleiner Kreis an Pos. x, y
        return self.canvas.create_oval(x - 2, y - 2, x + 2, y + 2, fill=farbe)

    def zeichne_punkt(self, g):
        self.zeichne_kreis((150 + (g * 10)), (150 + (g * 10)), farbe="red")
        
    def process_coordinates(self):
        while not q.empty():
            self.zeichne_punkt(q.get())
        self.canvas.after(100, self.process_coordinates)


def counter1():
    for i in range(1, 6):
        q.put(i)
        time.sleep(5)
    
def main():
    q = queue.Queue()
    tc = Thread(target=counter1, args=(q,))

    root = tk.Tk()
    dia = Diagramm(q, master=root)
    dia.pack()
    root.geometry('%dx%d+%d+%d' % (M_WIDTH, M_HEIGHT, M_LEFT, M_TOP))
    root.mainloop()

if __name__ == '__main__':
    main()
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hallo und vielen Dank, vor allem auch für die umfassende Korrektur von Sirius3! Wenn ich das Script laufen lasse, dann bekomme ich diesen Fehler. Muß ich "q" noch in der Klasse übergeben oder warum kennt er "q" nicht? Schätze mal, dass er dann "q" auch nicht in "counter1()" kennen wird...

File "/home/zip/py/treading-4.py", line 63, in process_coordinates
while not q.empty():

NameError: name 'q' is not defined

Das "sleep" habe ich gemacht, damit der Eventhandler vom Fenster auch mal dran kommt. Habe gelesen, dass "get" blockiert. Und dass ich mit meinem Window im Thread quasi doppelt gemoppelt habe, habe ich mir fast schon gedacht.
Und zum Schluß noch die Frage, was ich denn machen muß, um den Code "vernünftig" ins Forum zu kopieren. Wenn ich den Code aus Spyder kopiere, sind die Zeilennummern nicht drin, aber wenigstens die Einrückungen. Dass die hier auch verschwunden sind, ist mir gar nicht aufgefallen! Sorry...
Gruß Rainer
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@pyzip: dass in counter1 das Argument fehlt, `def counter1(q):`, hast Du sicher schon selbst herausgefunden. `queue.get` blockiert nicht, da Du ja vorher prüfst, dass auch wirklich ein Element in der Schlange wartet. Alternativ könnte man auch `get_nowait` verwenden und die Empty-Exception abfangen. Das `sleep` macht genau das Gegenteil, von dem, was Du erreichen willst; es blockiert die GUI.

Im Forum gibt es über dem Editfeld das Dropdown `Code auswählen`.
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

@ Sirius3, hallo, ja, dass mit "q" habe ich auch schon gesehen...und ich kopiere den Text aus Spyder und füge ihn mit "code" ein. Trotzdem...
Gruß Rainer
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

@pyzip man sieht klar, dass du dem oeffnenden code-tag seine schliessende Klammer geklaut hast. Insofern ist das jetzt nicht weiter ein Mysterium. Spiel doch mal mit der Vorschau-Funktion, ich bin mir sicher das klappt.
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hallo. auch wenn ich mich jetzt völlig entblöde...es gibt kein DropDown "Code auswählen", es gibt nur "code" und dahin paste ich meinen code von Spyder.
Und zum Code an sich.
Die Klasse kennt "q"immer noch nicht!? Habe einfach keine Idee, was ich da machen soll.
Gruß Rainer
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Doch, das gibt es - direkt zwischen diesem Eingabefenster und der Toolbar mit B, I, Quote, Code etc...

Der code-button funktioniert auch, aber du musst schon die tags in ordnung lassen.
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hi --deets--, also ich will mich ja jetzt nicht noch blöder darstellen, aber mit deiner Anweisung kann ich überhaupt nichts anfangen! Was verstehe ich nicht???? "Der code-button funktioniert auch, aber du musst schon die tags in ordnung lassen". Wie, wenn ich aus Spyder kopiere. Ich lass' da doch alles in Ordnung?
Rainer
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

@pyzip: Sei beruhigt, ist mir in den ersten Wochen nicht anders ergangen, gesucht und gesucht und dennoch über das Feld hinweggesehen.

Bild
Antworten