Csarg
Csarg

Reputation: 363

Updating tkinter label based on loop

Background Information

Hello. What I'm trying to do currently is create the tkinter window, run a for loop and update the label in real-time as the for loop is running through the directories (in this case, it is listing all the directories with their full paths in the C drive of my computer).

The Problem

The problem that I'm running into is that as soon as I click the start button (which begins the for loop) the GUI completely freezes (I'm aware that this is because tkinter and loops don't play well, I just wonder if there is a solution I'm not aware of), which is counter intuitive as I'd like it to display the directory that the loop is currently iterating over within my tooltip label.

What I've tried so far

from tkinter import Tk, Label, Frame, Button
import os

def start_command():
    for root_directory, sub_directories, files in os.walk("C:\\"):
        for sub_directory in sub_directories:
            full_directory = os.path.join(root_directory, sub_directory)
            tooltip.config(text=full_directory)

window = Tk()
tooltip = Label(window, text="Nothing Here Yet")
tooltip.pack()
start = Button(text="Start", command=start_command)
start.pack()

window.mainloop()

Tl;Dr I'm trying to run a for loop within tkinter and update the label every iteration. The problem is the GUI is freezing with the above code.

Any help would be greatly appreciated. Thank you for your time :)

Upvotes: 3

Views: 2435

Answers (2)

martineau
martineau

Reputation: 123473

Although it's a somewhat advanced approach, you could use a combination of a coroutine, a tkinter control variable, and the universal widget after() method to drive a loop that updates the Label widget with minimal runtime overhead. A nice thing about control variables is that any widgets referencing one of them will automatically be updated anytime its set() method is called from somewhere—so you don't have to do that manually.

#!/usr/bin/env python3
# https://stackoverflow.com/questions/52015836/updating-tkinter-label-based-on-loop

from tkinter import Tk, Label, Frame, Button, StringVar
import os

DELAY = 250  # Milliseconds between var updates.

def walk_dirs(start_dir):
    """ Iteratively walk directory tree from start_dir. """
    yield "Walking " + start_dir  # Optional.

    for root_directory, sub_directories, files in os.walk(start_dir):
        for sub_directory in sub_directories:
            full_directory = os.path.join(root_directory, sub_directory)
            yield full_directory

def update_variable(var, walker):
    try:
        directory = next(walker)
    except StopIteration:
        var.set("DONE!")
        return  # Don't call after() so repetitive updates stop.
    var.set(directory)  # Setting var automatically updates Label.
    tooltip.after(DELAY, update_variable, var, walker)  # Rinse and repeat.

def start_command(var):
    walker = walk_dirs("C:\\")  # Start coroutine.
    update_variable(var, walker)  # Start periodic updates using it.

window = Tk()
var = StringVar()
var.set("Nothing Here Yet")
tooltip = Label(window, textvariable=var)
tooltip.pack()
start = Button(text="Start", command=lambda: start_command(var))
start.pack()

window.mainloop()

Upvotes: 0

Joshua Nixon
Joshua Nixon

Reputation: 1427

import tkinter as tk
import os

class MyDirectoryLabel(tk.Label):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.dirGen = self.directoryGen()
        self.master = self._nametowidget(self.winfo_parent())

    def directoryGen(self):
        for root_directory, sub_directories, files in os.walk("C:\\"):
            for sub_directory in sub_directories:
                yield os.path.join(root_directory, sub_directory)

    def loop(self):
        try:
            full_directory = next(self.dirGen)
        except StopIteration as e:
            self.config(text = "Finished...")
            return 0

        self.config(text = full_directory)

        self.master.after(10, self.loop)        

root     = tk.Tk()
root.geometry("500x100")

tooltip = MyDirectoryLabel(root, text = "Nothing Here Yet")
start   = tk.Button(root, text = "Start", command = tooltip.loop)

tooltip.pack()
start.pack()

root.mainloop()

Uses the after method in combination with a generator which creates the directory for the label.

Upvotes: 2

Related Questions