Tkinter indeterminate progress bar not running as expected

Question:

This is a simple gui.
I am taking url from the user in the Entry. When the button is pressed, url is saved in a file and another function is called to start another process through call (within subprocess).
while the process runs, I want to show the indeterminate progress bar (until the button is hit the bar needs to be hidden) and when the process is completed a showinfo message displays to destroy the gui.

Problem: The bar doesn’t show up until the process is finished. After the showinfo dialog is displayed, only then it starts progressing. Means, the bar starts progressing the moment it should actually get destroyed.
What is wrong with my code?

import scrapy
import tkinter as tk
from tkinter import messagebox as tkms
from tkinter import ttk
import shlex
from subprocess import call
    

def get_url():
    # get value from entry and write to a file


def scrape():
    progress_bar = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
    progress_bar.grid(row=3, column=2)
    progress_bar.start(10)

    command_line = shlex.split('scrapy runspider /media/mayank/Local/Coding/Lab/Scraping/Practices/img.py')
    call(command_line)
    
    mes = tkms.showinfo(title='progress', message='Scraping Done')
    if mes == 'ok':
        root.destroy()

root = tk.Tk()
root.title("Title")

 
entry1 = tk.Entry(root, width=90, textvariable=url)
entry1.grid(row=0, column=0, columnspan=3)

my_button = tk.Button(root, text="Process", command=lambda: [get_url(), scrape()])
my_button.grid(row=2, column=2)

root.mainloop()

—-Updated Code —

import scrapy
    import tkinter as tk
    from tkinter import messagebox as tkms
    from tkinter import ttk
    import shlex
    from subprocess import call
        
    
def get_url():
        # get value from entry and write to a file

scrapy = None


def watch():
    global scrapy
    if scrapy:
        if scrapy.poll() != None:
            # Update your progressbar to finished.
            progress_bar.stop()
            progress_bar.destroy()
            # Maybe report scrapy.returncode?
            print(f'scrapy return code =--######==== {scrapy.returncode}')
            scrapy = None
        else:
            # indicate that process is running.
            progress_bar.start(10)
            print(f'scrapy return code =--######==== {scrapy.returncode}')
            # Re-schedule `watch` to be called again after 0.1 s.
            root.after(100, watch)


def scrape():
    global scrapy
    command_line = shlex.split('scrapy runspider ./img.py')
    scrapy = Popen(command_line)

    watch()

    mes = tkms.showinfo(title='progress', message='Scraping Done')
    if mes == 'ok':
        root.destroy()


root = tk.Tk()
root.title("Title")

url = tk.StringVar(root)

entry1 = tk.Entry(root, width=90, textvariable=url)
entry1.grid(row=0, column=0, columnspan=3)

my_button = tk.Button(root, text="Process", command=lambda: [get_url(), scrape()])
my_button.grid(row=2, column=2)

progress_bar = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress_bar.grid(row=3, column=2)

root.mainloop()
Asked By: Mayank Mishra

||

Answers:

Using subprocess.call interrupts the current process until the called process is finised.
So the GUI won’t update until the call is finished.

Important takeaway: Never call subprocess.run, subprocess.call or one of the other convenience functions from the main thread of a tkinter program. Doing so will freeze the GUI. You should only create subprocess.Popen objects from the main thread.

What you should do instead is create a Popen object, while at the same time disabling the start button.

To track the progress, define a function that is periodically called with root.after(), say every 0.1 s.
In this function you could call the poll() method to check if the subprocess has finished.
Alternatively, you could set stdout=subprocess.PIPE and read the data from the subprocess from the stdout attribute of the Popen object.

The code below is a working (for me) example based on your updated question.
Note that I have replaced scrapy (which I don’t have) with a relative long-running command on my UNIX machine.
Since you are running scrapy as a subprocess, you should not need import scrapy.

import tkinter as tk
from tkinter import messagebox as tkms
from tkinter import ttk
from subprocess import Popen


proc = None


def watch():
    global proc
    if proc:
        if proc.poll() is not None:
            # Update your progressbar to finished.
            progress_bar.stop()
            progress_bar.destroy()
            # Maybe report proc.returncode?
            print(f'proc return code =--######==== {proc.returncode}')
            proc = None
            mes = tkms.showinfo(title='progress', message='Scraping Done')
            if mes == 'ok':
                root.destroy()
        else:
            # indicate that process is running.
            progress_bar.start(10)
            # print(f'proc return code =--######==== {proc.returncode}')
            # Re-schedule `watch` to be called again after 0.1 s.
            root.after(100, watch)


def scrape():
    global proc
    command_line = ['netstat']
    proc = Popen(command_line)

    watch()


root = tk.Tk()
root.title("Title")

url = tk.StringVar(root)

entry1 = tk.Entry(root, width=90, textvariable=url)
entry1.grid(row=0, column=0, columnspan=3)

my_button = tk.Button(root, text="Process", command=lambda: [get_url(), scrape()])
my_button.grid(row=2, column=2)

progress_bar = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress_bar.grid(row=3, column=2)

root.mainloop()

Answered By: Roland Smith
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.