Tkinter Programm nebenläufig

Fragen zu Tkinter.
Antworten
ichbins
User
Beiträge: 37
Registriert: Samstag 5. September 2020, 07:07

Freitag 16. Oktober 2020, 23:11

Hallo

ich finde eure Hilfe super. Jetzt kommt ein Problem mit Tkinter. Ich wollte vom Hauptprogramm Tkinter ausführen wo die Funktion werte ausgeführt wird und die Variaben aufgerufen werden. Das Problem ist jedoch dass ich das Programm schließen muss bevor ich die Werte bekomme und das Hauptprogramm die Werte weiter verarbeiten kann. So habe ich es mir vorgestellt es funktioniert nicht perfekt. Muss ich das mit threading lösen oder habt ihr eine bessere bzw. andere Problemlösung?

Danke!

Hauptprogramm

Code: Alles auswählen

import probe_gui

werte=probe_gui.Window().werte(None)

wert1=werte[0]
wert2=werte[1]
wert3=werte[2]

print(wert1, wert2, wert3)

while(True):
    #Funktionen
    pass
GUI

Code: Alles auswählen

import tkinter as tk

class Window():
    def __init__(self): 

        self.root = tk.Tk()
        self.root.geometry("600x400")        

        self.entries=[]
        for column in range(0,4):
            self.entry=tk.Entry(self.root, width=5)
            self.entry.pack(side=tk.LEFT, anchor=tk.W)
            self.entries.append(self.entry)
        
        self.button=tk.Button(self.root, text='Auslesen', command=self.werte)
        self.button.pack()
        
        self.root.bind('<Return>', self.werte)
                  
    def werte(self, event):
        auslesen=self.auslesen()
        funktion1=self.funktion1()
        funktion2=self.funktion2()
        #print('Werte wurde gedrückt')
        #print(auslesen, funktion1, funktion2)
        return auslesen, funktion1, funktion2
            
    def auslesen(self):   
        liste=[]
        for x in self.entries: 
            #print(x)
            #print(x.get())
            x=x.get()
            liste.append(x)
        #print(liste)
        return liste
    
    def funktion1(self):
        #print('Funktion1')
        return 1
    
    def funktion2(self):
        #print('Funktion2')
        return 2
   
            
graph=Window()
graph.root.mainloop()
Benutzeravatar
__blackjack__
User
Beiträge: 7056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 17. Oktober 2020, 01:30

@ichbins: Du ziehst das von der falschen Seite her auf. Die GUI-Hauptschleife ist das Hauptprogramm. Das ist das was man ausführt. Da muss sich Dein Code entweder direkt integrieren lassen, oder *der* läuft in einem Thread und die GUI kommuniziert per Queue(s) und `after()`-Methode mit dem Code. Die Geschäftslogikt ruft keinen GUI-Code auf.

Beim Importieren von Modulen darf kein Code ablaufen der mehr macht als Konstanten, Funktionen, und Klassen zu definieren. Bei Dir wird da sogar eine GUI ausgeführt.
long long ago; /* in a galaxy far far away */
ichbins
User
Beiträge: 37
Registriert: Samstag 5. September 2020, 07:07

Samstag 17. Oktober 2020, 20:29

Ich bin gerade am Überlegen ob es sich auszahlt in Thread und Queue einzuarbeiten oder ob es doch besser wäre sich in ein Webframework einzuarbeiten. Wird bei einem Webframework auch Thread und Queue benötigt oder geht es auch ohne.
Sirius3
User
Beiträge: 13054
Registriert: Sonntag 21. Oktober 2012, 17:20

Samstag 17. Oktober 2020, 20:43

Das Problem ist doch das selbe: Du hast eine Ebene mit Benutzerinteraktion und parallel dazu irgendeine Berechnung. Also brauchst Du Threads und Queues.
ichbins
User
Beiträge: 37
Registriert: Samstag 5. September 2020, 07:07

Sonntag 18. Oktober 2020, 23:04

Irgendwie fehlt mir der Ansatz wie ich die Threads bei zwei Klassen bzw. zwei Dateien lösen kann. Habt ihr da ein Bsp. oder paar Tipps. Beim Start bleibt es in der Klasse Messinstrumente hängen.

LG

GUI

Code: Alles auswählen

import tkinter as tk
import probe_hauptprogramm
from threading import Thread


class Window():
    def __init__(self): 

        self.root = tk.Tk()
        self.root.geometry("600x400")        

        self.entries=[]
        for column in range(0,4):
            self.entry=tk.Entry(self.root, width=5)
            self.entry.pack(side=tk.LEFT, anchor=tk.W)
            self.entries.append(self.entry)
        
        self.button=tk.Button(self.root, text='Auslesen', command=self.werte)
        self.button.pack()
        
        #self.root.bind('<Return>', self.werte)
                  
    def werte(self):
        auslesen=self.auslesen()
        funktion1=self.funktion1()
        funktion2=self.funktion2()
        probe_hauptprogramm.Messinstrumente(auslesen, funktion1, funktion2)

            
    def auslesen(self):   
        liste=[]
        for x in self.entries: 
            #print(x)
            #print(x.get())
            x=x.get()
            liste.append(x)
        #print(liste)
        return liste
    
    def funktion1(self):
        #print('Funktion1')
        return 1
    
    def funktion2(self):
        #print('Funktion2')
        return 2
    

if __name__=='__main__':            
    graph=Window()
    graph.root.mainloop()
    x = Thread(graph)
    x.start()

Messprogramm

Code: Alles auswählen

from time import sleep
from threading import Thread

class Messinstrumente:
    def __init(self, auslesen, funk1, funk2):
        self.lesen=auslesen
        self.function1=funk1
        self.function2=funk2
        print(self.lesen, self.function1, self.function2)
    
    x = Thread()
    x.start()
   
    #Hauptprogramm
    while(True):
        #Funktionen
        print('programm wird ausgeführt´')
        sleep(4)

        try:
            print(self.lesen)
            print(self.function1)
            print(self.function2)
        except:
            print('Fehler')
Benutzeravatar
__blackjack__
User
Beiträge: 7056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Montag 19. Oktober 2020, 02:03

@ichbins: Das sieht nach programmieren nach raten aus. Ein `Thread`-Objekt erstellen dem weder ein `target` mitgegeben wurde noch die `run()`-Methode überschrieben wurde macht einfach gar nichts wenn man diesen Thread mit `start()` startet. Also da wird ein Thread gestartet der dann auch gleich wieder beendet ist, weil der ja nix macht.

Und dann folgt auf Modulebene eine Endlosschleife bei der Du die Ausnahme die da unweigerlich auftritt, weil `self` nirgends definiert ist, über ein nacktes ``except:`` in eine absolut nicht hilfreiche Ausgabe des Wortes "Fehler" behandelst. Keine nackten ``except:``\s es sei denn man protokolliert die Ausnahme samt Traceback und es ist tatsächlich sinnvoll möglich an der Stelle *alle* Ausnahmen einfach zu ignorieren und so weiter zu machen, als wäre nichts passiert.

Das ganze passiert schon beim importieren von `probe_hauptprogramm` weil Code auf Klassenebene ausgeführt wird wenn die ``class``-Anweisung ausgeführt wird.

Ich sehe auch noch nicht so ganz ob/warum das eine Klasse sein muss.
long long ago; /* in a galaxy far far away */
ichbins
User
Beiträge: 37
Registriert: Samstag 5. September 2020, 07:07

Montag 19. Oktober 2020, 16:24

Da es nicht so funktioniert hat wie ich dachte, war es bisschen programmieren nach raten. Deswegen habe ich gemeint ein Bsp. wäre nicht schlecht. Ich habe es bisschen geändert, aber bei tkinter bleibt der Button noch immer blockiert.

Du hast gemeint ein Modul sollte immer eine Klasse oder Funktion bzw. Konstante sein. Letztendlich ist es das Hauptprogramm jedoch wird die Grafik zum Hauptprogramm. Ich könnte die 400 Zeilen in eine Funktion packen. Aber es kommt mir vor als ob es bisschen viele Zeilen für eine Funktion wären. Vielleicht könnte man das Programm noch in mehrere Funktionen unterteilen. Bzw. wie hast du dir das vorgestellt.

Code: Alles auswählen

import tkinter as tk
import probe_hauptprogramm
from threading import Thread


class Window():
    def __init__(self): 

        self.root = tk.Tk()
        self.root.geometry("600x400")        

        self.entries=[]
        for column in range(0,4):
            self.entry=tk.Entry(self.root, width=5)
            self.entry.pack(side=tk.LEFT, anchor=tk.W)
            self.entries.append(self.entry)
        
        self.button=tk.Button(self.root, text='Auslesen', command=self.werte)
        self.button.pack()
        
        #self.root.bind('<Return>', self.werte)
                  
    def werte(self):
        auslesen=self.auslesen()
        funktion1=self.funktion1()
        funktion2=self.funktion2()
        probe_hauptprogramm.messinstrumente(auslesen, funktion1, funktion2)

            
    def auslesen(self):   
        liste=[]
        for x in self.entries: 
            #print(x)
            #print(x.get())
            x=x.get()
            liste.append(x)
        #print(liste)
        return liste
    
    def funktion1(self):
        #print('Funktion1')
        return 1
    
    def funktion2(self):
        #print('Funktion2')
        return 2
    

if __name__=='__main__':            
    graph=Window()
    graph.root.mainloop()
    x = Thread(target=graph)
    x.start()
    y = Thread(target=probe_hauptprogramm.messinstrumente)
    y.start()

Code: Alles auswählen

from time import sleep
from threading import Thread

def messinstrumente(auslesen, funk1, funk2):

    #Hauptprogramm
    while(True):
        #Funktionen
        print('programm wird ausgeführt´')
        sleep(4)

        print(auslesen)
        print(funk1)
        print(funk2)
Sirius3
User
Beiträge: 13054
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 19. Oktober 2020, 17:03

@ichbins: dass self.entry falsch ist, wurde Dir schon gesagt, dass `x` ein sehr schlechter Variablenname ist, wurde Dir schon gesagt.

Wenn Du von einem Thread mit einer GUI kommunizieren willst, brauchst Du eine Queue.
`messinstrumente` übergibst Du Funktionen, was nicht sein darf, weil aus Threads heraus keine GUI angesprochen werden darf.
Was hast Du Dir bei x = Thread(target=graph) gedacht? graph ist doch nicht ausführbar.
Nach mainloop darf kein Code mehr kommen.

Code: Alles auswählen

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

def messinstrumente(queue):
    print('programm wird ausgeführt´')
    while True:
        werte = queue.get()
        print(werte)

class Window():
    def __init__(self, queue): 
        self.queue = queue
        self.root = tk.Tk()
        self.entries = []
        for column in range(4):
            entry=tk.Entry(self.root, width=5)
            entry.pack(side=tk.LEFT, anchor=tk.W)
            self.entries.append(entry)
        tk.Button(self.root, text='Auslesen', command=self.werte_uebertragen).pack()

    def werte_uebertragen(self):   
        werte = [entry.get() for entry in self.entries]
        self.queue.put(werte)

def main():
    queue = Queue()
    mess_thread = Thread(target=messinstrumente, args=(queue,))
    graph = Window(queue)
    graph.root.mainloop()

if __name__=='__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 7056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Montag 19. Oktober 2020, 17:26

@ichbins: Natürlich blockiert das, Du rufst da bei einem Klick auf die Schaltfläche eine Funktion auf die eine Endlosschleife enthält, also nie wieder zur GUI-Hauptschleife zurück kehrt.

Die Funktion macht auch nicht so wirklich Sinn, denn da werden alle vier Sekunden die gleiche Werte per `print()` ausgegeben.

Was ebenfalls blockiert ist die GUI-Hauptschleife, solange das Fenster angezeigt wird. Nach dem schliessen des Fensters versuchst Du das `Tk`-Objekt in einem Thread auszuführen, was nicht geht weil das nicht aufrufbar ist. Und die `probe_hauptprogramm.messinstrumente()`-Funktion ebenfalls in einem Thread auszuführen, was aber nicht klappen wird, weil die ja drei Argumente erwartet, die aber nicht bekommt. Und wie gesagt, sinnvoll ist das auch nicht bis in alle Ewigkeit alle vier Sekunden die gleichen Werte auszugeben.

Die Namensgebung ist auch teilweise sehr verwirrend. `auslesen` ist eine Liste und `funktion1` und `funktion2` sind ganze Zahlen — das ist extrem irreführend. Wie man an der Antwort von Sirius3 sehen kann. Bei `messinstrumente()` dann `funk1` und `funk2` macht es nicht besser. Auf welchen Frequenzen wird denn da gesendet? Auch `messinstrumente()` geht als Name nicht. Funktionsnamen beschreiben die Tätigkeit die von der Funktion (oder Methode) ausgeführt wird. `messinstrumente` ist keine Tätigkeit.

Ich habe nicht gemein ein Modul sollte immer eine Klasse, Funktion, oder Konstante sein. Also ”sein” schon mal gar nicht, denn ein Modul ist ein Modul. Der Code auf Modulebene sollte nur Konstanten, Funktionen, und Klassen definieren. Da sollte kein Code stehen der globale Variablen definiert oder gar das Hauptprogramm. Man muss ein Modul importieren können, ohne das dabei mehr passiert als eben jene Definitionen von Konstanten, Funktionen, und Klassen.

Wenn Du da 400 Zeilen stehen hast die etwas anderes machen, dann ist das definitiv falsch. Und definitiv nichts was man mit einer GUI verbinden/-wenden kann. Dazu muss das mindestens in Funktionen stehen, wenn nicht sogar auch eine eigene Klasse sein wenn es Zustand gibt, der über meherere Aufrufe von der GUI hinweg gemerkt werden muss. Vielleicht ginge auch eine Funktion die in einem Thread läuft und über Queues mit der GUI kommuniziert.

Die 400-Zeilen-Aussage macht mir so ein bisschen sorgen. Denn für GUI-Programmierung braucht man Klassen. Für Klassen muss man Funktionen drauf haben. Und Funktionen heisst kleine globalen Variablen. Und jetzt kommst Du und sagst Du hast da 400 Zeilen Code die offenbar nicht einmal in *einer* Funktion stecken. Dann hast Du erst einmal noch gar kein Problem mit GUI-Programmierung, denn da ist noch überhaupt nicht dran zu denken.
long long ago; /* in a galaxy far far away */
ichbins
User
Beiträge: 37
Registriert: Samstag 5. September 2020, 07:07

Montag 19. Oktober 2020, 22:37

Danke für die ausführlichen Antworten.

@Sirius3: Kann es sein dass am Code irgendetwas nicht stimmt? Es wird 'while True' nicht ausgeführt bzw. keine Ausgabe.

@Blackjack: funk1 und funk2 sind die Funktionen 1 und 2. Es geht darum die Werte die in den ganzen Entry Widgets sind, in der Funktion messinstrumente aufzurufen.

Es gibt das Hauptprogramm mit 400 Zeilen was man noch in Funktionen aufteilen könnte und ein Modul aus Funktionen mit ca. 600 Zeilen. Man kann ein Programm immer verschönern jedoch ist mir zuerst wichtig dass ich die Eingaben im GUI im Hauptprogramm aufrufen kann.
Benutzeravatar
__blackjack__
User
Beiträge: 7056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Montag 19. Oktober 2020, 23:12

@ichbins: `funk1` und `funk2` sind keine Funktionen sondern die ganzen Zahlen 1 und 2. Zahlen sind keine Funktionen. Darum ist das echt verwirrend die so zu benennen. Die Werte in den Entries kann man auch nicht ”aufrufen”. Aufrufen ist einen Ausdruck zu schreiben der etwas aufrufbares ergibt und dann dahinter runde Klammern, optional mit Argumenten. *Das* ist ein Aufruf. Das geht mit Zeichenketten (die stehen in den Entries) oder Zahlen nicht.

Es geht hier nicht ums verschönern. Sauber auf echte Funktionen aufteilen und keine globalen Variablen zu verwenden ist nicht irgendwie schön, sondern der normale Grundzustand. Wenn man nicht einmal da ist, macht es keinen Sinn darüber nachzudenken wie man da eine GUI drauf setzt, denn das geht schlicht nicht. Eine GUI kann nicht Code aufrufen der auf Modulebene steht, dazu *muss* der mindestens in Funktionen stecken. Und da man für die GUI sowieso Klassen braucht, gibt es auch keinerlei Ausreden woanders globale Variablen zu verwenden.

Falls Dein Hauptprogramm in einem eigenen Thread läuft muss das auch mindestens in einer Funktion stecken, denn Code auf Modulebene kann man nicht in einem anderen Thread laufen lassen (es sei denn man macht noch mehr Sachen die man einfach nicht macht!).

Der Code in dem Thread kann auch nicht einfach so auf die GUI zugreifen, der muss threadsicher mit dem Thread in dem die GUI läuft kommunizieren. Was in der Regel am einfachsten über Queues geht.

Bei dem was Du da gezeigt hast ist aber nicht ganz klar was Du überhaupt erreichen willst. Wenn nur alle vier Sekunden *kurz* irgend etwas gemacht werden soll, dann braucht man keine Threads sondern kann einfach die `after()`-Methode verwenden.
long long ago; /* in a galaxy far far away */
ichbins
User
Beiträge: 37
Registriert: Samstag 5. September 2020, 07:07

Dienstag 20. Oktober 2020, 20:03

Ich möchte erreichen dass beim Start des Programms die GUI angezeigt wird und das Hauptprogramm blockiert. Nachdem ich den Button drücke sollen die ganzen Werte aus den Entry Widgets an das Hauptprogramm übergeben werden und das Hauptprogramm starten. Somit kommt auch das Hauptprogramm in eine while True Schleife und läuft die ganze Zeit. Die GUI ist nur nebenbei offen. Falls ich wieder auf den Button drücke sollen die Aktualisierten Werte wieder an das Hauptprogramm übergeben werden und von neu starten bzw. einfach nur die aktualisierten Werte übernehmen und damit weiter rechnen bzw. das Hauptprogramm bearbeiten. Es sind auch Wartezeiten in der while Schleife die mehrere Sekunden machen aber es läuft die ganze Zeit. Das Problem ist halt wenn das Hauptprogramm läuft dass die GUI blockiert. Bei diesem Bsp. war ja bei sleep() der Button blockiert. Somit war auch die Rede dass ich Threads brauche und Queues kamen auch zu Wort.

Ich habe das Programm vom Sirius probiert aber es lief nicht ganz nach meiner Vorstellung. Wenn ich auf "Ausführen" drücke erschienen nie die Werte von print(werte).
Sirius3
User
Beiträge: 13054
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 21. Oktober 2020, 07:21

Da fehlt ja auch ein `mess_thread.start()`.
Und bei GUIs mußt Du umdenken. Das "Hauptprogramm" wie Du es nennst, läuft nebenbei, denn GUIs betrachten sich immer selbst als Hauptprogramm. Daran kann man nichts ändern. Und so wie Du das beschreibst, ist es ungefähr das, was ich geschrieben habe: Das Messprogramm läuft in einem Thread und kann regelmäßig die Queue abfragen, ob neue Werte eingetragen wurden.
Antworten