Using ttk.Progressbar in determinate mode - python

I'm trying to get a progress bar to appear on a Toplevel widget, then incrementally increase every few seconds until complete.
When I click "Start", there is delay of a few seconds before the progressbar widget appears. When it does appear, the progress bar does not increment at all.
Here is what I've tried so far:
class MainUI:
def __init__(self, parent):
self.parent = parent
self.counter = IntVar()
self.main_container = Frame(self.parent)
self.main_container.pack()
self.btn_start = Button(self.main_container, command=self.btn_start_click)
self.btn_start.configure(
text="Start", background="Grey",
padx=50
)
self.btn_start.pack(side=LEFT)
def progress_bar(self):
self.pbar_top = Toplevel(self.main_container)
self.download_label = Label(
self.pbar_top,
text="Download Bar"
)
self.download_label.pack(side=TOP)
self.download_bar = ttk.Progressbar(
self.pbar_top, orient="horizontal",
length=400, mode="determinate",
variable=self.counter, maximum=5
)
self.download_bar.pack(side=TOP)
def btn_start_click(self):
self.progress_bar()
for i in range(4):
self.counter = i
time.sleep(1)
root = Tk()
root.title("Progress Bar Test")
main_ui = MainUI(root)
root.mainloop()
I found that commenting out the for loop inside btn_start_click causes the progress bar to appear immediately after clicking "Start". However, as before the actual bar does not increment.
Could someone please point out what I'm doing wrong?

The problem is that you are calling time.sleep(1) in the same thread than your Tkinter code. It makes your GUI unresponsive until the task (in this case, the call to btn_start_click) finishes. To solve this, you can start a new thread which executes that function, and update the progress bar in the GUI thread by using a synchronized object like Queue. This is a working example I wrote for a similar question.
Besides, you should call self.counter.set(i) instead of self.counter = i to update the value of the IntVar. Another solution more explicit is self.download_bar.step() with the appropiate increment.

from tkinter import *
from tkinter import ttk
class MainUI:
def __init__(self, parent):
self.parent = parent
self.counter = IntVar()
self.main_container = Frame(self.parent)
self.main_container.pack()
self.btn_start = Button(self.main_container, command=self.startThread)
self.btn_start.configure(
text="Start", background="Grey",
padx=50
)
self.btn_start.pack(side=LEFT)
def progress_bar(self):
self.pbar_top = Toplevel(self.main_container)
self.download_label = Label(
self.pbar_top,
text="Download Bar"
)
self.download_label.pack(side=TOP)
self.download_bar = ttk.Progressbar(
self.pbar_top, orient="horizontal",
length=400, mode="determinate",
variable=self.counter, maximum=5
)
self.download_bar.pack(side=TOP)
def startThread(self):
import threading
def btn_start_click():
self.progress_bar()
for i in range(6):
self.counter.set(i)
import time
time.sleep(1)
t = threading.Thread(None, btn_start_click, ())
t.start()
root = Tk()
root.title("Progress Bar Test")
main_ui = MainUI(root)
root.mainloop()

Related

Keep subprocess as topmost on Linux OS in python3

I have a gui in python3 that I want to call another python script using subprocess (or something better?). I have the main gui full screen, which is what I want. The problem is, when I launch the subprocess, it initially becomes the topmost window on LXDE, so far so good. You can click on the main gui in the background which brings the main gui to topmost. This is what is expected from the window manager, but this covers the subprocess that I currently have blocking the main gui. I need to prevent the main gui from accepting focus while the subprocess is running or keeping the subprocess as topmost window.
maingui.py
#!/usr/bin/env python3
import tkinter as tk
import subprocess
def on_escape(event=None):
print("escaped")
root.destroy()
def do_something():
child = subprocess.run(["python3", "childgui.py"], shell=False)
######################################################################
busy=False
root = tk.Tk()
root.title("My GUI")
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
#don't run this as the subprocess blocks #root.attributes("-fullscreen", True) # run fullscreen
root.focus_set()
root.bind("<Escape>", on_escape)
#doesn't work#root.protocol("WM_TAKE_FOCUS", on_focus)
canvas = tk.Canvas(root)
canvas.grid(row=0)
cbutton = tk.Button(root, text = "Run Child", width=50, height=50, bg = "green", compound = "top", command = do_something)
lab = tk.Label(canvas, text = 'Output Here')
lab.grid(row=0, column=1)
cbutton.grid(row=1, column=0)
# --- start ---
root.mainloop()
childgui.py
from tkinter import *
class app(Frame):
def __init__(self, master):
Frame.__init__(self, master=None)
self.master.title("Child Process")
self.master.geometry("500x200")
Button(self.master, text="Grandchild", command=self.dialog).pack()
self.data = StringVar()
self.data.set("Here is the data")
Label(self.master, textvariable=self.data).pack()
def dialog(self):
d = MyDialog(self.master, self.data, "Grandchild", "Enter Data")
self.master.wait_window(d.top)
class MyDialog:
def __init__(self, parent, data, title, labeltext = '' ):
self.data = data
self.rt = parent
self.top = Toplevel(parent)
self.top.transient(parent)
self.top.grab_set()
self.top.geometry("300x100")
if len(title) > 0: self.top.title(title)
if len(labeltext) == 0: labeltext = 'Data'
Label(self.top, text=labeltext).pack()
self.top.bind("<Return>", self.ok)
self.e = Entry(self.top, text=data.get())
self.e.bind("<Return>", self.ok)
self.e.bind("<Escape>", self.cancel)
self.e.pack(padx=15)
self.e.focus_set()
b = Button(self.top, text="OK", command=self.ok)
b.pack(pady=5)
def ok(self, event=None):
print ("The data:", self.e.get())
self.data.set(self.e.get())
self.top.destroy()
def cancel(self, event=None):
self.top.destroy()
def main():
root = Tk()
a = app(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit: I should have mentioned the child gui is an app that is not mine. I only created this one for the example code to show the issue. While self.top.attributes may work, the actual child app is pretty large and I don't want to change it if I can avoid it. I don't have problems getting the focus, which is actually the problem. The main gui gets the focus back causing the subprocess to go behind. When the main gui is set to fullscreen (delete this to see #don't run this as the subprocess blocks #) the main gui is now stuck waiting for the child to close which you can't get to with the mouse

Make a reminder which will remind every X minute using Tkinter

I am totally new in python GUI and Tkinter. Now i want an entry field where i can change the value or time of self.hide when i will execute this code. that means self.hide value will change from Entry field. In this code this value is statically set to 1 minute. need help from experts.
import Tkinter as Tk
import time
import tkMessageBox
class Window:
def __init__(self):
self.root = None
self.hide = 1 #minutes
self.show = 3 #seconds
def close(self):
self.root.destroy()
return
def new(self):
self.root = Tk.Tk()
self.root.overrideredirect(True)
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
self.root.configure(bg='black')
Tk.Label(self.root, text='Hello', fg='white', bg='black', font=('Helvetica', 30)).place(anchor='center', relx=0.5, rely=0.5)
#tkMessageBox.showinfo("Notification", "Your time is up. Time to do next job. . .")
Tk.Button(text = 'Close', command = self.close).pack()
self.root.after(self.show*1000, self.pp)
def pp(self):
if self.root:
self.root.destroy()
time.sleep(self.hide*60)
self.new()
self.root.mainloop()
return
Window().pp()
Try This. It may help you.
from Tkinter import *
import time
root = Tk()
def close():
root.destroy()
def show():
root.deiconify()
button.config(text = 'Close', command = close)
root.after(1000, hide)
def hide():
root.withdraw()
time_to_sleep = set_time_to_sleep.get()
time_to_sleep = float(time_to_sleep)
#print time_to_sleep
time.sleep(time_to_sleep)
show()
set_time_to_sleep = Entry(root)
set_time_to_sleep.pack(side=LEFT)
button = Button(text = 'Set Time', command = hide)
button.pack()
root.mainloop()
To summarise:
Instead of using the sleep function, use the after function. This will not freeze the GUI.
Set the "wait" time of the after function self.Entry.get(). This will collect the info you have put into the Entry.
For more info, look at these links. People smarter than myself give a very clear explication on how to use the functions.
Tkinter, executing functions over time
tkinter: how to use after method

Trying to launch single threaded window, but two are launching

I'm trying to launch a threaded window because I have background processes that are running, but two identical windows are launching under the same thread and I'm not sure why. I'm guessing this code can be optimized! thx for any suggestions.
6/1: I made the modification as suggested to not run two windows in the main loop, and that works. The 2nd piece, is that once the button is clicked, the window is destroyed "self.root.destroy()", but if I try to open another window it will not open. I do a check before trying to launch the new window and there is only the main thread running, so the first thread is gone. Not sure what's going on and I'm not able to debug with threads running.
from Tkinter import *
import ttk
import threading
import thread
from pdctest.test_lib.hid_util import *
class GUI():
def __init__(self, root):
self.root = root # root is a passed Tk object
self.root.title("GUI--->user instruction window")
self.frame = Frame(self.root)
self.frame.pack()
self.label0 = Label(self.frame, text=" ")
self.label0.configure(width=50, height=1)
self.label0.pack()
self.label = Label(self.frame, text="GUI--->execute a SKP on HS within 3s...")
self.label.configure(width=50, height=1)
self.label.pack()
self.button = Button(self.root, text=" GUI--->then click this button ", command=self.buttonWasPressed, fg = 'black', bg = 'dark orange')
self.button.configure(width=40, height=5)
self.button.pack(pady=25)
def removethis(self):
print("GUI--->in removethis")
self.root.destroy()
def buttonWasPressed(self):
print( "GUI--->inside buttonWasPressed: the button was pressed! " + '\r')
self.removethis()
print("GUI--->leaving buttonWasPressed")
def guiSKP(self):
#root = Tk()
#window = GUI(root)
#root.mainloop()
# modified 6/1, fixes multi-window issue
self.root.mainloop()
class GUI_SKP_Thread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
root = Tk()
window = GUI(root)
window.guiSKP()
def launch_SKP_thread():
print("beginning launch_SKP_thread" + '\r')
thread_SKP = GUI_SKP_Thread("GUI_SKP_Thread")
thread_SKP.start()
print("exiting launch_SKP_thread" + '\r')
def whatsRunning():
t = threading.enumerate()
print("--------------->whatsRunning")
print t
if __name__ == '__main__':
launch_SKP_thread()
# trying to launch 2nd window
whatsRunning()
time.sleep(4)
whatsRunning()
launch_SKP_thread()
I't not really sure what you're trying to do with the program. But I'm sure about one thing, that is, you are trying to run two frames in the same mainloop. You defined window twice in different locations, meaning that there are two frames/windows. Once in guiSKP in GUI, and once in run in GUI_SKP_Thread.
root = Tk()
window = GUI(root)
So, you should change the text in guiSKP from:
root = Tk()
window = GUI(root)
root.mainloop()
To simply:
self.root.mainloop()
I added the self to make sure it runs its own root.mainloop()

Tkinter Large Text Dialogue (save string on button click)

I am trying to make a large text-entry popup as part of a gui. The idea is to get paragraph-long user-input. The problem is that the method get_big_text() returns before the button is pushed. How can I have a separate window pop-up like this, and be able to save the user's text to a variable in my control program? Everything else in my program has been working out, until I tried to implement this. I am new to gui programming. I get the feeling that there is something fundamentally different about waiting for user input here, but I can't wrap my head around it in the functional context.
My goal is to have the line print(foo.get_big_text()) print the user's text, but of course it prints None because the get_big_text() method finishes.
I have left out the details of the rest of the gui, and wrote an __init__() that probably doesn't need to be there, but this is the basics of how my gui is coming along. The Toplevel widget is the only widget in my gui that is not somehow connected to root.
from tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def get_big_text(self, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda: big_text.get('0.0', 'end'))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
print(foo.get_big_text())
root.mainloop()
You should pass the text to one function in your class and then do whatever you want with it (like printing):
from Tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def f(self, text):
print(text)
def get_big_text(self, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda: self.f(big_text.get('0.0', 'end')))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
foo.get_big_text()
root.mainloop()
If you want to print the text after the gui finished you can do this modifications:
On Gui.f:
def f(self, text):
self.text = text
At the end of your code:
root.mainloop()
print(foo.text)
After some suggestions from #xndrme, and some hard thinking, I realized the solution is quite simple. It's just that I'm not used to programing in this functional style. Really fun to discover this, though.
I wanted the get_big_text() method to return the text so that I could pass it somewhere else and "do something" with it when the text comes. The solution was to pass an anonymous function to the method and "tell it" what should be done with it when it does come.
Note the new callback parameter in get_big_text()
from tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def get_big_text(self, callback, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda:callback(big_text.get('0.0', 'end')))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
foo.get_big_text(lambda x:print(x))
root.mainloop()
The general flow for a dialog is to create the window, then call wait_window to wait until the window has been dismissed by the user. Your function can then return whatever you want.
There's a bit of a chicken-and-egg thing going on, in that you need to get the value from the dialog before the dialog is destroyed since the text widget will be destroyed when the toplevel is destroyed. You do this by explicitly managing the destruction of the window (read: get the value before actually destroying the window).
Here's a working example, trying to preserve as much as code as possible but without using a global import:
import Tkinter as tk
class CustomDialog(object):
def __init__(self, parent, title="Enter a paragraph", default_text=""):
self.parent = parent
self.title = title
self.default = default_text
def show(self):
self.popup = tk.Toplevel(self.parent)
self.popup.title(self.title)
txtframe = tk.Frame(self.popup)
txtframe.pack()
self.big_text = tk.Text(txtframe)
self.big_text.insert('1.0',self.default)
self.big_text.pack()
btnframe = tk.Frame(self.popup)
btnframe.pack()
grab_text = tk.Button(btnframe)
grab_text.config(text="Done", command=self.done)
grab_text.pack()
# make sure our "done" method gets called even if the
# user destroys the window
self.popup.wm_protocol("WM_DELETE_WINDOW", self.done)
# wait for the window to be destroyed
root.wait_window(self.popup)
return self.data
def done(self, *args):
# get the data from the window, then destroy
# the window and return to the caller
self.data = self.big_text.get("1.0", "end-1c")
self.popup.destroy()
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
b = tk.Button(self, text="Get Input", command=self.go)
b.pack()
def go(self):
dialog = CustomDialog(self, default_text="totally foobar")
result = dialog.show()
print "result:", result
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Another option that you have is to pass a callback to your dialog, and tie that callback to the "done" button. That way, whenever the user clicks the button, you execute the callback to do whatever you want with the data before destroying the window.
That is how you implement a non-modal dialog, since you don't necessarily have to destroy the window. Font dialogs are a good example of this, where you might want to keep the dialog open for quite a while, and affect whatever is currently selected.
The effbot site has a decent writeup on dialogs. See http://effbot.org/tkinterbook/tkinter-dialog-windows.htm

Python Tkinter frame inside frame limitation or user error?

I've been building an app to track stock prices. The user should see a window with an entry widget and a button that creates a new frame with a label and a button. The label is the stock price and symbol, the button is a delete button, and should hide that frame if clicked.
I've re-written this program 4 times now, and it's been a great learning experience, but what I've learned is that I can't have the "mini-frames" being called from methods part of the main GUI class - this funks up the delete buttons, and updates the value behind frame.pack_forget() so it only deletes the last item ever.
I've moved my mini-frame widgets down into the class for the actual stock values. I've packed them (what I assume to be correct) but they don't show up. They also don't error out, which isn't very helpful. Here's my code, although I've omitted a lot of the functional parts to show what is happening with my frames. Keep in mind I need to keep it so that I can call my updater (self.update_stock_value) with a .after method against myapp.myContainer.
Is there a better way to do this?? Thanks in advance, my head hurts.
import re
import time
import urllib
from Tkinter import *
import threading
from thread import *
runningThreads = 0
# each object will be added to the gui parent frame
class MyApp(object):
def __init__(self, parent):
self.myParent = parent
self.myContainer = Canvas(parent)
self.myContainer.pack()
self.create_widgets()
# METHOD initiates basic GUI widgets
def create_widgets(self):
root.title("Stocker")
self.widgetFrame = Frame(self.myContainer)
self.widgetFrame.pack()
self.input = Entry(self.widgetFrame)
self.input.focus_set()
self.input.pack()
self.submitButton = Button(self.widgetFrame, command = self.onButtonClick)
self.submitButton.configure(text = "Add new stock")
self.submitButton.pack(fill = "x")
# METHOD called by each stock object
# returns the "symbol" in the entry widget
# clears the entry widget
def get_input_value(self):
var = self.input.get()
self.input.delete(0, END)
return var
# METHOD called when button is clicked
# starts new thread with instance of "Stock" class
def onButtonClick(self):
global runningThreads # shhhhhh im sorry just let it happen
runningThreads += 1 # count the threads open
threading.Thread(target = self.init_stock,).start() # force a tuple
if runningThreads == 1:
print runningThreads, "thread alive"
else:
print runningThreads, "threads alive"
def init_stock(self):
new = Stock()
class Stock(object):
def __init__(self):
# variable for the stock symbol
symb = self.stock_symbol()
# lets make a GUI
self.frame = Frame(myapp.myContainer)
self.frame.pack
# give the frame a label to update
self.testLabel = Label(self.frame)
self.testLabel.configure(text = self.update_stock_label(symb))
self.testLabel.pack(side = LEFT)
# create delete button to kill entire thread
self.killButton = Button(self.frame, command = self.kill_thread)
self.killButton.configure(text = "Delete")
self.killButton.pack(side = RIGHT)
# create stock label
# call updater
def kill_thread(self):
global runningThreads
runningThreads -= 1
self.stockFrame.pack_forget() # hide the frame
self.thread.exit() # kill the thread
def update_stock_label(self, symb):
self.testLabel.configure(text = str(symb) + str(get_quote(symb)))
myapp.myContainer.after(10000, self.update_stock_label(symb))
def stock_symbol(self):
symb = myapp.get_input_value()
print symb
# The most important part!
def get_quote(symbol):
try:
# go to google
base_url = "http://finance.google.com/finance?q="
# read the source code
content = urllib.urlopen(base_url + str(symbol)).read()
# set regex target
target = re.search('id="ref_\d*_l".*?>(.*?)<', content)
# if found, return.
if target:
print "found target"
quote = target.group(1)
print quote
else:
quote = "Not Found: "
return quote
# handling if no network connection
except IOError:
print "no network detected"
root = Tk()
root.geometry("280x200")
myapp = MyApp(root)
root.mainloop()
Your code won't run because of numerous errors, but this line is definitely not doing what you think it is doing:
self.frame.pack
For you to call the pack function you must include (), eg:
self.frame.pack()
You ask if your code is the best way to do this. I think you're on the right track, but I would change a few things. Here's how I would structure the code. This just creates the "miniframes", it doesn't do anything else:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.entry = tk.Entry(self)
self.submit = tk.Button(self, text="Submit", command=self.on_submit)
self.entry.pack(side="top", fill="x")
self.submit.pack(side="top")
def on_submit(self):
symbol = self.entry.get()
stock = Stock(self, symbol)
stock.pack(side="top", fill="x")
class Stock(tk.Frame):
def __init__(self, parent, symbol):
tk.Frame.__init__(self, parent)
self.symbol = tk.Label(self, text=symbol + ":")
self.value = tk.Label(self, text="123.45")
self.symbol.pack(side="left", fill="both")
self.value.pack(side="left", fill="both")
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Categories

Resources