I am building a small python program that is waiting for input from a bluetooth device. Depending on the input of
the device I want to update my
GUI.
My decision was TkInter,
the de-facto standard GUI for Python. But my main problem was the blocking method mainloop
.
The method mainloop
has an important role for TkInter, it is waiting for events and updating
the GUI. But this method is blocking the code after it. You have a conflict, if the core of your application has
also a blocking loop that is waiting for some events. In my case waiting for input from a bluetooth device.
Let us assume the following small example with the raw_input
instead of bluetooth. We have a
minimal TkInter GUI and a while loop that will just close if you type in exit
. In all other
input cases we want to modify the GUI depending on the user input.
from Tkinter import *
ROOT = Tk()
LABEL = Label(ROOT, text="Hello, world!")
LABEL.pack()
ROOT.mainloop()
LOOP_ACTIVE = True
while LOOP_ACTIVE:
USER_INPUT = raw_input("Give me your command! Just type \"exit\" to close: ")
if USER_INPUT == "exit":
LOOP_ACTIVE = False
else:
LABEL = Label(ROOT, text=USER_INPUT)
LABEL.pack()
My naive imagination of this program was something like a passive GUI that can be controlled by the bash input. But
the result was something else. The GUI opened and the bash was not asking for a command. As I closed the GUI the
bash prompted Give me your command! Just type "exit" to close:
, I was answering with
Hello Gordon!
, but the result was error, cause the GUI was already closed.
python tkexample.py
Give me your command! Just type "exit" to close: Hello Gordon!
Traceback (most recent call last):
File "tkexample.py", line 13, in <module>
LABEL.pack()
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1939, in pack_configure
+ self._options(cnf, kw))
_tkinter.TclError: can't invoke "pack" command: application has been destroyed
There are three possible solutions in my eyes, the after
method, a thread and using method
update
in my own loop. All three solutions are mentioned as possible solutions in the following
question on stackoverflow.com.
I will try all three solutions on the minimal example from above.
The after
method is just taken a delay and a callback method.
from Tkinter import *
ROOT = Tk()
def ask_for_userinput():
user_input = raw_input("Give me your command! Just type \"exit\" to close: ")
if user_input == "exit":
ROOT.quit()
else:
label = Label(ROOT, text=user_input)
label.pack()
ROOT.after(0, ask_for_userinput)
LABEL = Label(ROOT, text="Hello, world!")
LABEL.pack()
ROOT.after(0, ask_for_userinput)
ROOT.mainloop()
The example with the after
method worked pretty well. It was possible to communicate with the bash and
to update the GUI with the input of the bash.
python tkexample.py
Give me your command! Just type "exit" to close: Hello Gordon!
Give me your command! Just type "exit" to close: Hello TkInter!
Give me your command! Just type "exit" to close: exit
We will use threading.Thread
to get the assumed behavior of our small application. The following small
application with a thread has the same behavior as the one with the after
method from above.
from Tkinter import *
import threading
class App(threading.Thread):
def __init__(self, tk_root):
self.root = tk_root
threading.Thread.__init__(self)
self.start()
def run(self):
loop_active = True
while loop_active:
user_input = raw_input("Give me your command! Just type \"exit\" to close: ")
if user_input == "exit":
loop_active = False
self.root.quit()
self.root.update()
else:
label = Label(self.root, text=user_input)
label.pack()
ROOT = Tk()
APP = App(ROOT)
LABEL = Label(ROOT, text="Hello, world!")
LABEL.pack()
ROOT.mainloop()
The third solution is just without the mainloop
method. You may have seen the update
method in the example above. The following solution behaves like the two other solutions. But the one without
mainloop
is my favourite, cause the difference to the original idea is just one row. But there is a
huge disadvantage, the handling of events in TkInter is not working anymore if our own loop is not calling
update
often enough. In my case no problem, cause all I want is to show some pictures that are only
waiting for bluetooth input.
from Tkinter import *
ROOT = Tk()
LABEL = Label(ROOT, text="Hello, world!")
LABEL.pack()
LOOP_ACTIVE = True
while LOOP_ACTIVE:
ROOT.update()
USER_INPUT = raw_input("Give me your command! Just type \"exit\" to close: ")
if USER_INPUT == "exit":
ROOT.quit()
LOOP_ACTIVE = False
else:
LABEL = Label(ROOT, text=USER_INPUT)
LABEL.pack()