errechnete Werte in Canvas anzeigen

Fragen zu Tkinter.
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: 14480
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: 14480
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: 17703
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: 17703
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: 14480
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: 14480
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
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

danke...na denn...
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

Hallo, hier also die lauffähige Version. Die Achsbeschriftungen sind aber noch nicht wirklich gut. Es funktioniert eigentlich nur mit y-Werten zwischen 0 und 1, aber dass kann ja jeder selbst einbauen...
Danke an Alle.
Gruß Rainer

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)
        print("das sind ticks : ", ticks)
        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 self.queue.empty():
            self.zeichne_punkt(self.queue.get())
        self.canvas.after(100, self.process_coordinates)
 
#def counter1(q): 
def counter1(q):
    for i in range(1, 6):
        q.put(i)
#        print("aus counter i = ", i)
        time.sleep(5)
   
def main():
    q = queue.Queue()
    tc = Thread(target=counter1, args=(q,))
    tc.start()
    
    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()
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@pyzip: Du hast jetzt noch das Problem, dass Du an vielen Stellen Informationen über Dein Diagramm verteilt hast. Die Skalierung in Zeile 26ff, 42, 44, 50ff, und 61. Es ist also nur mit großen Aufwand möglich, daran etwas zu ändern. Ein erster Schritt wäre es, Funktionen zu schreiben, die Canvas-Koordinaten in Diagramm-Koordinaten und umgekehrt umrechnen können.
pyzip
User
Beiträge: 89
Registriert: Freitag 16. Juni 2017, 19:36

@Sirius3, ja du hast recht. Ursprünglich wollte ich auch nur ein paar Werte der Zwischenergebnisse meiner Rechnungen darstellen. Nun ist es natürlich schlau, Skalierung und Beschriftung etwas besser und universeller zu gestalten. Mal sehen...
Gruß Rainer
Antworten