Python: Attribute error when trying to access thread owned variable - multithreading

I create a simple window with tkinter and start it as thread to keep the main program running next to the window. This is a shortened version of it:
import tkinter as tk
import threading
class mainWindow(threading.Thread):
def __init__(self, winWidth=500, winHeight=300):
threading.Thread.__init__(self)
self.winWidth = winWidth
self.winHeight = winHeight
# Save all drawn objects, to move or delete them later
self.bricks = []
self.start() #start thread
def run(self):
# parent object for all windows
self.master = tk.Tk()
self.master.protocol("WM_DELETE_WINDOW", self.callback)
self.show()
def callback(self):
self.master.quit()
# Initialize everything important
def show(self, tileSize=10):
# create main window
self.w = tk.Canvas(
self.master,
width=self.winWidth,
height=self.winHeight,
background="white")
self.w.pack()
# draw brick
color = "gray49"
posX = 200
posY = 100
self.bricks.append(self.w.create_rectangle(posX, posY, posX+20, posY+20, fill=color))
tk.mainloop()
def move_brick(self, x,y):
self.w.move(self.brick, x, y)
mainWindow = mainWindow()
mainWindow.move_brick(100,100)
When I run the shown code the window opens correctly, but when I try to move the rectangle with move_brick(...) I get this error:
AttributeError: 'mainWindow' object has no attribute 'w'
Why can't the object find my Canvas w?

You probably have a race condition, which is common for threaded applications. The main thread is likely calling move_brick before the worker thread has a chance to create the widget.
You can probably see this happening if you add print statements right before and after creating the widget, and in your move_brick function.
Even if you fix this, this code likely won't work because all tkinter code needs to run in a single thread. Creating the GUI in one thread and calling move_brick in another thread is not the right way to use tkinter.

Related

Calling functions without closing the master window - Python Tkinter

I've tried to create a software. In this software there is a menu widget with function button that open functions e widgets. But, I've noticed, to keep going on script, it's necessary to close the master window (menu).
I've created an example to you understand my problem.
from tkinter import *
#Create Fuction that open new fuctions and widget
def test():
#Open a new widget
def fun_test_1():
top_level = Tk()
def test1():
top_level1 = Toplevel()
top_level1.title('new1')
top_level1.mainloop()
Button(top_level, text='test1',command=test1).pack()
top_level.mainloop()
fun_test_1()
#Before, open the second widget
def fun_test_2():
print('def fun_test_2(): works!')
top_level = Tk()
def test1():
top_level1 = Toplevel()
top_level1.title('new1')
top_level1.mainloop()
Button(top_level, text='Button', command=test1).pack()
top_level.mainloop()
fun_test_2()
root = Tk()
root.title('MASTER')
Button(root, text='Button',command=test).pack()
root.mainloop()
So, I need that fun_test_2() be called without close the root widget
And all functions i've tried to change Tk() to Toplevel() and Toplevel() to Tk().
The problem is you calling mainloop more than once, and for creating more than one instance of Tk. When you call mainloop, it won't return until that window has been destroyed. That's a fundamental aspect of how tkinter works.
The solution is to not create more than one instance of Tk and to not call mainloop more than once. If you need multiple windows, create instances of Toplevel. And again, only call mainloop once in total, not once per window.

How to open two modules in one window, Python Tkinter

I have a question: i am tossing the code from 2ch files, i have already lost ideas. Calling fileA.py opens a window for me with two buttons exit and start. Exit works but when I click start I need to open the second window fileB.pt. (I want both windows to open in one window) Seemingly works only problem I have is it doesn't open "window on window" but "docks underneath" and I have the effect of two windows open :/. Please help, thank you in advance:) Python 3.10
fileA.py
import tkinter as tk
from GUI.module.scale_of_img import get_scale
class FirstPage:
def __init__(self, root):
self.root = root
def get_settings(self):
# Window settings
self.root.title('....')
self.root.resizable(False, False)
self.root.geometry("1038x900")
if __name__ == '__main__':
first = FirstPage(tk.Tk())
first.get_run_first_page()
fileB.py
import tkinter as tk
"importy..."
''' The second side of the application '''
class SecondPage:
def __init__(self, root=None):
self.root = root
self.my_canvas = tk.Canvas(self.root, width=1038, height=678)
self.my_canvas.pack(fill="both", expand=True)
if __name__ == '__main__':
second = SecondPage(tk.Tk())
second.get_run()
in order to put two "windows" in the same "window" you need to put all items inside a Frame, which is basically a container than you can simply pack when you want everything to show and unpack when you want everything in it to disapear.
all items in the first window will be children of a frame and all items in the second window will be children of another frame, and to switch you just need to call pack_forget() on one and pack() on another.
for the first file
class FirstPage:
def __init__(self, root):
self.root = root
self.frame = tk.Frame(root)
self.frame.pack(expand=True)
def get_picture(self):
# all items inside this window must be children of self.frame
self.my_canvas = tk.Canvas(self.frame, width=1038, height=500)
...
def get_second_page(self):
from GUI.module.second_page import SecondPage
self.frame.pack_forget() # to hide first page
# self.frame.destroy() # if you are never brining it back
SecondPage(self.root).get_run()
and for the second file
class SecondPage:
def __init__(self, root=None):
self.root = root
self.frame = tk.Frame(root) # new frame
self.frame.pack(expand=True)
self.my_canvas = tk.Canvas(self.frame, width=1038, height=678)
self.my_canvas.pack(fill="both", expand=True)
def get_button(self):
# Add buttons
# all here should be children of self.frame now
button1 = tk.Button(self.frame, text="...", )
...
you could destroy the first frame when you switch over to save some resources if you don't intend to return to it ever again, but the difference in memory is negligible.
assuming what you want is another Tk window to open, you shouldn't give it the same root, instead use an instance of Toplevel
from tkinter import Toplevel
# class definition here
def get_second_page(self):
from GUI.module.second_page import SecondPage
SecondPage(Toplevel(self.root)).get_run()
passing the Toplevel as a child of self.root is necessary, but note that the two windows have different roots.
Edit: turns out this wasn't what the OP ment by "window on window" -_-, but it am keeping it here for other readers.

Working with the results of a thread without blocking tkinter

I have a minimal tkinter program which analyses some data. Some of the datafiles are quite large, so to ensure that the GUI remains responsive I load the data in a new thread.
How can I run analysis on the data once the thread has terminated?
Some example code is below.
import tkinter
from threading import Thread
from time import sleep
result = []
def func(result):
sleep(10)
ans = 1
result.append(ans)
class myApp(tkinter.Tk):
def __init__(self, parent):
tkinter.Tk.__init__(self, parent)
self.grid()
self.myButton = tkinter.Button(self, text="Press me!", command=self.onButtonPress)
self.myButton.grid(column=0, row=0)
def onButtonPress(self):
thread = Thread(target=func, args=(result,))
thread.start()
self.myButton["text"]=result
app = myApp(None)
app.mainloop()
How can I make the button text change only when func returns?
multiprocessing has an isalive() function and so you can use Tkinter's after() to query it periodically if you want to switch over

Python 3.5 tkinter confirmation box created progress bar and reads in csv not quite working

I'm realtively new to python and am making a GUI app that does a lot of file i/o and processing. To complete this i would like to get a confirmation box to pop-up when the user commits and actions. From this when clicking 'yes' the app then runs the i/o and displays a progress bar.
From other threads on here I have gotten as far as reading about the requirement to create an addtional thread to take on one of these processes (for example Tkinter: ProgressBar with indeterminate duration and Python Tkinter indeterminate progress bar not running have been very helpful).
However, I'm getting a little lost because I'm not activating the threaded process from the Main() function. So I'm still getting lost in how, and where, I should be creating the progress bar and passing of the i/o process to another thread (reading in a csv file here).
Here is my code and I would be very grateful for any help anyone can give me:
import tkinter as tk
import tkinter.messagebox as messagebox
import csv
import tkinter.ttk as ttk
import threading
class ReadIn(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Read in file and display progress")
self.pack(fill=tk.BOTH, expand=True)
self.TestBtn = tk.Button(self.parent, text="Do Something", command=lambda: self.confirm_pb())
self.TestBtn.pack()
def confirm_pb(self):
result = messagebox.askyesno("Confirm Action", "Are you sure you want to?")
if result:
self.handle_stuff()
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
nf.join()
def Pbar(self):
self.popup = tk.Tk()
self.popup.title('Loading file')
self.label = tk.Label(self.popup, text="Please wait until the file is created")
self.progressbar = ttk.Progressbar(self.popup, orient=tk.HORIZONTAL, length=200,
mode='indeterminate')
self.progressbar.pack(padx=10, pady=10)
self.label.pack()
self.progressbar.start(50)
def import_csv(self):
print("Opening File")
with open('csv.csv', newline='') as inp_csv:
reader = csv.reader(inp_csv)
for i, row in enumerate(reader):
# write something to check it reading
print("Reading Row " + str(i))
def main():
root = tk.Tk() # create a Tk root window
App = ReadIn(root)
root.geometry('400x300+760+450')
App.mainloop() # starts the mainloop
if __name__ == '__main__':
main()
The statement nf.join() in function handle_stuff() will block tkinter's main loop to show the progress bar window. Try modify handle_stuff() as below:
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
#nf.join() # don't call join() as it will block tkinter's mainloop()
while nf.is_alive():
self.update() # update the progress bar window
self.popup.destroy()

tkinter tkMessageBox not working in thread

i have tkinter class and some functions in it, (assume all other functions are present to initiate the GUI). what i have done i have started one self.function as a thread from other self.function and in threaded function upon error i want to use tkMessageBox.showerror('Some Error') but this does not work in threaded function and my program got stuck. msgbox is working in other function.
import threading
from Tkinter import *
import Pmw
import tkMessageBox
class tkinter_ui:
def __init__(self, title=''):
... assume all functions are present ...
def login(self, username, password)
if password == "":
tkMessageBox.showerror('Login Error', 'password required') # but on this msg box program become unresponsive why???
def initiateLogin(self)
tkMessageBox.showinfo('Thread', 'Started') #you see this msg box works
self.t = threading.Timer(1, self.login)
self.t.start()
Since I got stuck on the same problem and didn't find a proper, well explained solution, I'd like to share a basic strategy I came out with.
Note that this is not the only nor the best way to do threading with tkinter, but it's quite straightforward and should preserve your workflow if you designed your code without being aware of tkinter's thread-unsafetiness.
Why threads?
First of all, I chose to use threads seeing that blocking actions like os.popen, subprocess.call, time.sleep and the like would "freeze" the GUI until they run (of course this may not be your case since threads are useful by their own for many reasons and sometimes they are just needed).
This is how my code looked like before using threads:
from Tkinter import *
import tkMessageBox
from time import sleep
# Threadless version.
# Buttons will freeze the GUI while running (blocking) commands.
def button1():
sleep(2)
tkMessageBox.showinfo('title', 'button 1')
def button2():
sleep(2)
tkMessageBox.showinfo('title', 'button 2')
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=button1, text="Button 1").pack( side = LEFT )
Button(frame, command=button2, text="Button 2").pack( side = LEFT )
root.mainloop()
Buggy threaded version
Then I turned the commands called by the buttons into threads. This way, the GUI would not freeze.
I thought it was ok, but on Windows this code leads the interpreter to crash irreparably due to the tkMessageBoxes called from threads other than the one in which the tkinter's root is running:
from Tkinter import *
import tkMessageBox
from time import sleep
import threading
# Buggy threads.
# WARNING: Tkinter commands are run into threads: this is not safe!!!
def button1():
sleep(2)
tkMessageBox.showinfo('title', 'button 1')
def button2():
sleep(2)
tkMessageBox.showinfo('title', 'button 2')
def start_thread(fun, a=(), k={}):
threading.Thread(target=fun, args=a, kwargs=k).start()
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
root.mainloop()
Thread-safe version
When I discovered the thread-unsafetiness of tkinter, I wrote a small function tkloop that would run in the main thread each few milliseconds checking requests and executing requested (tkinter) functions on behalf of the threads that wish to run them.
The two keys here are the widget.after method that "registers a callback function that will be called after a given number of milliseconds" and a Queue to put and get requests.
This way, a thread can just put the tuple (function, args, kwargs) into the queue instead of calling the function, resulting in a unpainful change of the original code.
This is the final, thread-safe version:
from Tkinter import *
import tkMessageBox
from time import sleep
import threading
from Queue import Queue
# Thread-safe version.
# Tkinter functions are put into queue and called by tkloop in the main thread.
q = Queue()
def button1():
sleep(2)
q.put(( tkMessageBox.showinfo, ('title', 'button 1'), {} ))
def button2():
sleep(2)
q.put(( tkMessageBox.showinfo, ('title', 'button 2'), {} ))
def start_thread(fun, a=(), k={}):
threading.Thread(target=fun, args=a, kwargs=k).start()
def tkloop():
try:
while True:
f, a, k = q.get_nowait()
f(*a, **k)
except:
pass
root.after(100, tkloop)
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
tkloop() # tkloop is launched here
root.mainloop()
Edit: two-way communication: if your threads need to get informations from the main (e.g. return values from tkinter functions) you can edit the interface of tkloop adding a queue for the return values. Here's an example based on the code above:
def button1():
q1 = Queue()
sleep(2)
q.put(( tkMessageBox.askokcancel, ('title', 'question'), {}, q1 ))
response = 'user said ' + 'OK' if q1.get() else 'CANCEL'
q.put(( tkMessageBox.showinfo, ('title', response), {}, None ))
# ...
def tkloop():
try:
while True:
f, a, k, qr = q.get_nowait()
r = f(*a, **k)
if qr: qr.put(r)
except:
pass
root.after(100, tkloop)
tkinter is not thread safe -- you can't reliably call any tkinter functions from any thread other than the one in which you initialized tkinter.
If you want your other thread to block until you get response (e,g: you want to ask a question and wait for the answer) you can use this function:
def runInGuiThreadAndReturnValue(self, fun, *args, **kwargs):
def runInGui(fun, ret, args, kwargs):
ret.append(fun( *args, **kwargs))
ret = []
sleeptime = kwargs.pop('sleeptime', 0.5)
self.after(0, runInGui, fun, ret, args, kwargs)
while not ret:
time.sleep(sleeptime)
return ret[0]

Resources