Reputation: 693
I want a Python program that implements an HTTP API (e.g. using Flask) on which it can receive messages to show various windows on the screen (e.g. using tkinter).
What is a good way of structuring such a program? I believe I will need two separate threads: one for drawing the tkinter windows and one for listening for HTTP requests.
say, I want to send an http request to e.g. /show_window, then a window is shown and kept on screen until a request is sent to /hide_window, and the window is then closed.
I can draw the window just fine via tkinter. But if I put this in a Flask route, of course it gets stuck on window.mainloop().
import tkinter as tk
from flask import Flask
app = Flask(__name__)
@app.route("/show")
def show():
root = tk.Tk()
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
root.attributes('-alpha', 0.0) #For icon
root.iconify()
window = tk.Toplevel(root)
window.geometry("%sx%s" % (screen_width, screen_height))
window.configure(background='black', cursor='none')
window.overrideredirect(1)
window.attributes('-topmost', 1)
close = tk.Button(window, text = "Close Window", command = lambda: root.destroy())
close.pack(fill = tk.BOTH, expand = 0)
window.mainloop() # app is stuck here until gui window is closed
return "show!"
@app.route("/hide")
def hide():
### Code to destroy or hide the Window here.
return "hide"
I am thinking I need something like two threads: One that runs Flask + one that starts up the window and then the flask thread needs to send messages to the window thread in order to show, hide, create, destroy, windows, etc. But I am not really sure how to do that.
Note, it is in no way a requirement to use Flask or tkinter. This is just the tools that seemed good for a simple web framework for the API and a simple way of creating GUI windows.
Upvotes: 0
Views: 2225
Reputation: 24966
You will indeed need separate threads.
Here's an approach that's worked for me. It involves starting the Flask app in a separate thread, and then using something like threading.Event
to communicate with the foreground GUI thread, or threading.Lock
to control access to shared data structures.
Starting a Flask app in a thread is straightforward, and looks something like
import threading
import time
from yourapp import app
def webserver(shared_state):
app.config['SHARED'] = shared_state
# It isn't safe to use the reloader in a thread
app.run(host='127.0.0.1', debug=True, use_reloader=False)
def main():
shared_state = SharedState()
ui_thread = threading.Thread(target=webserver, args=(shared_state,))
ui_thread.start()
while shared_state.running():
time.sleep(0.1)
if shared_state.button_clicked():
# do your Tk popup here
ui_thread.join()
if __name__ == '__main__':
main()
(This is the 'spin lock' approach. Check into threading.Event
for a different approach.)
The interesting bit is the shared state object, which uses a threading lock to serialize access to shared data (a click counter, in this example)
class SharedState:
def __init__(self):
self._lock = threading.Lock()
self._running = True
self._click_count = 0
def record_click(self):
# this gets called from the Flask thread to record a click
with self._lock:
self._click_count += 1
def clicked(self):
# this gets called from the GUI thread to 'get' a click
with self._lock:
if self._click_count > 0:
self._click_count -= 1
return True
return False
def stop(self):
# called from either side to stop running
with self._lock:
self._running = False
The Flask side (in yourapp.py
) does something like
app = Flask(__name__)
@app.route('/')
def home():
if request.method == 'POST':
app.config['SHARED'].record_click()
return render_response('index.html')
Stopping the app from the Flask side is a bit trickier than just calling .stop()
on the shared control. See here for the code to do that.
Upvotes: 1