Running a Tkinter Form in a Separate Thread

Running a Tkinter form in a separate thread

From your comments it sound's like you do not need a GUI at all. Just write the image to disk and call an external viewer.

On most systems it should be possible to launch the default viewer using something like this:

import subprocess 

subprocess.Popen("yourimage.png")

How to set tkinter textvariable running on separate Thread?

Most GUIs don't like to change values in widgets in separate thread.

You should rather use queue to send value to thread and it should use root.after(time, function) to run periodically function which will get value from queue and update value in GUI.

import tkinter as tk  # PEP8: `import *` is not preferred
from threading import Thread
import queue
import time # simulate show program

class GUI(Thread):

def __init__(self, queue):
super().__init__()
self.queue = queue
self.start()

def run(self):
self.root = tk.Tk()
self.var = tk.StringVar()
self.var.set("Initiated")

screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()

width = int(screen_width*.12)
height = int(screen_height)
x = int(screen_width - width)
y = int(screen_height*.025)

self.root.geometry(f'{width}x{height}+{x}+{y}')

label = tk.Label(self.root, textvariable=self.var)
label.pack()

# run first time after 100ms (0.1 second)
self.root.after(100, self.check_queue)

self.root.mainloop()

def check_queue(self):
#if not self.queue.empty():
while not self.queue.empty():
i = self.queue.get()
self.var.set(f'Current Iteration: {i}')

# run again after 100ms (0.1 second)
self.root.after(100, self.check_queue)

def main():
q = queue.Queue()
gui = GUI(q)

for i in range(1000):
q.put(i)
# simulate show program
time.sleep(0.5)

if __name__ == '__main__':
main()

PEP 8 -- Style Guide for Python Code

Why httpserver won't on separate thread with tkinter

I found two mistakes

  1. you forgot import threading so it gives error message

    import threading
  2. you created thread but you forgot to start it

    t = threading.Thread(target=webServer.serve_forever, daemon=True)
    t.start()

BTW:

You could better organize code.

See: PEP 8 --Style Guide for Python Code

import threading
import tkinter as tk # PEP8: `import *` is not preferred
from http.server import BaseHTTPRequestHandler, HTTPServer

# --- classes ---

class HttpHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
self.wfile.write(bytes("<body>", "utf-8"))
self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
self.wfile.write(bytes("</body></html>", "utf-8"))

# --- functions ---

# empty

# --- main ---

webServer = HTTPServer(('localhost', 9080), HttpHandler)
print('running http server: http://localhost:9080') # some consoles will display URL as clickable so it is easier to run browser
t = threading.Thread(target=webServer.serve_forever, daemon=True)
t.start()

window = tk.Tk()

#window.attributes("-fullscreen", True)
window.config(cursor="none")

#winWidth = int(window.winfo_screenwidth() * 1)
#winHeight = int(window.winfo_screenheight() * 1)
#window.geometry(f"{winWidth}x{winHeight}")

window.mainloop()

I only wondering if using http.server is good idea. If you want to access by web page then it can be simpler to create pages with Flask. If you want to send small commands then maybe it would be simpler use server MQTT
instead of HTTP. Some IoT devices my already use MQTT


Other problem can make communications between threads. Tkinter doesn't like to run in subthreads so you can't access widget directly in thread with server and it will need queue to send values to main thread and tkinter will need after(millisecond, function) to check queue periodically to get command.


EDIT:

Version which uses queue to send information from http server to tkinter and it displays it in widget Text

import threading
import tkinter as tk # PEP8: `import *` is not preferred
from http.server import BaseHTTPRequestHandler, HTTPServer
import queue

# --- classes ---

class HttpHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
self.wfile.write(bytes("<body>", "utf-8"))
self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
self.wfile.write(bytes("</body></html>", "utf-8"))

q.put("Request: %s" % self.path) # put in `queue`

# --- functions ---

def check_queue():
if not q.empty():
text.insert('end', q.get()+'\n') # get from `queue` and put in `Text`
window.after(100, check_queue) # check again after 100ms

# --- main ---

q = queue.Queue()

webServer = HTTPServer(('localhost', 9080), HttpHandler)
print('running http server: http://localhost:9080') # some consoles will display URL as clickable so it is easier to run browser
t = threading.Thread(target=webServer.serve_forever, daemon=True)
t.start()

window = tk.Tk()

text = tk.Text(window)
text.pack()

check_queue()

window.mainloop()

Sample Image


EDIT:

The same with Flask. It can get data: args, json, form, files, etc.

import queue
import threading
import tkinter as tk # PEP8: `import *` is not preferred
from flask import Flask, request, render_template_string

# --- classes ---


# --- functions ---

app = Flask(__name__)

#@app.route('/')
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):

print('path:', path)
print(f'Request: {request.method} {request.url}')
print('args:', request.args)
print('form:', request.form)
print('data:', request.data)
print('json:', request.json)
print('files:', request.files)

q.put(f'Request: {request.method} {request.url}') # put in `queue`

return render_template_string('''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>https://pythonbasics.org</title>
</head>
<body>
<p>Request: {{ request.method }} {{ request.url }}</p>
<p>This is an example web server.</p>
</body>
</html>''', request=request)

def check_queue():
if not q.empty():
text.insert('end', q.get()+'\n') # get from `queue` and put in `Text`
window.after(100, check_queue) # check again after 100ms

# --- main ---

q = queue.Queue()

print('running http server: http://localhost:9080') # some consoles will display URL as clickable so it is easier to run browser
t = threading.Thread(target=app.run, args=('localhost', 9080), daemon=True)
t.start()

window = tk.Tk()

text = tk.Text(window)
text.pack()

check_queue()

window.mainloop()

But now question is: why to use tkinter if you may do all in flask

multithreading from a tkinter app

I'm not too familiar with tkinter so i can't show you ze code, but you should look at tkinter's event system, particularly firing custom events.

This question may help you get started

Update Tkinter GUI from a separate thread running a command

In tkinter, you can submit an event from a background thread to the GUI thread using event_generate. This allows you to update widgets without threading errors.

  1. Create the tkinter objects in the main thread
  2. Bind the root to a vitual event (ie << event1 >>), specifying an event handler. Arrow brackets are required in the event name.
  3. Start the background thread
  4. In the background thread, use event_generate to trigger the event in the main thread. Use the state property to pass data (number) to the event.
  5. In the event handler, process the event

Here's an example:

from tkinter import *
import datetime
import threading
import time

root = Tk()
root.title("Thread Test")
print('Main Thread', threading.get_ident()) # main thread id

def timecnt(): # runs in background thread
print('Timer Thread',threading.get_ident()) # background thread id
for x in range(10):
root.event_generate("<<event1>>", when="tail", state=123) # trigger event in main thread
txtvar.set(' '*15 + str(x)) # update text entry from background thread
time.sleep(1) # one second

def eventhandler(evt): # runs in main thread
print('Event Thread',threading.get_ident()) # event thread id (same as main)
print(evt.state) # 123, data from event
string = datetime.datetime.now().strftime('%I:%M:%S %p')
lbl.config(text=string) # update widget
#txtvar.set(' '*15 + str(evt.state)) # update text entry in main thread

lbl = Label(root, text='Start') # label in main thread
lbl.place(x=0, y=0, relwidth=1, relheight=.5)

txtvar = StringVar() # var for text entry
txt = Entry(root, textvariable=txtvar) # in main thread
txt.place(relx = 0.5, rely = 0.75, relwidth=.5, anchor = CENTER)

thd = threading.Thread(target=timecnt) # timer thread
thd.daemon = True
thd.start() # start timer loop

root.bind("<<event1>>", eventhandler) # event triggered by background thread
root.mainloop()
thd.join() # not needed

Output (note that the main and event threads are the same)

Main Thread 5348
Timer Thread 33016
Event Thread 5348
......

I added an Entry widget to test if the StringVar can be updated from the background thread. It worked for me, but you can update the string in the event handler if you prefer. Note that updating the string from multiple background threads could be a problem and a thread lock should be used.

Note that if the background threads exits on its own, there is no error. If you close the application before it exits, you will see the 'main thread' error.

Tkinter is opening new windows when running a function in a thread

Since the child process will inherit resource from parent process, that means it will inherit tkinter from parent process. Put the initialization of tkinter inside if __name__ == '__main__' block may solve the problem:

from tkinter import *
from multiprocessing import Process
import time

def threadFunction():
print('started')
time.sleep(5)
print('done')

def start():
thread1 = Process(target=threadFunction)
thread2 = Process(target=threadFunction)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

if __name__ == '__main__':
window = Tk()
window.title("Test threadinng")
window.geometry('400x400')
btn = Button(window, text="Click Me", command=start)
btn.grid(column=1, row=1)
window.mainloop()


Related Topics



Leave a reply



Submit