In the previous recipes, one of our main challenges was how to combine two GUI technologies that were designed to be the one-and-only GUI toolkit for an application. We found various simple ways to combine them.
We will again launch the wxPython GUI from a tkinter GUI main event loop and start the wxPython GUI in its own thread, which runs within the Python.exe process.
In order to do this, we will use a shared global multiprocessing Python Queue.
Here is the Python code that makes the two GUIs communicate with each other to a certain degree. In order to save space, this is not pure OOP code. Neither are we showing the creation code for all widgets. That code is the same as in the previous recipes:
# Communicate.py
import tkinter as tk
from tkinter import ttk
from threading import Thread
win = tk.Tk()
win.title("Python GUI")
from queue import Queue
sharedQueue = Queue()
dataInQueue = False
def putDataIntoQueue(data):
global dataInQueue
dataInQueue = True
sharedQueue.put(data)
def readDataFromQueue():
global dataInQueue
dataInQueue = False
return sharedQueue.get()
#===========================================================
import wx
class GUI(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
parent.CreateStatusBar()
button = wx.Button(self, label="Print", pos=(0,60))
self.Bind(wx.EVT_BUTTON, self.writeToSharedQueue, button)
#--------------------------------------------------------
def writeToSharedQueue(self, event):
self.textBox.AppendText("The Print Button has been clicked!n")
putDataIntoQueue('Hi from wxPython via Shared Queue.n')
if dataInQueue:
data = readDataFromQueue()
self.textBox.AppendText(data)
text.insert('0.0', data) # insert data into tkinter GUI
#============================================================
def wxPythonApp():
app = wx.App()
frame = wx.Frame(None, ,
size=(300,180))
GUI(frame)
frame.Show()
runT = Thread(target=app.MainLoop)
runT.setDaemon(True)
runT.start()
print(runT)
print('createThread():', runT.isAlive())
#============================================================
action = ttk.Button(win, text="Call wxPython GUI", command=wxPythonApp)
action.grid(column=2, row=1)
#======================
# Start GUI
#======================
win.mainloop()
Running the preceding code first creates the tkinter part of the program and, when we click the button in this GUI, it runs the wxPython GUI. Both are running at the same time as before but, this time, there is an extra level of communication between the two GUIs:
Communicate.py
The tkinter GUI is shown on the left-hand side in the preceding screenshot, and by clicking the Call wxPython GUI button, we invoke an instance of the wxPython GUI. We can create several instances by clicking the button several times.
Clicking the Print button on any of the wxPython GUI instances writes one sentence to its own TextCtrl widget and then writes another line to itself, as well as to the tkinter GUI. You will have to scroll up to see the first sentence in the wxPython GUI.
One important element to note is that we create a thread to run the wxPython app.MainLoop, as we did in the previous recipe:
def wxPythonApp():
app = wx.App()
frame = wx.Frame(None, ,
size=(300,180))
GUI(frame)
frame.Show()
runT = Thread(target=app.MainLoop)
runT.setDaemon(True)
runT.start()
We create a class which inherits from wx.Panel and name it GUI and then instantiate an instance of this class in the preceding code.
We create a button-click event callback method in this class, which then calls the procedural code that was written above it. Because of this, the class has access to the functions and can write to the shared queue:
#------------------------------------------------------
def writeToSharedQueue(self, event):
self.textBox.AppendText("The Print Button has been clicked!n")
putDataIntoQueue('Hi from wxPython via Shared Queue.n')
if dataInQueue:
data = readDataFromQueue()
self.textBox.AppendText(data)
text.insert('0.0', data) # insert data into tkinter
We first check whether the data has been placed in the shared queue in the preceding method and, if that is the case, print the common data to both GUIs.
The following are the procedural functions (not methods, for they are not bound) which are being called in the code and make it work:
from multiprocessing import Queue
sharedQueue = Queue()
dataInQueue = False
def putDataIntoQueue(data):
global dataInQueue
dataInQueue = True
sharedQueue.put(data)
def readDataFromQueue():
global dataInQueue
dataInQueue = False
return sharedQueue.get()
We used a simple Boolean flag named dataInQueue to communicate when the data is available in the queue.