简体   繁体   English

如何通过HTTP API控制Python GUI

[英]How to control a Python GUI via HTTP API

I want a Python program that implements an HTTP API (eg using Flask) on which it can receive messages to show various windows on the screen (eg using tkinter). 我想要一个实现HTTP API(例如,使用Flask)的Python程序,在该程序上它可以接收消息以在屏幕上显示各种窗口(例如,使用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. 我相信我将需要两个单独的线程:一个用于绘制tkinter窗口,另一个用于侦听HTTP请求。

say, I want to send an http request to eg /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. 例如,我想将http请求发送到例如/ show_window,然后显示一个窗口并将其保留在屏幕上,直到将请求发送到/ hide_window,然后关闭该窗口。

I can draw the window just fine via tkinter. 我可以通过tkinter绘制窗口。 But if I put this in a Flask route, of course it gets stuck on window.mainloop(). 但是,如果我把它放在Flask路由中,则当然会卡在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. 我在想我需要两个线程:一个运行Flask的线程+一个启动窗口的线程,然后flask线程需要将消息发送到窗口线程以显示,隐藏,创建,销毁窗口等。但是我不太确定该怎么做。

Note, it is in no way a requirement to use Flask or tkinter. 注意,绝不是使用Flask或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. 对于API的简单Web框架和创建GUI窗口的简单方法来说,这些工具似乎都很不错。

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. 它涉及在单独的线程中启动Flask应用程序,然后使用诸如threading.Event东西与前台GUI线程进行通信,或者使用threading.Lock来控制对共享数据结构的访问。

Starting a Flask app in a thread is straightforward, and looks something like 在线程中启动Flask应用非常简单,看起来像

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.) (这是“自旋锁”方法。签入threading.Event以获取其他方法。)

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 Flask一侧(在yourapp.py )的作用类似于

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. 从Flask端停止应用程序要比仅在共享控件上调用.stop()稍微麻烦一些。 See here for the code to do that. 请参阅此处的代码以执行此操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM