Progressbar updatet nicht

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Borneo
User
Beiträge: 5
Registriert: Freitag 28. Juli 2023, 18:21

Hallo alle zusammen,
ich habe das Problem, dass wenn ich die Funktion "update" aufrufe meine Progressbar nicht Stück für Stück sich updatet,
sondern erst wenn die gesamte Funktion "update" abgelaufen ist. Dann sehe ich allerdings auch nur den letzten Wert
der Progressbar.
Ich danke schon einmal im Vorraus für euro Antworten.



from tkinter import *
from tkinter import ttk
import time

root = Tk()
root.geometry('600x400')

my_progress = ttk.Progressbar(root, orient=HORIZONTAL,length=200,mode='determinate')
my_progress.pack()

def update():
my_progress['value']= 0
time.sleep(0.5)
my_progress['value']= 20
time.sleep(0.5)
my_progress['value']= 40
time.sleep(0.5)
my_progress['value']= 60
time.sleep(0.5)
my_progress['value']= 80
time.sleep(0.5)
my_progress['value']= 100
time.sleep(0.5)

my_button = Button(root,text='Progress',command=update)
my_button.pack()

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

*-Importe benutzt man nicht, ein my-Präfix ist unsinnig, solange es nicht auch ein our_progress gibt. Globale Variablen darf man nicht benutzen; für GUIs bedeutet das, dass man fast zwangsläufig mit Klassendefinitionen arbeiten muß.
Bei GUIs darf es keine langlaufenden Funktionen geben, weil die GUI erst am Ende der Funktion weiterarbeitet.
Will man was wiederholt aufrufen, benutzt man after.

Code: Alles auswählen

import tkinter as tk
from tkinter import ttk
from functools import partial

def update(progress, value):
    progress['value']= value
    if value < 100:
        progress.after(500, update, progress, value + 20)

def main():
    root = tk.Tk()
    progress = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=200, mode='determinate')
    progress.pack()
    tk.Button(root,text='Progress',command=partial(update, progress, 0)).pack()
    root.mainloop()

if __name__ == "__main__":
    main()
Borneo
User
Beiträge: 5
Registriert: Freitag 28. Juli 2023, 18:21

Vielen Dank für die schnelle Antwort Sirius3.
Leider reichen meine Python Kenntnisse nicht aus, um mit deiner Antwort etwas anzufangen.
Ich habe meinen Code umgeschreiben, aber leider habe ich noch immer das gleiche Problem.

In deinem Beispielcode ist es so, dass der Fortschritt der Progressbar durch die Zeile "progress.after(500, update, progress, value + 20)" in der Funktion "update" stattfindet. Aber ich will, dass der Fortschritt der Progressbar in der Funktion "main" bestimmt wird.
Du musst dir vorstellen, dass die Zeilen "time.sleep(0.5) eigentlich eigenständige Funktionen mit "SQL" Abfragen sind.
Ich will halt visuell mitkriegen, wie weit die 5 SQL Abfragen abgearbeitet sind.

Ich habe mal deinen Code auf mein Problem umgeschrieben, aber leider funktioniert das nicht.
Wäre nett, wenn jamand mir hier helfen könnte:

import tkinter as tk
from tkinter import ttk
from functools import partial
import time

def update(progress, value):
progress['value']= value
#if value < 100:
#progress.after(500, update, progress, value + 20)

def main():
root = tk.Tk()
progress = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=200, mode='determinate')
progress.pack()
#tk.Button(root,text='Progress',command=partial(update, progress, 20)).pack()
partial(update, progress, 20)()
time.sleep(0.5)
partial(update, progress, 40)()
time.sleep(0.5)
partial(update, progress, 60)()
time.sleep(0.5)
partial(update, progress, 80)()
time.sleep(0.5)
partial(update, progress,100)()
time.sleep(0.5)
root.mainloop()

if __name__ == "__main__":
main()
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

@Borneo: Was du denkst, was du willst, funktioniert halt nicht. GUI-Frameworks haben eine Loop, mit dem sie sich selbst zeichnen. Bei Tkinter heißt der sogar mainloop.
Der mainloop darf nicht unterbrochen noch durch im selben Thread laufende Dinge verzögert werden.

Du tust da schon Dinge, bevor du den mainloop startest. Das kann nicht funktionieren, weil eben dieser Loop die Darstellung neu zeichnen würde.
Den Loop vorher zu starten und in der selben Funktion danach Dinge tun, funktioniert auch nicht, denn der mainloop wird nicht mehr verlassen.

Die Lösung hat Sirius dir bereits genannt. Du musst dich mit "after" und mindestens mit Funktionen beschäftigen.
GUI-Origrammierung hat eine gewisse Komplexität.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Deine Python-Kenntnisse noch nicht reichen, dann ist vielleicht die Aufgabe, die Du Dir vorgenommen hast, noch zu groß. GUI-Programmierung ist ein sehr komplexes Thema, weil man von einem linearen Programmablauf zu einem umdenken muß, wo man auf Ereignisse reagieren muß. Dazu muß man sich noch den Zustand des Programms zwischen den Ereignisbehandlungen merken, was man mit Klassen machen muß.
Bedeutet, man muß Funktionen gut beherrschen, Objektorientierte Programmierung gut beherrschen und dann kann man sich dem Thema GUI zuwenden, um ein paar einfache Projekte damit umzusetzen.

Eine große Stufe schwieriger wird es dann, wenn noch Langlaufende Berechnungen dazu kommen, denn dann muß man sich mit dem Abarbeiten von Aufgaben im Hintergrund beschäftigen, der Kommunikation mit dem Vordergrund-GUI-Prozess usw.

Ein Minimal-Beispiel könnte so aussehen:

Code: Alles auswählen

import random
import tkinter as tk
from tkinter import ttk
from threading import Thread
from queue import Queue, Empty
import time

def do_processing(queue):
    progress = 0
    while progress < 100:
        time.sleep(random.random())
        progress += random.randint(2, 10)
        queue.put(progress)

class Window(tk.Tk):
    def __init__(self):
        super().__init__()
        self.progress = ttk.Progressbar(self, orient=tk.HORIZONTAL, length=200, mode='determinate')
        self.progress.pack()
        self.start_button = tk.Button(self, text='Progress', command=self.start_process)
        self.start_button.pack()
        self.thread = None
        self.queue = None
    
    def start_process(self):
        if self.thread is None:
            self.start_button['state'] = tk.DISABLED
            self.queue = Queue()
            self.thread = Thread(target=do_processing, args=(self.queue,))
            self.update()
            self.thread.start()

    def stop_process(self):
        self.thread.join()
        self.start_button['state'] = tk.ACTIVE
        self.thread = None
        self.queue = None

    def update(self):
        try:
            value = self.queue.get_nowait()
            self.progress['value']= value
            if value >= 100:
                self.stop_process()
                return
        except Empty:
            pass
        self.after(100, self.update)

def main():
    root = Window()
    root.mainloop()

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

@Borneo: ``partial(update, progress, 20)()`` ist eine umständliche Art ``update(progress, 20)`` zu schreiben. Das macht keinen Sinn das mit `partial()` unnötig komplizierter zu machen. Und da die Funktion letztlich nur aus einer Zeile besteht, könnte man auch gleich ``progress['value'] = 20`` schreiben.

Muss es denn unbedingt eine GUI-Anwendung sein? Es gibt ja auch diverse Möglichkeiten Fortschrittsbalken in der Konsole anzuzeigen. Hier ein Beispiel mit `rich`:

Code: Alles auswählen

#!/usr/bin/env python3
from time import sleep

from rich.progress import track


def main():
    progress = track(range(5))
    next(progress)
    sleep(0.5)
    next(progress)
    sleep(0.5)
    next(progress)
    sleep(0.5)
    next(progress)
    sleep(0.5)
    next(progress)
    sleep(0.5)
    next(progress, None)


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Borneo
User
Beiträge: 5
Registriert: Freitag 28. Juli 2023, 18:21

Einen großen Dank an Sirius3, sparrow und __blackjack__.
Eure Antworten haben mir gezeigt, dass so eine Prozessbar innerhalb eines GUI-Frameworks leider nicht so einfach umzusetzen ist.
Antworten