For very simple applications with limited GUIs, we can design the interface from inside the application source code. Once the GUI becomes more complex, this solution is not acceptable and we need a tool to support us in the GUI design. One of the most well-known tools for this activity in wxWidgets is wxGlade.
wxGlade is an interface design program written in Python using wxPython, and this allows it to run on all platforms where these two are available.
The philosophy is similar to Glade, the famous GTK+ GUI designer, and the look and feel are very similar as well. wxGlade is a program that helps us to create wxWidgets or wxPython user interfaces, but it is not a full-featured code editor; it's just a designer, and the code it generates does nothing more than display the created widgets.
Although project Phoenix and wxPython 4 are relatively new, they are both supported by wxGlade. wxGlade can be downloaded from sourceforge, and one can easily download the zipped file, unzip it, and run wxGlade with the python3 command:
python3 wxglade.py
And here comes the user interface!
Here is a breakdown of the three main windows in Figure 5. The upper left window is the main Palette window. The first button of the first row (Windows) is the button to create a frame as a basis of everything. The lower left window is the Properties window, which lets us display and edit the properties of applications, windows, and controls. The window on the right is the Tree window. It enables us to visualize the structure. It allows editing the structure of the project, with its application, windows, sizers, and controls. By choosing an item in the Tree window, we can edit its corresponding properties in the Properties window.
Let us click on the button to Add a Frame. This will be followed by this small window:
Select the base class as wxFrame; and here we will generate a Design window as shown in the following screenshot. From here, we can start to click and add buttons and features that we like:
Let us first create a container for the code we have shown previously. Before we click on any buttons, let us revisit the essential elements needed for the preceding GUI:
- Frame
- Vertical box (wx.BoxSizer)
- Button
So it's very straightforward; let's click on the sizer button in the Palette window and then click on the Design window:
From the Tree window, we can see the structure of our GUI. We have a frame containing a sizer of two slots. However, we want a vertical box instead of a horizontal one; this can be modified in the Properties window when we click on sizer_2:
Now let's add a button to slot 2! This can be done by simply clicking on Button in the Palette window, then clicking on the slot in the lower part of the Design window. However, the button doesn't look very nice from there. It's on the left-hand side of the lower panel. This can be altered by selecting _button1 in the Tree window and modifying the alignment details in the Layout tab of the Properties window.
In here, we have selected wxEXPAND and wxALIGN_CENTER, which indicate that it has to expand and fill the width of the frame; this also ensures that it aligns at the center of the slot at all times:
For now, the frame is all set. Let's export the code by choosing File and Generate Code:
Upon clicking on Generate Code, a file will be saved in the desired folder (where the user saved the wxWidget file) and here is a code snippet:
#!/usr/bin/env python # -*- coding: UTF-8 -*- # # generated by wxGlade 0.8.0 on Sun Apr 8 20:35:42 2018 # import wx # begin wxGlade: dependencies # end wxGlade # begin wxGlade: extracode # end wxGlade class MyFrame(wx.Frame): def __init__(self, *args, **kwds): # begin wxGlade: MyFrame.__init__ kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) self.SetSize((500, 550)) self.button_1 = wx.Button(self, wx.ID_ANY, "button_1") self.__set_properties() self.__do_layout() # end wxGlade def __set_properties(self): # begin wxGlade: MyFrame.__set_properties self.SetTitle("frame") # end wxGlade def __do_layout(self): # begin wxGlade: MyFrame.__do_layout sizer_2 = wx.BoxSizer(wx.VERTICAL) sizer_2.Add((0, 0), 0, 0, 0) sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER | wx.EXPAND, 0) self.SetSizer(sizer_2) self.Layout() # end wxGlade # end of class MyFrame class MyApp(wx.App): def OnInit(self): self.frame = MyFrame(None, wx.ID_ANY, "") self.SetTopWindow(self.frame) self.frame.Show() return True # end of class MyApp if __name__ == "__main__": app = MyApp(0) app.MainLoop()
The preceding code provides the GUI on its own. However, it lacks some key functions to make everything work. Let's quickly expand on that to see how it works:
import matplotlib matplotlib.use('WXAgg') import wx, numpy, matplotlib.cm as cm, matplotlib.pyplot as plt from numpy import arange, sin, pi, random, linspace from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas from matplotlib.backends.backend_wx import NavigationToolbar2Wx from matplotlib.figure import Figure class MyFrame(wx.Frame): def __init__(self, *args, **kwds): # begin wxGlade: MyFrame.__init__ kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) self.SetSize((500, 550)) self.button_1 = wx.Button(self, wx.ID_ANY, "button_1")
##Code being added*** self.Bind(wx.EVT_BUTTON, self.__updat_fun, self.button_1) #Setting up the figure, canvas and axes self.fig = Figure(figsize=(5,5), dpi=100) self.canvas = FigureCanvas(self, -1, self.fig) self.ax = self.fig.add_subplot(111, projection='polar') ##End of Code being added***self.__set_properties() self.__do_layout() # end wxGlade def __set_properties(self): # begin wxGlade: MyFrame.__set_properties self.SetTitle("frame") # end wxGlade def __do_layout(self): # begin wxGlade: MyFrame.__do_layout sizer_2 = wx.BoxSizer(wx.VERTICAL) sizer_2.Add(self.canvas, 0, wx.ALIGN_CENTER|wx.ALL, 1) sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER | wx.EXPAND, 0) self.SetSizer(sizer_2) self.Layout() # end wxGlade
##The udpate_fun that allows the figure to be updated upon clicking ##The __ in front of the update_fun indicates that it is a private function in Python syntax def __updat_fun(self,event): self.ax.cla() self.ax = self.fig.add_subplot(111, projection='polar') #Here, we borrow one example shown in the matplotlib gtk3 cookbook #and show a beautiful bar plot on a circular coordinate system self.theta = linspace(0.0, 2 * pi, 30, endpoint=False) self.radii = 10 * random.rand(30) self.plot_width = pi / 4 * random.rand(30) self.bars = self.ax.bar(self.theta, self.radii, width=self.plot_width, bottom=0.0) #Here defines the color of the bar, as well as setting it to be transparent for r, bar in zip(self.radii, self.bars): bar.set_facecolor(cm.jet(r / 10.)) bar.set_alpha(0.5) self.fig.canvas.draw() print('Updating figure!') # end of class MyFrame
class MyApp(wx.App): def OnInit(self): self.frame = MyFrame(None, wx.ID_ANY, "") self.SetTopWindow(self.frame) self.frame.Show() return True # end of class MyApp if __name__ == "__main__": app = MyApp(0) app.MainLoop()