I have a GUI which has two buttons and a progressbar stacked on a single column. Each button calls a different function which takes some time to execute. I want the progress bar to move when someone clicks any of the two buttons and keep moving (indeterminately) until the function finishes and then stop. I know I need to use multi-threading but I can't seem to get the code right!
Code
from tkinter import Tk
import time
from tkinter import *
from tkinter import Button
from tkinter import Frame
from tkinter import ttk
import threading
def sample_function():
time.sleep(2) # SAMPLE FUNCTION BEING CALLED
def prepare_clicked():
sample_function()
def social_clicked():
sample_function()
def anomaly_clicked():
sample_function()
window = Toplevel() # Tried using Tk but I am using image to design each buttons through the button config in my actual code and tk throws error
topFrame = Frame(window)
topFrame.pack()
prepare_btn = Button(topFrame, command=prepare_clicked,text='Button1')
anomaly_btn = Button(topFrame,command=anomaly_clicked,text='Button2')
social_btn = Button(topFrame, command=social_clicked,text='Button3')
processing_bar = ttk.Progressbar(topFrame, orient='horizontal', mode='indeterminate')
window.rowconfigure((0,1), weight=1) # make buttons stretch when
window.columnconfigure((0,3), weight=1) # when window is resized
prepare_btn.grid(row=0, column=1, columnspan=1, sticky='EWNS')
anomaly_btn.grid(row=1, column=1, columnspan=1, sticky='EWNS')
social_btn.grid(row=2, column=1, columnspan=1, sticky='EWNS')
processing_bar.grid(row=3, column=1, columnspan=1, sticky='EWNS')
window.mainloop()
I've added threading to your code. I assume you don't want any of the buttons to be pressable while a function is in progress. If you don't need that, just get rid of the for loops in run_function that change btn['state']. I've also fixed the row & column configuration code so that the widgets expand & contract when the user resizes the window. And I got rid of the evil "star" import.
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
def sample_function():
time.sleep(2)
def run_function(name, func):
# Disable all buttons
for btn in buttons:
btn['state'] = 'disabled'
processing_bar.start(interval=10)
print(name, 'started')
func()
processing_bar.stop()
print(name, 'stopped')
# Enable all buttons
for btn in buttons:
btn['state'] = 'normal'
def run_thread(name, func):
Thread(target=run_function, args=(name, func)).start()
def prepare_clicked():
run_thread('prepare', sample_function)
def social_clicked():
run_thread('social', sample_function)
def anomaly_clicked():
run_thread('anomaly', sample_function)
window = tk.Tk()
#window = tk.Toplevel()
topFrame = tk.Frame(window)
# Tell the Frame to fill the whole window
topFrame.pack(fill=tk.BOTH, expand=1)
# Make the Frame grid contents expand & contract with the window
topFrame.columnconfigure(0, weight=1)
for i in range(4):
topFrame.rowconfigure(i, weight=1)
prepare_btn = tk.Button(topFrame, command=prepare_clicked, text='Button1')
anomaly_btn = tk.Button(topFrame,command=anomaly_clicked, text='Button2')
social_btn = tk.Button(topFrame, command=social_clicked, text='Button3')
buttons = [prepare_btn, anomaly_btn, social_btn]
processing_bar = ttk.Progressbar(topFrame, orient='horizontal', mode='indeterminate')
prepare_btn.grid(row=0, column=0, columnspan=1, sticky='EWNS')
anomaly_btn.grid(row=1, column=0, columnspan=1, sticky='EWNS')
social_btn.grid(row=2, column=0, columnspan=1, sticky='EWNS')
processing_bar.grid(row=3, column=0, columnspan=1, sticky='EWNS')
window.mainloop()
Update
Here's the new improved version, with an 'All' button that runs all the functions, in order. Enjoy!
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
def prepare_func():
print('prepare started')
time.sleep(2)
print('prepare stopped')
def anomaly_func():
print('anomaly started')
time.sleep(2)
print('anomaly stopped')
def social_func():
print('social started')
time.sleep(2)
print('social stopped')
def all_func():
print('all started')
show_and_run(prepare_func, buttons['Prepare'])
show_and_run(anomaly_func, buttons['Anomaly'])
show_and_run(social_func, buttons['Social'])
print('all stopped')
def show_and_run(func, btn):
# Save current button color and change it to green
oldcolor = btn['bg']
btn['bg'] = 'green'
# Call the function
func()
# Restore original button color
btn['bg'] = oldcolor
def run_function(func, btn):
# Disable all buttons
for b in buttons.values():
b['state'] = 'disabled'
processing_bar.start(interval=10)
show_and_run(func, btn)
processing_bar.stop()
# Enable all buttons
for b in buttons.values():
b['state'] = 'normal'
def clicked(func, btn):
Thread(target=run_function, args=(func, btn)).start()
window = tk.Tk()
#window = tk.Toplevel()
topFrame = tk.Frame(window)
# Tell the Frame to fill the whole window
topFrame.pack(fill=tk.BOTH, expand=1)
# Make the Frame grid contents expand & contract with the window
topFrame.columnconfigure(0, weight=1)
for i in range(4):
topFrame.rowconfigure(i, weight=1)
button_data = (
('Prepare', prepare_func),
('Anomaly', anomaly_func),
('Social', social_func),
('All', all_func),
)
# Make all the buttons and save them in a dict
buttons = {}
for row, (name, func) in enumerate(button_data):
btn = tk.Button(topFrame, text=name)
btn.config(command=lambda f=func, b=btn: clicked(f, b))
btn.grid(row=row, column=0, columnspan=1, sticky='EWNS')
buttons[name] = btn
row += 1
processing_bar = ttk.Progressbar(topFrame,
orient='horizontal', mode='indeterminate')
processing_bar.grid(row=row, column=0, columnspan=1, sticky='EWNS')
window.mainloop()
Related
I'm trying to make a sub window for configuration settings. But the text in the sub window is invisible. I am reading correctly but I cannot see the text. Below is my sample code with the problem:
import tkinter as tk
from tkinter import ttk
from tkinter import Menu
class Frames:
def __init__(self):
self.port_com = None
def main_frame(self, win):
# Main Frame
main = ttk.LabelFrame(win, text="")
main.grid(column=0, row=0, sticky="WENS", padx=10, pady=10)
return main
def dut_configuration_frame(self, win):
# Configuration Frame
dut_config_frame = ttk.LabelFrame(win, text="Config")
dut_config_frame.grid(column=0, row=0, sticky='NWS')
# Port COM
ttk.Label(dut_config_frame, text="Port COM").grid(column=0, row=0)
self.port_com = tk.StringVar()
ttk.Entry(dut_config_frame, width=12, textvariable=self.port_com).grid(column=0, row=1, sticky=tk.EW)
self.port_com.set(value="COM7")
print(self.port_com.get())
class ConfigFrames:
def __init__(self):
self.port_com = None
def main_frame(self, win):
# Main Frame
main = ttk.LabelFrame(win, text="")
main.grid(column=0, row=0, sticky="WENS", padx=10, pady=10)
return main
def configuration_frame(self, win):
# Configuration Frame
dut_config_frame = ttk.LabelFrame(win, text="Config")
dut_config_frame.grid(column=0, row=0, sticky='NWS')
# Port COM
ttk.Label(dut_config_frame, text="Port COM").grid(column=0, row=0)
self.port_com = tk.StringVar()
ttk.Entry(dut_config_frame, width=12, textvariable=self.port_com).grid(column=0, row=1, sticky=tk.EW)
self.port_com.set(value="COM5")
print(self.port_com.get())
def menu_bar(win):
def _config():
config_frame = ConfigFrames()
config_window = tk.Tk()
config_window.title("Sub window")
config_window.geometry("200x200")
config_window.resizable(0, 0)
main = config_frame.main_frame(config_window)
config_frame.configuration_frame(main)
config_window.mainloop()
# Menu
menuBar = Menu(win)
win.config(menu=menuBar)
settingsMenu = Menu(menuBar, tearoff=0)
settingsMenu.add_command(label="Config", command=_config)
menuBar.add_cascade(label="Settings", menu=settingsMenu)
frames = Frames()
win = tk.Tk()
win.title("Main window")
win.geometry("200x200")
win.resizable(0, 0)
menu_bar(win)
main = frames.main_frame(win)
frames.dut_configuration_frame(win)
win.mainloop()
As you can see in main window it is visible, but in sub window it is invisible.
And printing in console is correct:
I have tried all the other posts on this topic but none of them have worked for me...
Here is my code:
from tkinter import *
window=Tk()
window.geometry('600x400')
window.title('hello world')
def wrong():
root=Tk()
text=Text(root)
text.insert(INSERT,"WRONG!, you stupid idiot!!!!")
text.pack()
def right():
root=Tk()
text=Text(root)
text.insert(INSERT,"CORRECT, good job!")
text.pack()
def reset():
hide_widgets()
class UIProgram():
def setupUI(self):
buttonlist=[]
button= Button(window,text='Sanjam',command=wrong).pack()
button2=Button(window,text='Sunny the Bunny',command=wrong).pack()
button3= Button(window, text='Sunjum',command=right).pack()
button4= Button(window, text='bob',command=wrong).pack()
button5= Button(window, text='next',command=reset)
button5.pack()
self.label=Label(window)
self.label.pack()
window.mainloop()
program= UIProgram()
program.setupUI()
I am aware of pack_forget() and tried it, but it keeps giving me an error. Also, is it possible to make a command(like the 'reset' one I have) and use that in the command for a clear screen button. Please help, I am new to Tkinter and don't know much about these things..
Thanks
Example code
I'm tired to describe the same mistakes hundreds of times - some comments in code.
import tkinter as tk
# --- classes ---
class UIProgram():
def __init__(self, master):
self.master = master # use to add elements directly to main window
self.buttons = [] # keep buttons to change text
# frame to group buttons and easily remove all buttons (except `Next`)
self.frame = tk.Frame(self. master)
self.frame.pack()
# group button in frame
button = tk.Button(self.frame, text='Sanjam', command=self.wrong)
button.pack()
self.buttons.append(button)
button = tk.Button(self.frame, text='Sunny the Bunny', command=self.wrong)
button.pack()
self.buttons.append(button)
button = tk.Button(self.frame, text='Sunjum', command=self.right)
button.pack()
self.buttons.append(button)
button = tk.Button(self.frame, text='bob', command=self.wrong)
button.pack()
self.buttons.append(button)
# button outside frame
button_next = tk.Button(self.master, text='Next >>', command=self.reset)
button_next.pack()
self.label = tk.Label(self.frame)
self.label.pack()
def wrong(self):
# create second window with message and closing button
win = tk.Toplevel()
tk.Label(win, text="WRONG!, you stupid idiot!!!!").pack()
tk.Button(win, text='close', command=win.destroy).pack()
def right(self):
# create second window with message and closing button
win = tk.Toplevel()
tk.Label(win, text="CORRECT, good job!").pack()
tk.Button(win, text='close', command=win.destroy).pack()
def reset(self):
# remove frame with all buttons
self.frame.pack_forget()
tk.Label(self.master, text="frame removed").pack()
# or only remove text in labels
#for button in self.buttons:
# button['text'] = '-- place for new text --'
# --- main ---
root = tk.Tk()
root.geometry('600x400')
root.title('hello world')
program = UIProgram(root)
root.mainloop()
BTW: if you do var = Widget(...).pack() then you assign None to var because pack()/grid()/place() return None. You have to do it in two lines
var = Widget(...)
var.pack()
or in one line if you don't need var
Widget(...).pack()
I am trying to center some text on a canvas, during program initialization. However, winfo_width/height don't return the correct values for me in this case, so I cannot properly place text using Canvas method create_text(), since I cannot calculate the correct center position. I can only get the correct dimensions post-init, say if I query the size in a button callback.
How to solve this? Here's the code:
try:
from Tkinter import *
except ImportError:
from tkinter import *
class GUI:
def __init__(self):
# root window of the whole program
self.root = Tk()
self.root.minsize(800, 600)
# canvas/viewport for displaying the image and drawing vectors on it
self.viewport = Canvas(self.root, bd=2, relief='ridge', highlightthickness=0)
# define master buttons for audio preview, render to file and clear all vectors
btn_preview = Button(self.root, text='Preview', command=self.Preview)
# layout managing
self.viewport.grid(columnspan=3, padx=5, pady=5, sticky=N+S+W+E)
btn_preview.grid(row=1, padx=85, pady=10, ipadx=5, ipady=5, sticky=W)
# position text on canvas to notify user he can load the image by clicking it
self.viewport.update_idletasks()
textpos = (self.viewport.winfo_width(),self.viewport.winfo_height())
print(textpos)
self.viewport.create_text(textpos[0] / 2, textpos[1] / 2, text="Click here to load an image!", justify='center', font='arial 20 bold')
# weights of rows and columns
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
def Preview(self, event=None):
textpos = (self.viewport.winfo_width(),self.viewport.winfo_height())
print(textpos)
if __name__ == '__main__':
mainwindow = GUI()
mainloop()
Compare the dimensions returned on init to dimensions after you click the Preview button. They're different!
OK haha, I managed to solve it after checking this answer. I needed to bind <Configure> event to the canvas, and define a function that does stuff when window is resized. It's working now!
try:
from Tkinter import *
except ImportError:
from tkinter import *
class GUI:
textid = 0
def __init__(self):
# root window of the whole program
self.root = Tk()
self.root.minsize(800, 600)
# canvas/viewport for displaying the image and drawing vectors on it
self.viewport = Canvas(self.root, bd=2, relief='ridge', highlightthickness=0)
# define master buttons for audio preview, render to file and clear all vectors
btn_preview = Button(self.root, text='Preview', command=self.Preview)
# layout managing
self.viewport.grid(columnspan=3, padx=5, pady=5, sticky=N+S+W+E)
btn_preview.grid(row=1, padx=85, pady=10, ipadx=5, ipady=5, sticky=W)
# weights of rows and columns
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
# bind mouse actions for the canvas
self.viewport.bind('<Configure>', self.ResizeCanvas)
def Preview(self, event=None):
textpos = (self.viewport.winfo_width(),self.viewport.winfo_height())
print(textpos)
def ResizeCanvas(self, event):
if self.textid != 0:
event.widget.delete('openfiletext')
# position text on canvas to notify user he can load the image by clicking it
textpos = (self.viewport.winfo_width(), self.viewport.winfo_height())
self.textid = self.viewport.create_text(textpos[0] / 2, textpos[1] / 2, text="Click here to load an image!", justify='center', font='arial 20 bold', tag='openfiletext')
if __name__ == '__main__':
mainwindow = GUI()
mainloop()
I want to create a GUI in tkinter with two Frames, and have the bottom Frame grayed out until some event happens.
Below is some example code:
from tkinter import *
from tkinter import ttk
def enable():
frame2.state(statespec='enabled') #Causes error
root = Tk()
#Creates top frame
frame1 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame1.grid(column=0, row=0, padx=10, pady=10)
button2 = ttk.Button(frame1, text="This enables bottom frame", command=enable)
button2.pack()
#Creates bottom frame
frame2 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame2.grid(column=0, row=1, padx=10, pady=10)
frame2.state(statespec='disabled') #Causes error
entry = ttk.Entry(frame2)
entry.pack()
button2 = ttk.Button(frame2, text="button")
button2.pack()
root.mainloop()
Is this possible without having to individually gray out all of the frame2's widgets?
I'm using Tkinter 8.5 and Python 3.3.
Not sure how elegant it is, but I found a solution by adding
for child in frame2.winfo_children():
child.configure(state='disable')
which loops through and disables each of frame2's children, and by changing enable() to essentially reverse this with
def enable(childList):
for child in childList:
child.configure(state='enable')
Furthermore, I removed frame2.state(statespec='disabled') as this doesn't do what I need and throws an error besides.
Here's the complete code:
from tkinter import *
from tkinter import ttk
def enable(childList):
for child in childList:
child.configure(state='enable')
root = Tk()
#Creates top frame
frame1 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame1.grid(column=0, row=0, padx=10, pady=10)
button2 = ttk.Button(frame1, text="This enables bottom frame",
command=lambda: enable(frame2.winfo_children()))
button2.pack()
#Creates bottom frame
frame2 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame2.grid(column=0, row=1, padx=10, pady=10)
entry = ttk.Entry(frame2)
entry.pack()
button2 = ttk.Button(frame2, text="button")
button2.pack()
for child in frame2.winfo_children():
child.configure(state='disable')
root.mainloop()
Based on #big Sharpie solution here are 2 generic functions that can disable and enable back a hierarchy of widget (frames "included"). Frame do not support the state setter.
def disableChildren(parent):
for child in parent.winfo_children():
wtype = child.winfo_class()
if wtype not in ('Frame','Labelframe','TFrame','TLabelframe'):
child.configure(state='disable')
else:
disableChildren(child)
def enableChildren(parent):
for child in parent.winfo_children():
wtype = child.winfo_class()
print (wtype)
if wtype not in ('Frame','Labelframe','TFrame','TLabelframe'):
child.configure(state='normal')
else:
enableChildren(child)
I think you can simply hide the whole frame at once.
If used grid
frame2.grid_forget()
If used pack
frame2.pack_forget()
In your case the function would be
def disable():
frame2.pack_forget()
To enable again
def enable():
frame2.pack()
grid_forget() or pack_forget() can be used for almost all tkinter widgets
this is a simple way and reduces the length of your code, I'm sure it works
I want to create a GUI program base on tkinter. One of the widgets is Text. I want to add a horizontal scrollbar in it, but it didn't work.
Where did I make a mistake?
from Tkinter import *
import tkFont
class DpWin(object):
def run(self):
root=Tk()
root.geometry('768x612')
title='dp'
root.title(title)
xscrollbar = Scrollbar(root, orient=HORIZONTAL)
xscrollbar.pack(side=BOTTOM, fill=X)
yscrollbar = Scrollbar(root)
yscrollbar.pack(side=RIGHT, fill=Y)
text = Text(root,xscrollcommand=xscrollbar.set,yscrollcommand=yscrollbar.set)
text.pack()
xscrollbar.config(command=text.xview)
yscrollbar.config(command=text.yview)
text.insert(END,'a'*999)
mainloop()
def start(self):
self.b_start.config(state=DISABLED)
self.b_stop.config(state=ACTIVE)
def stop(self):
self.b_stop.config(state=DISABLED)
self.b_start.config(state=ACTIVE)
if __name__=='__main__':
win=DpWin()
win.run()
I've modified your code according to here. There are 2 main differences.
I made it so the textbox doesn't wrap. If you wrap text, there is nothing for the horizontal scrollbar to scroll to.
I used the grid geometry manager on a frame to keep the scrollbars and text widgets together. The advantage to using .grid is that you actually get scrollbars which are the correct width/height (something you can't achieve with pack).
...
from Tkinter import *
import tkFont
class DpWin(object):
def run(self):
root=Tk()
root.geometry('768x612')
title='dp'
root.title(title)
f = Frame(root)
f.pack()
xscrollbar = Scrollbar(f, orient=HORIZONTAL)
xscrollbar.grid(row=1, column=0, sticky=N+S+E+W)
yscrollbar = Scrollbar(f)
yscrollbar.grid(row=0, column=1, sticky=N+S+E+W)
text = Text(f, wrap=NONE,
xscrollcommand=xscrollbar.set,
yscrollcommand=yscrollbar.set)
text.grid(row=0, column=0)
xscrollbar.config(command=text.xview)
yscrollbar.config(command=text.yview)
text.insert(END, 'a'*999)
mainloop()
def start(self):
self.b_start.config(state=DISABLED)
self.b_stop.config(state=ACTIVE)
def stop(self):
self.b_stop.config(state=DISABLED)
self.b_start.config(state=ACTIVE)
if __name__=='__main__':
win=DpWin()
win.run()
There is one comment regarding making both x and y scrollbars work within the pack framework. Here is a minimal example:
import tkinter as tk
from tkinter import X, Y, BOTTOM, RIGHT, LEFT, Y, HORIZONTAL
class TextExample(tk.Frame):
def __init__(self, master=None):
super().__init__()
sy = tk.Scrollbar(self)
sx = tk.Scrollbar(self, orient=HORIZONTAL)
editor = tk.Text(self, height=500, width=300, wrap='none')
sx.pack(side=BOTTOM, fill=X)
sy.pack(side=RIGHT, fill=Y)
editor.pack(side=LEFT, fill=Y)
sy.config(command=editor.yview)
sx.config(command=editor.xview)
self.pack()
def main():
root = tk.Tk()
root.geometry("800x500+0+0")
app = TextExample(master=root)
root.mainloop()
if __name__ == '__main__':
main()