Tkinter Frozen Screen when start button is clicked - python-3.x

I'm trying to write a tkinter program with start and stop buttons. For both the start and stop buttons, I am importing two different functions from different .py files. When I click on the start button, tkinter freezes and my cursor is constantly "processing" and does not let me click on the stop button.
My tkinter program is below:
import tkinter as tk
from start import hello
from stop import quitprog
class Page(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
def show(self):
self.lift()
class Page1(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs)
label = tk.Label(self, text="Collecting Data Now...")
label.pack(side="top", fill="both", expand=True)
class Page2(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs)
label = tk.Label(self, text="Analyzing Data Now...")
label.pack(side="top", fill="both", expand=True)
class MainView(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
p1 = Page1(self)
p2 = Page2(self)
buttonframe = tk.Frame(self)
container = tk.Frame(self)
buttonframe.pack(side="top", fill="x", expand=False)
container.pack(side="top", fill="both", expand=True)
p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
b1 = tk.Button(buttonframe, text="start", command=hello)
b2 = tk.Button(buttonframe, text="stop", command=quitprog)
b1.pack(side="left")
b2.pack(side="left")
p1.show()
if __name__ == "__main__":
root = tk.Tk()
main = MainView(root)
main.pack(side="top", fill="both", expand=True)
root.wm_geometry("400x400")
root.mainloop()
My start.py is below:
import time
def hello():
for i in range(20):
print("hello world")
time.sleep(1)
My stop.py:
import sys
def quitprog():
sys.exit()
My frozen window:
loading/processing cursor image
Please let me know how to fix this.
Edit: Instead of the start.py program, I'm actually invoking a live twitter stream and that doesn't make use of .sleep(). Instead, there is a continuous flow of twitter data, which causes the program to freeze. Any fixes for this?

You need to use multithreading. Simplest to use (in my opinion) is the standard library multiprocessing module:
import multiprocessing
...
class MainView(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
p1 = Page1(self)
p2 = Page2(self)
buttonframe = tk.Frame(self)
container = tk.Frame(self)
buttonframe.pack(side="top", fill="x", expand=False)
container.pack(side="top", fill="both", expand=True)
p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
b1 = tk.Button(buttonframe, text="start", command=self.hello)
b2 = tk.Button(buttonframe, text="stop", command=quitprog)
b1.pack(side="left")
b2.pack(side="left")
p1.show()
def hello(self, *args):
p = multiprocessing.Process(target=hello)
p.start()
quitprog does not need to be run in a separate process,and can't be either, because then sys.exit won't terminate your program.
I only included the MainView class code - make sure to add the rest !

Related

Trouble Binding a Button in a Class - Python (Tkinter)

Just started learning Tkinter and was hoping someone could help me. I've been trying to bind a keyboard character (Enter button) to a tk button following this example and not getting anywhere.
Say I take the button (Enter) and try bind it nothing happens:
Enter.bind('<Return>', lambda:self.retrieve_Input(t))
If I bind to self instead using Lambda nothing happens also. I can get it to trigger if I remove the lambda but that's not the desired outcome
self.bind('<Return>', lambda:self.retrieve_Input(t))
My Code:
import sys
import tkinter as tk
from tkinter import ttk
class windows(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.wm_title("Test Application")
self.lift() #Bringing the GUI to the front of the screen
main_frame = tk.Frame(self, height=400, width=600) #Creating a main Frame for all pages
main_frame.pack(side="top", fill="both", expand=True)
main_frame.grid_rowconfigure(0, weight=1) #Configuring the location of the main frame using grid
main_frame.grid_columnconfigure(0, weight=1)
# We will now create a dictionary of frames
self.frames = {}
for F in (MainPage, CompletionScreen): #Add the page components to the dictionary.
page = F(main_frame, self)
self.frames[F] = page #The windows class acts as the root window for the frames.
page.grid(row=0, column=0, sticky="nsew")
self.show_page(MainPage) #Method to switch Pages
def show_page(self, cont):
frame = self.frames[cont]
frame.tkraise()
##########################################################################
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#switch_window_button = tk.Button(self, text="Go to the Side Page", command=lambda: controller.show_page(SidePage))
#switch_window_button.pack(side="bottom", fill=tk.X)
tk.Label(self, text="Project Python Search Engine", bg='white').pack()
tk.Label(self, text="", bg='white').pack()
tk.Label(self, text="Song", bg='white').pack()
tk.Label(self, text="", bg='white').pack()
t = tk.Entry(self, bg='white', width = 50)
t.pack()
tk.Label(self, text="", bg='white').pack()
Enter = tk.Button(self, text='Search', command= lambda:self.retrieve_Input(t))
Enter.pack()
tk.Button(self, text="Latest Popular Songs", command=lambda:self.Popular_Songs(t)).pack() #Line 210 onwards
Enter.bind('<Return>', lambda:self.retrieve_Input(t))
def retrieve_Input(self, t):
print ("work")
print (t)
class CompletionScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Completion Screen, we did it!")
label.pack(padx=10, pady=10)
switch_window_button = ttk.Button(
self, text="Return to menu", command=lambda: controller.show_page(MainPage)
)
switch_window_button.pack(side="bottom", fill=tk.X)
if __name__ == "__main__":
App = windows()
App.mainloop()
I'm not really sure what I'm missing
Answer: The button probably doesn't have the keyboard focus. When I run your code and then use the keyboard to move the focus to the button, your binding works. You probably want to bind to the entry widget rather than the button since that's what will have the keyboard focus. – Thanks Bryan Oakley

How to transfer data between Toplevel widgets (OOP):

I am beginner to Python language !
I designed three windows by three classes.
First window is the main window and it is on the first class.
And the others defined as toplevel widgets by another two classes.
I need to send data which input to the second window (first toplevel widget) and get (view) it from the third window (second toplevel widget).
So, to do that I used the - getattr - function.
But it gaven some (AttributeError) error :
d = self.var1.get()
File "C:\Python\Python310\lib\tkinter_init_.py", line 2383, in getattr
return getattr(self.tk, attr)
AttributeError: '_tkinter.tkapp' object has no attribute 'var1'
from tkinter import *
import tkinter as tk
class obj_1(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("TK WINDOW 01")
self.geometry("300x150+10+10")
btn_next = tk.Button(self, text="Second Window", width=15, height=1, command=lambda: obj_2())
btn_next.pack(pady=20)
class obj_2(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
self.title("TK WINDOW 02")
self.geometry("400x100+300+300")
self.var1 = tk.StringVar()
btn_next = tk.Button(self, text="-->", width=15, height=1, command=lambda: obj_3())
btn_next.pack()
self.lbl_ent_1 = tk.Entry(self, textvariable=self.var1, width=15)
self.lbl_ent_1.pack(pady=20)
self.lbl_ent_1.focus()
def get_from_second(self):
d = self.var1.get()
return d
class obj_3(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
self.title("TK WINDOW 03")
self.geometry("400x100+800+300")
self.var2 = tk.StringVar()
btn_view = tk.Button(self, text="View", width=15, height=1, command=lambda: self.get_from_third())
btn_view.pack()
self.lbl_vw = tk.Label(self, textvariable=self.var2, width=15, height=1, pady=20)
self.lbl_vw.pack()
def get_from_third(self):
p = getattr(obj_2, 'get_from_second')(self)
self.var2.set(p)
print(p)
if __name__ == '__main__':
app = obj_1()
app.mainloop()
I commented the changes where they were made, but in short: you had the wrong super for both of your toplevel windows, with the proper super you get a master to refer to. Too much juggling (C from A from B). My example is direct (B from A) then (C from A). You don't even need the get_from_second because it has been obsoleted by a virtual get_directly_from_root. In other words, store your var on the root and then refer to it via master.
from tkinter import *
import tkinter as tk
class obj_1(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("TK WINDOW 01")
self.geometry("300x150+10+10")
btn_next = tk.Button(self, text="Second Window", width=15, height=1, command=lambda: obj_2(self))
btn_next.pack(pady=20)
self.var1 = tk.StringVar() #store the var on the master
class obj_2(tk.Toplevel): #Toplevel not Tk
def __init__(self, master, *args, **kwargs): #master will be your main Tk window
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.title("TK WINDOW 02")
self.geometry("400x100+300+300")
btn_next = tk.Button(self, text="-->", width=15, height=1, command=lambda: obj_3(master))
btn_next.pack()
self.lbl_ent_1 = tk.Entry(self, textvariable=master.var1, width=15) #<--var from master
self.lbl_ent_1.pack(pady=20)
self.lbl_ent_1.focus()
class obj_3(tk.Toplevel): #Toplevel not Tk
def __init__(self, master, *args, **kwargs): #master will be your main Tk window
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.title("TK WINDOW 03")
self.geometry("400x100+800+300")
self.var2 = tk.StringVar()
btn_view = tk.Button(self, text="View", width=15, height=1, command=lambda: self.get_from_third())
btn_view.pack()
self.lbl_vw = tk.Label(self, textvariable=self.var2, width=15, height=1, pady=20)
self.lbl_vw.pack()
def get_from_third(self):
self.var2.set(self.master.var1.get())
if __name__ == '__main__':
app = obj_1()
app.mainloop()
I left your code mostly in tact but consider the below. Your button is entirely unnecessary, you can just automatically have the data by sharing var1 from master.
class obj_3(tk.Toplevel): #Toplevel not Tk
def __init__(self, master, *args, **kwargs): #master will be your main Tk window
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.title("TK WINDOW 03")
self.geometry("400x100+800+300")
self.lbl_vw = tk.Label(self, textvariable=master.var1, width=15, height=1, pady=20)
self.lbl_vw.pack()
Aside: All this obj_x naming is very poor practice. Give your classes meaningful names that give an indication of what they do. The same goes for varx, and any other name you make which is entirely arbitrary. Imagine if tkinter named everything Widget1, Widget2, etc...How could you possibly work with that? The same goes for your code. You keep naming everything arbitrarily and you wont be able to work with your own code.
What is vw in self.lbl_vw? VariableWindow, perhaps? That sounds like a great name for that class. While you're at it, stop naming your classes with definition syntax. Even if you kept the poor obj_x naming it should still, at least, be: Obj_x.
Not really a question:
Why are you opening a 3rd window to display a variable that was just typed, and why would you have to click a button to see it? Again, I'm not really asking this. I want you to ask yourself that. Now that I've boiled your code down to one var, your app doesn't make any sense in design. Your app is currently 3 entire windows built around 1 variable. It's probably time to reconsider what it is you are actually trying to do.
Imagine if you opened your browser, had to click a search button which opens up a query window, and then click another button which opens ANOTHER window with the page in it. It looks like you are essentially building that. It's a bad design.

Change Background Tkinter in Classes

I'm relatively new to Tkinter and Python and just started with Tkinter in object oriented way.
Im trying to change the background colour of all the different pages I have so i have the following code
import tkinter as tk
import sqlite3
from tkinter.ttk import *
from tkinter import *
LARGE_FONT = ("Verdana", 12)
HEIGHT = 700
WIDTH = 800
class programStart(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1, minsize=WIDTH)
container.grid_columnconfigure(0, weight=1, minsize=HEIGHT)
self.frames = {}
for F in (StartPage, Register, LoginPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise() #Raises to front
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
I have tried container.configure(bg='red') and so on, to no success
How can I go about this issue?
Try this:
import tkinter as tk
import sqlite3
from tkinter.ttk import *
from tkinter import *
LARGE_FONT = ("Verdana", 12)
HEIGHT = 700
WIDTH = 800
class programStart(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1, minsize=WIDTH)
container.grid_columnconfigure(0, weight=1, minsize=HEIGHT) #0 minimum, weight is priority
self.frames = {}
for F in (StartPage, Register, LoginPage):
frame = F(container, self, bg="red")
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise() #Raises to front
class StartPage(tk.Frame):
def __init__(self, parent, controller, bg=None, fg=None):
tk.Frame.__init__(self, parent, bg=bg=, fg=fg)
# Make sure that all of the tkinter widgets have `bg=bg=, fg=fg`
Basically you need to tell all of the widgets that you are creating that the background should be red. When creating your widgets you can pass in the bg parameter (background).
This is a minimal version of the system I use to give the user of the GUI the ability to change the colors and fonts of all the widgets in the app according to color schemes he can choose himself.
import tkinter as tk
formats = {
'bg' : 'black',
'fg' : 'white'
}
class Labelx(tk.Label):
def __init__(self, master, *args, **kwargs):
tk.Label.__init__(self, master, *args, **kwargs)
def winfo_subclass(self):
''' a method that works like built-in tkinter method
w.winfo_class() except it gets subclass names
of widget classes custom-made by inheritance '''
subclass = type(self).__name__
return subclass
class Label(Labelx):
'''
If this subclass is detected it will be reconfigured
according to user preferences.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['bg'],
fg=formats['fg'])
class LabelNegative(Labelx):
'''
If this subclass is detected it will be reconfigured
according to user preferences.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['fg'],
fg=formats['bg'])
def change_colors():
for widg in (lab1, lab2):
if widg.winfo_class() == 'Label':
if widg.winfo_subclass() == 'Label':
widg.config(bg=formats['fg'], fg=formats['bg'])
elif widg.winfo_subclass() == 'LabelNegative':
widg.config(bg=formats['bg'], fg=formats['fg'])
root = tk.Tk()
f1 = tk.Frame(root)
f1.grid(column=0, row=0)
lab1 = Label(f1, text='Label')
lab1.grid()
lab2 = LabelNegative(f1, text='LabelNegative')
lab2.grid()
button = tk.Button(root, text='colorize', command=change_colors)
button.grid()
root.mainloop()

Tkinter window does not update correctly when running

I'm trying to make a Tkinter window show updated data but it only pops up after 13 seconds with just the last value. I want it to pop up and change the values on screen. Mind you, the big goal of this code is to take data from a database (which updates every 3 seconds) and show the data onscreen, while running continuously, so if the answer could include some pointers on the "after" or "update" functions it would be greatly appreciated!
Here is what I have so far.
from tkinter import *
import time
class GUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Tk.wm_title(self, "Main Window")
self.container = Frame(self)
self.container.pack(side=TOP, fill=BOTH, expand=TRUE)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frame = StartPage(self.container, self)
self.frames[StartPage] = self.frame
self.frame.grid(row=0, column=0, sticky=NSEW)
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.label = Label(self, text="Current ID:\n")
self.label.pack(padx=10, pady=10)
self.data_label = Label(self)
self.data_label.pack()
self.update_data()
def update_data(self):
var1 = StringVar()
for i in range(10):
var1.set(str(i))
self.data_label.config(text=str(i))
time.sleep(1)
main = GUI()
main.mainloop()
I can give you a partial answer. The reason you don't see any updates is that time.sleep() suspends the process and does not allow for tkinter to repaint the window.
You don't use the label textvariable correctly. Specify it in the label and the label will change as you change the textvariable.
You use both pack and grid at the same time which may cause problems.
I have not used after() in a class before so I don't know how to work it, but this example should give you some pointers. I'm keeping console printouts in the code so I can see what it does.
from tkinter import *
import time
class GUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Tk.wm_title(self, "Main Window")
self.container = Frame(self)
self.container.pack(side=TOP, fill=BOTH, expand=TRUE)
self.frames = {}
self.frame = StartPage(self.container, self)
self.frames[StartPage] = self.frame
self.frame.pack(side=TOP, fill=BOTH, expand=TRUE)
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
frame.update_data()
print('Done')
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.parent = parent
self.label = Label(self, text="Current ID:\n")
self.label.pack(padx=10, pady=10)
self.var1 = StringVar()
self.data_label = Label(self, textvariable=self.var1)
self.data_label.pack()
self.update_idletasks() # Calls all pending idle tasks
def update_data(self):
if not self.var1.get(): self.var1.set('0')
iteration = int(self.var1.get())
print('iteration', iteration)
if iteration < 3:
iteration = iteration + 1
self.var1.set(str(iteration))
self.update_idletasks() # Calls all pending idle tasks
time.sleep(1)
self.update_data()
main = GUI()
main.mainloop()
You will have to research after() yourself as I can't help you there.

How to open and close another window with scrollbar in tkinter for python 3.5.?

I want to build a Tkinter app in python 3.5. with a StartPage and a another window PageTwo that includes a table with a scolldownbar. I have tried to apply a framework from an online tutorial and the listbox example from another website.
My problem is: when I run the program both pages are loaded directly. How can I manage to let PageTwo only open on click on Button in StartPage, and then apply another button in PageTwo that closed PageTwo again and redirects to StartPage?
Second question: Alternatively to the listbox example I would like to use canvas with scrollbar on PageTwo. But how and where do I have to introduce the canvas? I get totally messed up with all the inheritances throughout the different classes.
If you would suggest a complete different setup, this would also be fine.
Many thanks for your help.
import tkinter as tk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame = StartPage(container, self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise() # zeigt Frame oben an
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Your choice?")
label.pack(pady=10,padx=10)
button1 = tk.Button(self, text="Open PageTwo",
width = 25, command=lambda: controller.show_frame(PageTwo))
button1.pack(pady=10, padx=10)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
master = tk.Tk()
scrollbar = tk.Scrollbar(master)
scrollbar.pack(side=tk.RIGHT, fill="y")
listbox = tk.Listbox(master, yscrollcommand=scrollbar.set)
for i in range(1000):
listbox.insert(tk.END, str(i))
listbox.pack(side=tk.LEFT, fill="both")
scrollbar.config(command=listbox.yview)
if __name__ == '__main__':
app = GUI()
app.mainloop()
To fix the issues:
initialize PageTwo only when the button is clicked
use Toplevel for popup window
use root as the StartPage
Below is a demo based on your posted code:
import tkinter as tk
from tkinter import ttk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
label = tk.Label(self, text="Your choice?")
label.pack(pady=10,padx=10)
button1 = ttk.Button(self, text="Open PageTwo", width=25, command=lambda: self.show_frame(PageTwo))
button1.pack(pady=10, padx=10)
button2 = ttk.Button(self, text="Open PageCanvas", width=25, command=lambda: self.show_frame(PageCanvas))
button2.pack(pady=10, padx=10)
def show_frame(self, page):
win = page(self)
# make window modal
win.grab_set()
self.wait_window(win)
class PageTwo(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title('Two')
scrollbar = tk.Scrollbar(self)
scrollbar.pack(side=tk.RIGHT, fill="y")
listbox = tk.Listbox(self, yscrollcommand=scrollbar.set)
for i in range(1000):
listbox.insert(tk.END, str(i))
listbox.pack(side=tk.LEFT, fill="both")
scrollbar.config(command=listbox.yview)
class PageCanvas(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title('Canvas')
self.geometry('400x600')
canvas = tk.Canvas(self, bg='white', scrollregion=(0, 0, 400, 20000))
canvas.pack(fill='both', expand=True)
vbar = tk.Scrollbar(canvas, orient='vertical')
vbar.pack(side='right', fill='y')
vbar.config(command=canvas.yview)
canvas.config(yscrollcommand=vbar.set)
for i in range(1000):
canvas.create_text(5, i*15, anchor='nw', text=str(i))
if __name__ == '__main__':
app = GUI()
app.mainloop()

Resources