Interacting with Tkinter window during a long process - python

I have a basic python class that creates a window using the standard Tkinter library:
import Tkinter
class GUI(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def lock_func(self):
while 1==1:
print "blah"
def initialize(self):
self.processBtn = Tkinter.Button(self, text="Process", command=self.lock_func)
self.processBtn.pack()
app = GUI(None)
app.mainloop()
when I hit the Process button, the window doesn't respond.
I want to be able to close the program (using the x button) whene the lock_func is runing.

You could use a generator to hold the state within the loop, and use yield to relinquish control back to the main loop. Then use self.after to repeatedly call the generator's next method to simulate the effect of while True -- but doing it in a way which is friendly to Tkinter's main loop.
import Tkinter as tk
class App(object):
def __init__(self, master):
self.master = master
self.initialize()
def lock_func(self):
def step():
while True:
print("blah")
self.nextstep_id = self.master.after(1, nextstep)
yield
nextstep = step().next
self.nextstep_id = self.master.after(1, nextstep)
def stop(self):
self.master.after_cancel(self.nextstep_id)
print("stopped")
def initialize(self):
self.nextstep_id = 0
self.process_button = tk.Button(self.master, text="Process",
command=self.lock_func)
self.stop_button = tk.Button(self.master, text="Stop",
command=self.stop)
self.process_button.pack()
self.stop_button.pack(expand='yes', fill='x')
root = tk.Tk()
app = App(root)
root.mainloop()

You can use the window.update() method too keep your GUI active and functional after every time you change something on it. During the roots mainloop, this happens automatically but if you're prolonging the mainloop it's probably a good idea to do it manually your self. Put the window.update() in the loop that is taking a while. Note: window is a Tk() object

One way is to use threading:
import Tkinter
import thread
class GUI(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def lock_func(self):
while 1==1:
print "blah"
def initialize(self):
self.processBtn = Tkinter.Button(self, text="Process", command=lambda: thread.start_new_thread(self.lock_func, ()))
self.processBtn.pack()
app = GUI(None)
app.mainloop()
However, it will keep printing until you close the Python console.
To stop it, you can use another button that changes a variable:
import Tkinter
import thread
class GUI(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.shouldPrint = True
self.initialize()
def lock_func(self):
while self.shouldPrint:
print "blah"
def setShouldPrint(self, value):
self.shouldPrint = value
def initialize(self):
self.processBtn = Tkinter.Button(self, text="Process", command=lambda: thread.start_new_thread(self.lock_func, ()))
self.stopBtn = Tkinter.Button(self, text = "Stop", command = lambda: self.setShouldPrint(False))
self.processBtn.grid(row = 1)
self.stopBtn.grid(row = 2)
app = GUI(None)
app.mainloop()

Related

Loop triggered by Tkinter button and ended by another

The following code will show a small Tkinter user interface, however my buttons will not trigger the loop called copy_loop. I have been trying for several hours and I cannot figure out how to fix this. Any help will be very appreciated.
import tkinter as tk
import threading
class App():
def __init__(self, master):
self.isrunning = False
self.button1 = tk.Button(main, text='start')
self.button1.bind = ("<Button-1>", self.startrunning)
self.button1.pack()
self.button2 = tk.Button(main, text='stop')
self.button2.bind = ("<Button-1>", self.stoprunning)
self.button2.pack()
def startrunning(self, event):
self.isrunning = True
t = threading.Thread(target=self.copy_loop)
t.start()
def stoprunning(self, event):
self.isrunning = False
def copy_loop(self):
while self.isrunning:
print("Running...")
main = tk.Tk()
app = App(main)
main.mainloop()
bind is a function, not an attribute. Where you have:
self.button1.bind = ("<Button-1>", self.startrunning)
you are actually overwriting the bind function on your instance. It is now a tuple.
Instead, do:
self.button1.bind("<Button-1>", self.startrunning)
And likewise for button2.
You have an equals sign where there shouldn't be one. The command should be:
self.button1 = tk.Button(main, text='start')
self.button1.bind("<Button-1>", self.startrunning)
Or the much neater:
self.button1 = tk.Button(main, text='start', command=self.startrunning)
And your method signatures should have event=None:
def startrunning(self, event=None):
Or even more flexible:
def startrunning(self, *args):

import a python file that create a window when main window button clicks

I am creating 2 window in my program and i am using two class, since the code is complex, i separate it in 2 different python file. After i imported the second window file, how can i make sure it open without having this error which show in this picture
The original result should look like this after the new window button clicked:
Coding for Main Window:
from tkinter import *
import classGUIProgram
class Window(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.geometry("600x400+30+30")
self.wButton = Button(self, text='newWindow', command = self.OnButtonClick)
self.wButton.pack()
def OnButtonClick(classGUIProgram):
classGUIProgram.top = Toplevel()
master = Tk()
b = classGUIProgram.HappyButton(master)
master.mainloop()
if __name__ == "__main__":
window = Window(None)
window.title("title")
window.mainloop()
Coding for Second Window:
from tkinter import *
class HappyButton:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.printButton = Button(frame, text="Print message", command=self.printMessage)
self.printButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Quit", command= quit)
self.quitButton.pack(side=LEFT)
self.downloadHistoryCB=Checkbutton(frame, text="Download History")
self.downloadHistoryCB.pack(side=LEFT)
def printMessage(self):
print("Wow this actually worked!")
master = Tk()
b = HappyButton(master)
master.mainloop()
You're creating extra Tk windows. Here is an example of using Toplevel widgets and another file.
mainWindow.py
import tkinter as tk
import secondWindow as sW
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title("Main Window")
self.geometry("600x400+30+30")
tk.Button(self, text = "New Window", command = self.new_window).pack()
tk.Button(self, text = "Close Window", command = self.close).pack()
self._second_window = None
def new_window(self):
# This prevents multiple clicks opening multiple windows
if self._second_window is not None:
return
self._second_window = sW.SubWindow(self)
def close(self):
# Destory the 2nd window and reset the value to None
if self._second_window is not None:
self._second_window.destroy()
self._second_window = None
if __name__ == '__main__':
window = MainWindow()
window.mainloop()
secondWindow.py
import tkinter as tk
class SubWindow(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.title("Sub Window")
self.geometry("400x300+30+30")
# Change what happens when you click the X button
# This is done so changes also reflect in the main window class
self.protocol('WM_DELETE_WINDOW', master.close)
tk.Button(self, text = "Print", command = self.printMessage).pack()
def printMessage(self):
print("Wow this actually worked!")
When using another file be sure to not have any global code you don't want running. Your classes don't have to inherit from Tk and Toplevel, this is just an example. But you need to ensure you only ever have one instance of Tk otherwise you get the behaviour you encountered

Is it possible to keep the same window for every class in python's tkinter?

I am trying to create a program in tkinter which allows me to open an initial window then to keep it throughout all classes used. For example, if I was to create a button in a window then when I click this button, it would exuecute a method that destroys the widget, and then executes a new class that builds a new screen within the same window, such as text opposed to a button.
from tkinter import *
class Window1:
def __init__(self, master):
self.master = master
self.label = Button(self.master, text = "Example", command = self.load_new)
self.label.pack()
def load_new(self):
self.label.destroy()
## Code to execute next class
class Window2:
def __init__(self, master):
self.master = master
self.label = Label(self.master, text = "Example")
self.label.pack()
def main():
root = Tk()
run = Window1(root)
root.mainloop()
if __name__ == '__main__':
main()
I understand this is less practical, but I am curious. Cheers.
Tk() creates main window and variable root gives you access to this window. You can use root as argument for Window2 and you will have access to main window inside Window2
from tkinter import *
class Window1:
def __init__(self, master):
# keep `root` in `self.master`
self.master = master
self.label = Button(self.master, text="Example", command=self.load_new)
self.label.pack()
def load_new(self):
self.label.destroy()
# use `root` with another class
self.another = Window2(self.master)
class Window2:
def __init__(self, master):
# keep `root` in `self.master`
self.master = master
self.label = Label(self.master, text="Example")
self.label.pack()
root = Tk()
run = Window1(root)
root.mainloop()
--
Probably nobody use another class to create Label in place of Button ;)
--
EDIT: In this example using names Window1 and Windows2 is misleading because there is only one window and two classes which use this window. I would rather use names FirstOwner, SecondOwner
Everything is implemented in one Tk class and in this case there always is only one window.
from tkinter import *
from tkinter import ttk
class MainWindow():
def __init__(self, mainWidget):
self.main_frame = ttk.Frame(mainWidget, width=300, height=150, padding=(0, 0, 0, 0))
self.main_frame.grid(row=0, column=0)
self.some_kind_of_controler = 0
self.main_gui()
def main_gui(self):
root.title('My Window')
self.main_label_1 = ttk.Label(self.main_frame, text='Object_1')
self.main_label_1.grid(row=0, column=0)
self.main_label_2 = ttk.Label(self.main_frame, text='Object_2')
self.main_label_2.grid(row=1, column=0)
self.main_label_3 = ttk.Label(self.main_frame, text='Object_3')
self.main_label_3.grid(row=2, column=0)
self.setings_button = ttk.Button(self.main_frame, text='Setings')
self.setings_button.grid(row=0, column=1)
self.setings_button.bind('<Button-1>', self.setings_gui)
self.gui_elements = [self.main_label_1,
self.main_label_2,
self.main_label_3,
self.setings_button]
def setings_gui(self, event):
self.gui_elements_remove(self.gui_elements)
root.title('Setings')
self.main_label_1 = ttk.Label(self.main_frame, text='Object_1')
self.main_label_1.grid(row=2, column=0)
self.main_menu_button = ttk.Button(self.main_frame, text='Main menu')
self.main_menu_button.grid(row=0, column=1)
self.main_menu_button.bind('<Button-1>', self.back_to_main)
self.some_kind_of_controler = 1
self.gui_elements = [self.main_label_1,
self.main_menu_button]
def back_to_main(self, event):
if self.some_kind_of_controler == 1:
self.gui_elements_remove(self.gui_elements)
else:
pass
self.main_gui()
def gui_elements_remove(self, elements):
for element in elements:
element.destroy()
def main():
global root
root = Tk()
root.geometry('300x150+50+50')
window = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()

Python Tkinter Input Box

Good day.
I am trying to create my own input box for use in my project.
basically what i am trying to do is run my main form which will call the second. the user will provide some data on the second and when the press the ok/close button on the second for the data will be passed back to the first. similar in functionality to the inputbox.
here is what i have created, but being new to python i am not sure where i am going wrong/nor can i quick figure out when to put the return.
My Class is here
import tkinter as tk
class MainWindow():
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter a Grouping Name')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.focus_set()
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='OK', command=self.DestWin)
self.mySubmitButton.pack()
def DestWin(self):
self.top.destroy()
The method to call it is here
abc=configurator.MainWindow(root)
Not exactly sure what you are trying to achieve, but if you are trying to get values from one window to another, below you can find an extended example based on your code.
import tkinter as tk
class MainWindow():
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter a Grouping Name')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.focus_set()
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='OK', command=self.DestWin)
self.mySubmitButton.pack()
def DestWin(self):
# call callback function setting value in MyFrame
self.callback(self.myEntryBox.get())
self.top.destroy()
def set_callback(self, a_func):
self.callback = a_func
class MyFrame(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.pack()
self.myLabel1 = tk.Label(parent, text='Click OK to enter the group name')
self.myLabel1.pack()
self.mySubmitButton1 = tk.Button(parent, text='OK', command=self.get_group_name)
self.mySubmitButton1.pack()
def get_group_name(self):
mw = MainWindow(None)
# provide callback to MainWindow so that it can return results to MyFrame
mw.set_callback(self.set_label)
def set_label(self, astr = ''):
self.myLabel1['text'] = astr
root = tk.Tk()
mf = MyFrame(root)
root.mainloop()
The screenshot:
The text from the right window, when OK is pressed, will be shown in the left window. This is achieved through callbacks. MainWindow takes a callback function, and when you press OK, it is executed. The callback is set_label from MyFrame.
Hope this helps.

Changing the colour of a Tkinter button in a function

I want to change the colour of a button when pressing a different button. The below code recreates the Attribrute Error.
Ideally the solution should be able to change all of the attributes of the button (see the attempted state change) but I didn't put this in the title because I don't know if 'attributes' is the right word.
import Tkinter
def tester():
class window(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid()
button1 = Tkinter.Button(self,text=u"Button")
button1.grid(padx=5,pady=5)
button2 = Tkinter.Button(self,text=u"Change",command=self.colourer)
button2.grid(column=1,row=0,pady=5)
button3 = Tkinter.Button(self,text=u"Disabled",state='disabled')
button3.grid(column=1,row=0,pady=5)
def colourer(self):
self.button1.configure(bg='red')
# self.button1.config(bg='red') -- this gives same error
# self.button3.configure(state='normal') -- as does this
if __name__ == "__main__":
app = window(None)
app.title('Tester')
app.mainloop()
tester()
All of the ways suggested here give the same error: Changing colour of buttons in tkinter
Thanks
The root of your problem is that you're not defining self.button. You need to assign a value to that variable:
self.button = Tkinter.Button(...)
you need give self.button1 while declaring
if you see grid you gave same column name for button2 and button 3 so they overlap each other
try this
import Tkinter
def tester():
class window(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
print self.grid()
self.button1 = Tkinter.Button(self,text=u"Button")
self.button1.grid(padx=5,pady=5)
self.button2 = Tkinter.Button(self,text=u"Change",command=self.colourer)
self.button2.grid(column=1,row=0,pady=5)
self.button3 = Tkinter.Button(self,text=u"Disabled",state='disabled')
self.button3.grid(column=2,row=0,pady=5)
def colourer(self):
self.button1.configure(bg='red')
# self.button1.config(bg='red') -- this gives same error
# self.button3.configure(state='normal') -- as does this
if __name__ == "__main__":
app = window(None)
app.title('Tester')
app.mainloop()
tester()

Categories