I have written some unittests that use Selenium and created a Tkinter GUI for it.
script.py is:
from selenium import webdriver
import unittest
class LaunchChrome(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome(r"C:\Users\USERNAME\PycharmProjects\First\Drivers\chromedriver.exe")
cls.driver.maximize_window()
cls.driver.get('https://facebook.com')
if __name__ == '__main__':
unittest.main()
interface.py is:
from tkinter import Button
from tkinter.ttk import *
import unittest
from ex import LaunchChrome
class HomeApplication:
def init_page(self):
self.buStart = Button(self, text="Start", width=60)
self.buStart.grid(row=4, column=0, columnspan=3)
self.buStart.config(command=self.start_app)
def run(self):
Launch_Chrome = unittest.TestLoader().loadTestsFromTestCase(LaunchChrome)
test_suite = unittest.TestSuite([Launch_Chrome])
unittest.TextTestRunner(verbosity=2).run(test_suite)
def start_app(self):
print("run")
self.run()
After I open the GUI and click on the "Start" button, unittests work fine but the GUI stops to respond (Not Responding):
How can I use multiprocessing is this code?
The tests are running in the main thread. The Tkinter mainloop waits for the start_app method to return, which does not happen until the tests finish execution.
To solve the problem, you can spawn a separate thread or process specifically for the tests.
For example, replace interface.py with the following:
import unittest
import tkinter as tk
import tkinter.ttk as ttk
from threading import Thread
from ex import LaunchChrome
class HomeApplication(tk.Tk):
def __init__(self):
super().__init__()
ttk.Button(self, text="Start", width=60, command=self.start_app)\
.grid(row=4, column=0, columnspan=3)
def run_tests(self):
test_suite = unittest.TestSuite([unittest.TestLoader()\
.loadTestsFromTestCase(LaunchChrome)])
unittest.TextTestRunner(verbosity=2).run(test_suite)
def start_app(self):
Thread(target=self.run_tests, daemon=True).start()
if __name__ == "__main__":
HomeApplication().mainloop()
Related
I have this small code that execute a function and in the meanwhile shows an indeterminate tkinter progressbar. When you let the program execute everything works fine while if you try to stop the process by closing the tkinter window you get the RuntimeError: main thread is not in main loop. I understand that the solution could be to bring the bar mainloop in the main thread or alternatively use queue but it is not clear to me how to do this. Here you can find the code with a simple mock function (addit) that executes. Thank you everyone in advance!
import threading
importing tkinter module
import tkinter
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(threading.Thread):
def __init__(self):
threading.Thread.__init__(self, daemon=True)
def run(self):
self.wbar=tkinter.Tk()
self.wbar.attributes("-topmost", True)
self.wbar.title('Tool')
lb=Label(self.wbar,text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self.wbar,orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.wbar.protocol("WM_DELETE_WINDOW", self.on_closing)
self.loopmain()
def loopmain(self):
self.wbar.mainloop()
def quitall(self):
self.wbar.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.wbar.quit()
sys.exit()
def main():
mygui=tkwindow()
mygui.start()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
The structure of the code is only wrong. You should inherit tk.Tk and not threading.Thread. Instead of creating a function for the mainloop, just insert it at the bottom of the run() function (use self instead of self.wbar). Initialize the thread at the start of the run() function. In the main() function you have called start but in the class you defined it as run(). Here is your code if you added the above changes and fixed all errors:
import threading
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(tk.Tk):
def __init__(self):
super().__init__()
def run(self):
threading.Thread.__init__(self, daemon=True)
self.attributes("-topmost", True)
self.title('Tool')
lb=Label(self, text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self, orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.mainloop()
def quitall(self):
self.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.quitall()
def main():
mygui=tkwindow()
mygui.run()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
There is no way to put the progress bar into the thread. Even if you could, it wouldn't be of any use since it would make the code very complex. So just use this as an alternative.
Updated Code
import threading
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(tk.Tk):
def __init__(self):
super().__init__()
def run(self):
threading.Thread.__init__(self, daemon=True)
self.attributes("-topmost", True)
self.title('Tool')
lb=Label(self, text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self, orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
def quitall(self):
self.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.quitall()
def main():
mygui=tkwindow()
mygui.run()
addit(2,3)
mygui.mainloop()
def addit(a,b):
print("ADDIT")
threading.Timer(3, lambda: print(a+b)).start()
if __name__=='__main__':
main()
Probably I have found a way to do just what I wanted using queue. Unfortunately the code kill the main thread abruptly with interrupt_main and not in a nice way as sys.exit() could do but is the only solution I have found for now. The updated code is the following:
import threading
import tkinter
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
from queue import Queue
import _thread
import os
class tkwindow(threading.Thread):
def __init__(self,dataq):
threading.Thread.__init__(self, daemon=True)
self.dataq=dataq
def run(self):
self.wbar=tkinter.Tk()
self.wbar.attributes("-topmost", True)
self.wbar.title('Tool')
lb=Label(self.wbar,text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self.wbar,orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.dataq.put(0)
self.loopmain()
def loopmain(self):
self.wbar.protocol("WM_DELETE_WINDOW", self.on_closing)
self.wbar.after(100,self.checkq)
self.wbar.mainloop()
def checkq(self):
v=self.dataq.get()
if v:
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.wbar.quit()
os._exit(0) #or alternatively _thread.interrupt_main()
def quitall(self):
self.wbar.quit()
sys.exit()
def on_closing(self):
self.dataq.put(1)
self.checkq()
def main():
dataq=Queue()
mygui=tkwindow(dataq)
mygui.start()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
I'm trying to keep my code clean by separating the GUI from the logic.
Inside of the 'main.py' file, I'd like to call functions from other files that are imported to build the GUI.
The problem is that I cannot figure out how to build a GUI from the 'main.py' file when I try to call another file as an imported module.
Here's what I have in the 'main.py' file:
from tkinter import *
import create_btn
class Main(Tk):
def __init__(self):
super().__init__()
self.title('Main Window')
self.geometry('600x400')
self.eval('tk::PlaceWindow . center')
if __name__ == '__main__':
app = Main()
app.mainloop()
And here is what I have in the 'create_btn.py' file:
from tkinter import *
def createBTN(self):
self.b1 = Button(root, text='B1')
self.b1.pack()
So, how exactly can I build a simple button from another file that I want to import into the 'main.py', or in other words, how do I get the 'create_btn.py' file to build the button inside of the 'main.py' file? A simple example would be greatly appreciated.
I have completely rewritten your code:
# main.py
from tkinter import *
import create_btn
class Main_app:
def __init__(self):
self.root = Tk()
self.root.title("Main Window")
self.geometry("600x400")
self.eval('tk::PlaceWindow . center')
self.button = create_btn.create(self.root)
self.root.mainloop()
a = Main_app()
# create_btn.py
from tkinter import *
class create:
def __init__(self, root):
btn = Button(root, text="Button")
btn.pack()
return btn
This way you can edit the button later in main.py
I'm pretty new in Python. I'm trying to develop a GUI using a drag and drop designer called PAGE (http://page.sourceforge.net/html/index.html) based on Tkinter.
After having created a new project, I added a button to a window. The following code is autogenerated by PAGE and saved into a file called 'myGui.py':
import sys
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *
import myGui_support
class Toplevel1:
def __init__(self, top=None):
...
self.StartButton = tk.Button(self.top)
self.StartButton.configure(command=myGui_support.StartButton_Callback)
...
def start_up():
myGui_support.main()
if __name__ == '__main__':
myGui_support.main()
PAGE creates also a second file called 'myGui_support.py', in which after some import statements, the following code is included:
import myGui
def main(*args):
'''Main entry point for the application.'''
global root
root = tk.Tk()
root.protocol( 'WM_DELETE_WINDOW' , root.destroy)
# Creates a toplevel widget.
global _top1, _w1
_top1 = root
_w1 = myGui.Toplevel1(_top1)
root.mainloop()
if __name__ == '__main__':
myGui.start_up()
def StartButton_CallBack():
_w1.DebugLabel["text"] = 'START BUTTON PRESSED'
def StopButton_CallBack():
_w1.DebugLabel["text"] = 'STOP BUTTON PRESSED'
Once I run the myGui in Spyder, an Attribute error occurres:
AttributeError: module 'myGui_support' has no attribute 'StartButton_Callback'
How can it be possible? I already defined the callback function into the 'myGui_support.py'. Any suggestions?
I am creating a gui application using Tkinter, which imports other pieces of code stored in external .py files which contain time consuming functions. What I want to do is have a progressbar on my gui window which gets updated according to some function running in my imported script.
Gui script example:
#gui script
import tkinter
from tkinter import ttk
from somefile import somefunc
progcomp = ttk.Progressbar(root, orient='horizontal', length=200, mode = 'determinate', maximum=100)
somefunc()
External function example:
#somefile.py
def somefunc():
for i in range(1000):
#dosomething
#update progressbar of gui script
My actual code is too long to show in a question like this, so I chose to represent it as simply as possible. My question is, is this possible or will I have to change the infrastructure to accomplish?
You could use threading to implement this. Below is a very rough example of this.
threaded_task.py
import threading
import time
class ThreadedTask(threading.Thread):
def __init__(self, progress):
threading.Thread.__init__(self)
self.progress = progress
def run(self):
for i in range(100):
self.progress.step(1) # Update progress bar
time.sleep(1) # Simulate long running process
main.py
from tkinter import *
from tkinter import ttk
from threaded_task import ThreadedTask
root = Tk()
progcomp = ttk.Progressbar(root, orient='horizontal', length=200, mode = 'determinate', maximum=100)
progcomp.grid()
task = ThreadedTask(progcomp)
task.start()
root.mainloop()
I've heard that threads in Python are not easy to handle and they become more tangled with tkinter.
I have the following problem. I have two classes, one for the GUI and another for an infinite process. First, I start the GUI class and then the infinite process' class. I want that when you close the GUI, it also finishes the infinite process and the program ends.
A simplified version of the code is the following:
import time, threading
from tkinter import *
from tkinter import messagebox
finish = False
class tkinterGUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
class InfiniteProcess(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
while not finish:
print("Infinite Loop")
time.sleep(3)
GUI = tkinterGUI()
GUI.start()
Process = InfiniteProcess()
Process.start()
When I click in the close button (in the upper right corner) the following error appears in the console:
Tcl_AsyncDelete: async handler deleted by the wrong thread
I don't know why it happens or what it means.
All Tcl commands need to originate from the same thread. Due to tkinter's
dependence on Tcl, it's generally necessary to make all tkinter gui statements
originate from the same thread. The problem occurs because
mainWindow is instantiated in the tkinterGui thread, but -- because mainWindow is an attribute of tkinterGui -- is not destroyed until tkinterGui is destroyed in the main thread.
The problem can be avoided by not making mainWindow an attribute of tkinterGui
-- i.e. changing self.mainWindow to mainWindow. This allows mainWindow to be destroyed when the run method ends in the tkinterGui thread. However, often you can avoid threads entirely by using mainWindow.after calls instead:
import time, threading
from tkinter import *
from tkinter import messagebox
def infinite_process():
print("Infinite Loop")
mainWindow.after(3000, infinite_process)
mainWindow = Tk()
mainWindow.geometry("200x200")
mainWindow.title("My GUI Title")
lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
mainWindow.after(3000, infinite_process)
mainWindow.mainloop()
If you want to define the GUI inside a class, you can still do so:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def tkinterGui():
global finish
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
GUI = threading.Thread(target=tkinterGui)
GUI.start()
Process = threading.Thread(target=InfiniteProcess)
Process.start()
GUI.join()
Process.join()
or even simpler, just use the main thread to run the GUI mainloop:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
Process = threading.Thread(target=InfiniteProcess)
Process.start()
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
Process.join()
The fix here is simple, but hard to discover:
Call mainWindow.quit() immediately after mainwindow.mainloop(), so that the cleanup happens on the same thread as the one that created the tk UI, rather than on the main thread when python exits.