Seite 1 von 1
Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Sonntag 2. Mai 2021, 20:04
von kitsab
Hallo,
bin relativ neu bei der TKinter Verwendung.
Ich nutze einen Button um eine Klasse eines imporierten Moduls auszuführen:
GUI Modul:
...
Code: Alles auswählen
class Window:
.....
def init_main_window(self)
......
CalcButton = Button(self, text="Kalkulation", command=lambda: self.runCalcButton())
self.sbar = Label(self, textvariable=self.statusvar, relief=SUNKEN, anchor="w")
......
def runCalcButton(self):
R1, R2, R3, Inf, Warn, Err = IBRouteCalc.runthis()
def update_bar(self):
sleep(1)
if MQCounter > 0 or IBProgCount > 0:
self.statusvar.set("IB Progress: " + str(IBProgCount) + "Abfragen " + str(MQCounter))
self.sbar.update()
if __name__ == "__main__":
root = Tk()
root.geometry("400x600")
app = Window(root)
app.update_bar()
root.mainloop()
______________________________
Kalkulationsmodul:
Code: Alles auswählen
IBRouteCalc.py:
MQCounter = 0
IBProgCounter = 0
class IBRouteCalc:
def runthis(IB_File):
Ausführung meherer def's (einer dieser def's steigert die beiden Counter)
Ich dachte, ich könnte nun während der Ausführung der Klasse aus der IBRouteCalc.py auf die beiden Counter zugreifen und sobald die größer 0 sind sekündlich einen Fortschritt in der Statusbar aktulaisieren könnte. Dem ist aber nicht so.
Ich bekomme hin, dass sich der Status zumindest partiell so anzeigen lässt:
Code: Alles auswählen
def runCalcButton(self):
self.statusvar.set("In Progress")
self.sbar.update()
R1, R2, R3, Inf, Warn, Err = IBRouteCalc.runthis()
self.statusvar.set("Ready")
self.sbar.update()
Allerdings vermute ich, dass ich aktiv die werte aus der Kalkulationsklasse an die GUI senden muss, während der Ausführung der externen Klasse sind anscheinend die Updates bzw die Ausführung der GUI pausiert.
Wie stelle ich es an, dass ich aus einer Klasse während der Laufzeit Fortschrittsinformationen in der GUI erhalte?
Danke für Anregungen
Viele Grüße
Kitsab
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Sonntag 2. Mai 2021, 20:26
von Sirius3
Variablennamen und Funktionen schreibt man komplett klein.
Statt Strings per + zusammenzustückeln, benutzt man Formatstrings.
GUIs und Berechnungen im Hintergrund sind extrem komplexe Dinge. Sowas wie sleep darf in einer GUI nicht vorkommen, und längerlaufende Prozesse müssen in einem seperaten Thread laufen. Aus diesem Thread darf man aber die GUI nicht ändern, so dass man eine Queue für die Übertragung der Information braucht, die man regelmäßig in der GUI-Klasse abfragen muss.
Dazu gibt es hier im Forum etliche Beiträge. Einfach mal suchen.
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Montag 3. Mai 2021, 17:49
von kitsab
Hallo,
ja ich weiß ich bin Anfänger und habe mir Python anhand von Online Guides selber beigebracht. Mein Programmierstiel ist alles andere als sauber und ich danke für die Hinweise wie man das sauberer umsetzen könnte.
Was die Suche angeht bin/war ich mir nicht sicher welche Suchbegriffe ich verwenden sollte um das gewünschte Ergebnis zu erhalten.
Ich hab schon in google nach "Do something during Tkinter mainloop" gesucht aber irgendwie war ich damit auf dem Holzweg.
Durch weitere Recherche habe ich zumindest einen Teilerfolg Errungen indem ich meine Funktion so umgeschrieben habe:
Code: Alles auswählen
def update_bar(self):
self.counter += 1
self.statusvar.set(str(self.counter))
self.after(1000, func=lambda: self.update_bar())
Damit zählt die Statusbar dann zumindest schonmal im Sekundentakt hoch und da ich im def __init__ die Funktion aufrufe.
Nun muss ich nur noch die Updates aus meinem anderen Modul hinbekommen und die Dauerupdates mit einem True/False Schalter versehen, damit die Updates nur zum gewünschten Zeitpunkt laufen.
*Ein Tip was ich als Suchbegriff für die oben gestellt Frage eingeben könnte wäre nett.
Mit freundlichen Grüßen
Kitsab
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Dienstag 4. Mai 2021, 02:34
von __blackjack__
@kitsab: Noch ein paar Rückmeldungen zu den gezeigten Quelltextausschnitten:
Warum gibt es `init_main_window()`? Die `Window`-Klasse hat doch eine `__init__()`-Methode. Da gehört die Initialisierung rein.
In der Methode wird das Objekt selbst an die erzeugten Widget-Objekte als `master` übergeben, `Window` erbt aber gar nicht von einer Klasse die das ermöglichen würde *und* bekommt ein `Tk`-Objekt beim erstellen übergeben. Das ist extrem schräg und dürfte so überhaupt gar nicht funktionieren.
Beide gezeigten ``lambda``-Ausdrücke sind unnötig. Es wird ja nur eine Methode mit den gleichen Argumenten aufgerufen, welche die anonyme Funktion übergeben bekommt (in beiden Fällen gar keine). Da kann und sollte man einfach direkt die Methode übergeben, weil diese Indirektion keinen Sinn machen.
Das es im GUI-Modul offenbar globale Namen `MQCounter` und `IBProgCount` gibt, die es ebenfalls noch mal global im Kalkulationsmodul gibt, sieht verdächtig falsch aus. Also mal davon abgesehen, dass es überhaupt solche globalen Variablen gibt.
`root` und `app` sind auch globale Variablen in dem Modul. Das Hauptrprogramm gehört in eine Funktion.
Die `update()`-Methode sollte man nicht selbst aufrufen. Die ist nicht ganz ungefährlich und in der Regel ein Zeichen, dass man gegen das Rahmenwerk programmiert und nicht mit dem Rahmenwerk.
Das Kalkulationsmodul und `IBRouteCalc` sind falsch. Verwende keine globalen Variablen und keine Klassen die überhaupt gar keine Klassen sind. Klassen sind nicht einfach ein Namensraum für Funktionen. Das ist die Aufgabe von Modulen.
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Donnerstag 19. Mai 2022, 22:31
von kitsab
Hallo,
sorry, es hat ewig gedauert, dass ich wieder zum Programmieren gekommen bin, ich habe diese Woche am Programm weiter gemacht, mit Klassen habe ich noch zu Kämpfen. Aber danke schon mal für die Tips.
Ich habe meine erstes Programm Modul Classe IBRoute nun richtig als Klasse programmiert, jetzt nehme ich mir nochmals das Window vor.
Viele Grüße
Kitsab
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Donnerstag 19. Mai 2022, 23:31
von kitsab
Hallo,
ich hab nun doch noch weiter gemacht, und die Klasse angepasst. Ich habe mir Python komplett selbst beigebracht mit Online Tutorials, daher das "Unwissen" bei vielen Dingen. Ich suche mir einfach oft Beispiele und versuche das dann nachzubauen.
Nun sieht es so aus (Ausschnitte):
Gui File:
Code: Alles auswählen
import tkinter
import requests
from tkinter import *
from tkinter import messagebox as tkMessageBox
from IBroute import IBRouteCalc
from tkinter import filedialog as fd
class mainwindow(Tk):
def __init__(self, title, geometry):
super().__init__()
self.UpdateRate = 1000
self.title(title)
self.geometry(geometry)
self.UpdateRate = 1000
self.IBrunSession = IBRouteCalc()
self.counter = 0
self.statusvar = StringVar()
self.statusvar.set("Bereit")
self.update_bar()
.... Buttons, Textefelder, Labels, Menu etc (funktionert auch wie gewünscht)
def update_bar(self):
if self.IBrunSession.IBProgCount > 0:
self.statusvar.set("IB Progress: " + str(self.IBrunSession.IBProgCount))
self.after(1000, func=lambda: self.update_bar()) # wenn ich hier kein lambda verwende bricht dasScript mit dem Fehler
exzessive Wiedeholung ab
def runcalcbutton(self):
Frame.clipboard_clear(self)
ReturnArr = IBRouteCalc.runthis(self.IBrunSession) # <---- exzessiver Prozess aus einer Klasse in einem anderen Phyton Script
..... anderes Zeug
if __name__ == "__main__":
mainwindow = mainwindow("IBRouteGUI", "400x400")
mainwindow.mainloop()
IBRouteCalc in seperatem File (Klasse)
Code: Alles auswählen
import ... Zeug
class IBRouteCalc:
def __init__(self)
self.IBProgCount = 0
.... viele andere Variablen aus der Klasse
def runthis(self):
self.ErrorHandling()
self.loadconfig()
if self.SysName != "None" and self.SysPSI == "None":
self.Config.update({"KompColumn": 0})
self.Config.update({"KompCSV": self.Config["SysCSVSourceFile"]})
if self.IBFile != "None":
self.Config.update({"IBSourceFile": str.replace(self.IBFile, "/", "\\")})
print("Using manuallyT selected datasource: " + str(self.IBFile))
if self.SingleSystem == True:
self.debug.update({"SingleSystem": True})
self.DebugSingleSystem = {self.SysNo: [self.SysName, self.SysZip, 'dummy', 'dummy', 'dummy', self.SysCity, self.SysPSI]}
# self.DebugSingleSystem = {"PC3434XR03": ["Pristina", "89518", 'dummy', 'dummy', 'dummy', "Heidenheim", "XMM567"]}
self.Debugloop()
self.Load_IBList()
self.Debugloop()
self.Load_Kompetenz()
self.Load_SysFile()
self.Load_IBFile()
self.Load_TechFile()
self.Create_KompDic()
self.OutputSystemsFiles()
self.Prepare_Compdic()
self.ReadTechDrive()
self.IB_Tech_calc()
self.Output_override()
self.ReadTechDrive()
self.Output_result()
self.calc_statistic()
self.DebugLoop2()
self.SysPSI = "None"
self.SysCity = "None"
self.SysName = "None"
self.SingleSystem = False
self.SysNo = "None"
self.SysZip = "None"
return self.Results()
Beide Python Scripts funktionieren an sich gut.
Nur die Statusanzeige der Variable IBProgCount funktioniert nicht wie ich es gerne hätte.
Diese zeigt zu Anfang, wenn ich das Programm lade "Bereit" in der Statusbar an.
und wenn der Prozess IBRouteCalc.runthis(self.IBrunSession) ausführe ändert sich zur Laufzeit nichts, wenn der Prozess beendet ist, springt der Zähler in der Statusbar umgehend auf 1865.
Der Prozess kann bis zu 15-20 Minuten dauern, weil viele Daten über Internet Abfragen eingeholt werden.
Mein Wunsche wäre die Statusbar zur Laufzeit des IBRouteCalc Prozesses zu aktulaisieren.
Muss ich hier Funktionen wie Subprocess oder Threading in betracht ziehen. Es sieht mir so aus, als würde Python hier eine Stapelabarbeitung durchführen und die Aktualisierung erst nach Ende des Prozesses anzeigen.
Führe ich den Prozess IBRouteCalc in der eigenen Klasse als Main Window aus (if __name__ == "__main__":) dann erscheint die Ausgabe auf der Script-Befehlszeile und der Zähler wird laufend aktualisiert.
Danke und viele Grüße
Kitsab
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Freitag 20. Mai 2022, 06:13
von sparrow
Unabhängig von deinem akuten Problem Hinweise zu deinem Quelltext, warum ich gerade nicht tiefer in dein Programm einsteige:
Verwene keine * Importe.
Damit holt man sich eine Vielzahl von Namen in den Namensraum, die unter Umständen unbemerkt kollidieren und dadurch unerwartetes Verhalten hervorrufen. Zudem ist es für den Leser (und das bist auch du) nicht mehr möglich nachvollziehen, woher ein Name überhaupt kommt.
Gerade bei tkinter reden wir hier von hunderten Namen.
Verwende also statt "from tkinter import *" das übliche "import tkinter as tk" und greife per "tk.name" auf die entsprechenden Namen zu.
Namen schreibt man in Python klein_mit_unterstrich. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und die Namen von Klassen (PascalCase). Namen sollten sprechend sein, die von Funktionen die Tätigkeit beschreiben, die sie tun.
Dass du fast alle Namen in PascalCase schreibst, macht das Lesen für mich so unübersichtlich, dass ich nicht einsteige.
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Freitag 20. Mai 2022, 06:50
von Sirius3
Wie schon geschrieben, muß die Hauptschleife der GUI ständig laufen. Langlaufende Funktionsaufrufe sind nicht erlaubt, die müssen in einen Hintergrundprozess verschoben werden. Die falsch geschriebenen Variablen- und Methodennamen machen den Code schwierig zu lesen, auch 17 Funktionsaufrufe hintereinander sind nicht erfassbar für das menschliche Hirn. Benutze keine kryptischen Abkürzungen. Was ist ein Sys-No? oder ein IB? ein KombDic? Und warum ist das an anderer Stelle ein Compdic? Der Leser sollte nicht rätseln müssen.
Wenn man einen einzelnen Wert eines Wörterbuchs setzen will, benutzt man nicht update mit einem Wörterbuch mit nur einem einzigen Eintrag, sondern setzt einfach der Wert per Indexzugriff: `config["KompColumn"] = 0`
Wenn ein lambda-Ausdruck eh nur eine Funktion aufruft, dann übergibt man die Funktion direkt.
Man referenziert keine Methode über die Klasse: `self.IBrunSession.runthis()`
Genauso ist das auch bei str.replace.
Um Dir helfen zu können, brauchen wir einen für uns ausführbaren Code, der nur das Wesentliche enthält.
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Freitag 20. Mai 2022, 10:04
von __blackjack__
Ergänzend: das sind ja auch keine 17 Funktionsaufrufe sondern Methoden von denen keine Argumente entgegen nimmt und nur eine etwas zurück gibt. Zusammen mit dem Kommentar „... viele andere Variablen aus der Klasse“ sieht das so aus als wenn hier ”Funktionen” die auf einer grossen Menge globaler Variablen operieren, einfach in eine Gottklasse verschoben wurden. Das sollte man mit echten Funktionen und sinnvollen Klassen lösen.
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Sonntag 22. Mai 2022, 19:41
von kitsab
Hallo,
danke für die bisherigen Ratschläge, ich habe das nun versucht nach euren Hinweisen umzusetzen.
GUI Klasse:
Code: Alles auswählen
from tkinter import Tk, Menu, StringVar, Button, Label, SUNKEN, BOTTOM, X, messagebox
from test import counter
class mainwindow(Tk):
def __init__(self, title, geometry):
super().__init__()
self.counterinstance = counter()
self.title(title)
self.geometry(geometry)
self.updaterate = 1000
self.counter = 0
self.statusvar = StringVar()
self.statusvar.set("Bereit")
self.update_bar()
menu = Menu(self)
file = Menu(menu)
file.add_command(label="Exit", command=self.client_exit)
menu.add_cascade(label="File", menu=file)
self.statusvar = StringVar()
self.statusvar.set("Bereit")
self.sbar = Label(self, textvariable=self.statusvar, relief=SUNKEN, anchor="w")
self.sbar.pack(side=BOTTOM, fill=X)
quitbutton = Button(self, text="Quit", command=self.client_exit)
quitbutton.place(x=50, y=20)
singlesystembutton = Button(self, text="Single System", command=lambda: self.singlesystembutton())
singlesystembutton.place(x=10, y=100)
self.update_bar()
def update_bar(self):
print("test1")
if self.counterinstance.count > 0:
self.statusvar.set("IB Progress: " + str(self.ibrunsession.IBProgCount))
self.after(1000, func=lambda: self.update_bar())
def client_exit(self):
self.quit()
def singlesystembutton(self):
self.counterinstance.process()
print("Counter aktviert")
def update_bar(self):
if self.counterinstance.count > 0:
self.statusvar.set("Fortschritt: " + str(self.counterinstance.count))
self.after(1000, func=lambda: self.update_bar())
if __name__ == "__main__":
mainwindow = mainwindow("StatusBarTest", "400x400")
mainwindow.mainloop()
Klasse mit Rechenprozess:
Code: Alles auswählen
from time import sleep
class counter:
[code]
def __init__(self):
self.count = 0
def process(self):
for i in range(1,20):
self.count += 1
print(self.count)
sleep(1)
if __name__ == "__main__":
x = counter()
x.process()
Ich hoffe, dass das nun nachvollziehbar ist. Die Statusbar zeigt erst nach Ablauf der Zeit den letzten Counterstand an, mein Wunsch wäre die Aktualisierung direkt zu sehen.
Danke für eure Tipps
Viele Grüße
Kitsab
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Sonntag 22. Mai 2022, 19:52
von Sirius3
Klassen schreibt man mit großem Anfangsbuchstaben, daran erkennt man gleich was eine Klasse ist und das verhindert, dass Du die Klasse mainwindow mit deren Instanz überdeckst.
`update_bar` hast Du doppelt definiert, `client_exit` ist überflüssig, da Du gleich `quit` verwenden könntest. Alle Deine lambda-Ausdrücke sind unnötig, weil Du dort auch gleich die Funktionen, angeben könntest.
Du brauchst einen Thread, der im Hintergrund läuft.
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Sonntag 22. Mai 2022, 21:58
von kitsab
Hallo,
leider weiß ich nicht genau wie ich den Tip umsetzen soll, ich habe es nun mal mit Threading versucht, aber das resultat ist das selbe:
Gui Klasse:
Code: Alles auswählen
from tkinter import Tk, Menu, StringVar, Button, Label, SUNKEN, BOTTOM, X, messagebox
from test import Counter
import threading
# def counter_process():
# x = counter()
# threading.Thread(x.process())
class Mainwindow(Tk):
def __init__(self, title, geometry):
super().__init__()
self.counterinstance = Counter()
self.counterthread = None
self.title(title)
self.geometry(geometry)
self.updaterate = 1000
self.counter = 0
self.statusvar = StringVar()
self.statusvar.set("Bereit")
menu = Menu(self)
file = Menu(menu)
file.add_command(label="Exit", command=self.client_exit)
menu.add_cascade(label="File", menu=file)
self.statusvar = StringVar()
self.statusvar.set("Bereit")
self.sbar = Label(self, textvariable=self.statusvar, relief=SUNKEN, anchor="w")
self.sbar.pack(side=BOTTOM, fill=X)
quitbutton = Button(self, text="Quit", command=self.client_exit)
quitbutton.place(x=50, y=20)
# singlesystembutton = Button(self, text="Single System", command=lambda: self.singlesystembutton())
singlesystembutton = Button(self, text="Single System", command=self.singlesystembutton)
singlesystembutton.place(x=10, y=100)
self.update_bar()
def update_bar(self):
if self.counterinstance.count > 0:
self.statusvar.set("IB Progress: " + str(self.counterinstance.count))
self.after(1000, func=lambda: self.update_bar()) #hier benötige ich das Lamba, ohne das exzessive Wiederholung und Programm steigt aus
def client_exit(self):
quit()
def singlesystembutton(self):
self.counterthread = threading.Thread(self.counterinstance.process())
self.counterthread.start()
self.counterthread.join()
if __name__ == "__main__":
def window():
window = Mainwindow("StatusBarTest", "400x400")
window.mainloop()
t1 = threading.Thread(target=window)
t1.start()
t1.join()
Prozess Klasse:
Code: Alles auswählen
from time import sleep
class Counter:
def __init__(self):
self.count = 0
def process(self):
for i in range(1,10):
self.count += 1
print(self.count)
sleep(1)
if __name__ == "__main__":
x = counter()
x.process()
Danke für eure Hilfe.
Viele Grüße
Kitsab
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Sonntag 22. Mai 2022, 22:35
von __deets__
Du brauchst da immer noch kein lambda. Ein simples self.update_bar reicht. OHNE Klammern!
Und du sollst nicht die GUI in einen Thread packen. Das geht nicht gut, je nach OS. Sondern einen extra Thread, der die Arbeit macht. Und via einer queue zb den Fortschritt an das update_bar vermeldet.
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Montag 23. Mai 2022, 07:17
von Sirius3
Man definiert keine Funktionen innerhalb eines if. Der einzige Befehl innerhalb des if __name__...-Blocks ist `main()`.
Die GUI muss im Hauptthread laufen, dafür einen extra Thread zu starten, und den Hauptthread lahmzulegen ist eh Unsinn.
In `singlesystembutton`: target bei Thread muß eine Funktion sein, und nicht der Rückgabewert der Funktion, die man starten möchte, so blockiert ja wieder die Ereignisschleife bis die Berechnung zu Ende ist. Und auch das Warten auf das Ende des Threads ist kontraproduktiv, wenn man doch etwas parallel ausführen will.
Re: Statusbar Update während eine externe Klasse ausgeführt wird.
Verfasst: Dienstag 24. Mai 2022, 21:35
von kitsab
Hallo,
endlich geschafft, danke für die Hilfe.
def singlesystembutton(self):
self.counterinstance.count = 0
t1 = threading.Thread(target=self.counterinstance.process) #hier lag der Fehler ich hatte Urspürnglich eine Klammer hinter .process())
t1.start()
den Prozess für die GUI habe ich korrigiert, der einzige Thread ist die Funktion.
Viele Grüße
Kitsab