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()
CodePudding user response:
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()