Substitute for tkinter.dooneevent

2020-04-21 00:18发布

问题:

I am porting a program (VMD, Visual Molecular Dynamics), which is written in C++ and has both Python and TCL interpreters embedded, to Python 3.x. Most of its UI is hard coded using the TCL/TK framework and OpenGl, so UI refreshs are done manually. When the Python interpreter is running it is possible to dynamically create new windows and even add new menus to the main UI using Tkinter. In this case all TK events are flushed by periodically calling some code in the Python side (see below). This ensures that all updates are thread-safe and don't break the interpreter.

int PythonTextInterp::doTkUpdate() {
    // Don't recursively call into dooneevent - it makes Tkinter crash for
    // some infathomable reason.
    if (in_tk) return 0;
    if (have_tkinter) {
        in_tk = 1;
        int rc = evalString(
          "import Tkinter\n"
          "while Tkinter.tkinter.dooneevent(Tkinter.tkinter.DONT_WAIT):\n"
          "    pass\n"
        );
        in_tk = 0;
        if (rc) {
            return 1; // success
        }
        // give up
        have_tkinter = 0;
    }
    return 0;
}

However the function tkinter.dooneevent was removed from Python 3 and I can not find a substitute for it. I tried calling the low-level Tcl_DoOneEvent(TCL_DONT_WAIT) but when I dynamically created a new window I ended up crashing the Python interpreter with the error Fatal Python error: PyEval_RestoreThread: NULL tstate.

The answers in tkinter woes when porting 2.x code to 3.x, 'tkinter' module attribute doesn't exist doesn't help since I don't have a list of all windows that may created by the user.

Does anyone have any suggestion on how to flush the TK events in this case? It could be either on the Python side or in C++.

Thanks in advance

回答1:

It looks like this is equivalent:

root = tkinter.Tk()

# Here's your event handler. Put it in a loop somewhere.
root.tk.dooneevent(tkinter._tkinter.DONT_WAIT)
# I don't know if it's possible to access this method without a Tk object.

Now, I don't know how exactly to convert this into your code- do you have a root Tk object with which you can access dooneevent? I'm not at all familiar with python 2 tkinter so I don't know exactly how evenly my code maps to yours. However, I discovered this when I was doing something very similar to you- trying to integrate the tkinter event loop into the asyncio event loop. I was able to create a coroutine that calls this method in a loop, yielding each time (and sleeping occasionally), so that the GUI remains responsive without blocking the asyncio event loop with tkinter._tkinter.create().

@asyncio.coroutine
def update_root(root):
    while root.tk.dooneevent(tkinter._tkinter.DONT_WAIT):
        yield

EDIT: I just read your comment about not having a widget. I know that the root.tk object is a tkinter._tkinter.TkappType instance created by calling tkinter._tkinter.create, and I don't think it's global. I'm pretty sure it's the core Tcl interpreter. You might be able to create your own by calling create. While it isn't documented, you can look at its usage in tkinter.Tk.__init__