Home |
Last modified: 18-05-2024 |
wxWidgets, formerly known as wxWindows, is an open-source, cross-platform framework in C++ which uses the target OS' native widgets to achieve native look and feel. wxPython is a thin layer above the wxWidgets framework, so loads faster than Tkinter. It also offers a rich set of widgets (random gallery), and seems easier to use, at the expense of having to install an extra package while Tkinter is part of the Python package. The name wxWidgets refers to the two original platforms w for Microsoft Windows and x for Unix X server.
Fundamentaly, a wxPython application is a combination of an app (that handles the event loop) and a frame (the actual window on the screen). The frame itself is has a hollow client area that requires a panel to fill that area and include widgets; a sizer is recommended to hold widgets and make it easy to position and (re)size them.
Phoenix/wxPython 4 is a rewrite of wxPython for Python3. Code for older versions of Python/wxPython might need to be edited accordingly. Since wxPython relies on Python, do not upgrade the latter unless you're sure it won't break the former. wxPython Project Phoenix Migration Guide provides information about how to convert classic wxPython to Phoenix.
As of 2024, the most up-to-date source of information is here, including an overview. Besides the wiki (including its "Getting started with wxPython"; a lot of recipes/code samples are in Python2 and pre-Phoenix) and the demo scripts (demo\demo.py; the demo seems to be the most complete and up-to-date list of widgets in the official distribution; the code can be simplified by removing the call to its run module), Michael Driscoll's "Creating GUI Applications with wxPython", published in 2020, seems the only book worth recommending today as it matches the "Project Phoenix" rewrite of wxPython, although it mostly shows samples instead of really explaining the toolkit. wxPython in Action was published in 2006 so the code must be tweaked slightly to work with Python3 and Phoenix but is probably still the best book to start with; "wxPython Application Development Cookbook" and "wxPython Recipes" have quite negative user comments but are useful when looking for specific examples after learning the gist of wxPython. Tutorialspoint and ZetCode's wxPython tutorial are a good start too.
Unlike wxWidgets, wxPython currently has no regular discussion forum, and the wxPython-users mailing list has been replaced with a discuss-type site à la StackOverflow.
Starting with a GUI builder and playing with the widgets is an easy way to learn wxPython by reading the code it generates. wxGlade is one of them — the (much) bigger ZIP is a ready-to-use package while the smaller one assumes Python and wxPython are pre-installed. wxFormBuilder is an alternative. Note that sizers being the recommended way to add widgets to a panel, GUI builders don't let you draw widgets on the screen à la VisualBasic etc.
If needed, start by installing Python. If you work under Windows, you can either install the standard release of Python, or ActiveState's version of Python (It includes Python as available on www.python.org, Windows-specific add-on's like access to the Win32 API and COM, along with a few other items like BerkeleyDB.) The Enthought version includes a lot of stuff that are probably not useful to most developers, but you might need those.
To install wxPython, simply, run "pip install wxpython"; It contains wxWidgets, so you don't need to download both. To check which version of Python is installed, open a terminal window, and type "python -V", and to check which version of wxPython is installed, run "python -c "import wx;print(wx.__version__)" "
If you choose to use UltraEdit to work with Python and want to add support
for syntax highlighting, wordfiles are available on the site for various versions
of Python. As alternate IDE's built for Python, take at look at Thonny
(to use an existing Python setup, select it in the drop-down list in Tools >
Options > Interpreter; (4.1.4) doesn't scroll in-place with CTRL+up/down),
Wing
IDE Pro (commercial), PyCharm (free/commercial;
The installer of the free version is already over 450MB),
Visual Studio Code (open source),
PyScripter (open
source; Written in Delphi).
FWIW, the so-called "Python IDE" that comes
with ActiveState is just a graphical command-line interface to Python; Nothing
like rich IDE's like Delphi or VB. You may need to add the path to the Python
folder to the PATH environment variable.
Launch your favorite text editor, and copy/paste the following code into eg. basic.py:
The script can be run through either "python myscript.py", "pythonw myscript.py", or just "myscript.py" or "myscript.pyw" if the OS can locate the Python interpreter.
As shown above, a wxPython application requires two main objects: An application object and a top-level window object. The primary purpose of the application object is to manage the main event loop behind the scenes.
"In the realm of class methods, it is a convention to designate the first parameter as self, though it can be named differently if necessary. This concept of self proves pivotal in distinguishing between instance-specific variables and class variables […]. It represents the instance or objects of a class and binds the attributes of a class with specific arguments. […] if you want the variables to be shared by all instances of the class, you need to declare the instance variables without self […] Unlike other programming languages, Python does not use the @ syntax to access the instance attributes. This is the sole reason why you need to use the self variable in Python. […] Class variables are defined within a class and they are shared with all the instances (objects) of the class whereas instance variables are owned by the instances of a class. For different instances, the instance variables are different."
"When most people look at this running program, they see something they would call a window. However, wxPython does not call this a window. It calls this a frame. In wxPython, window is the generic term for any object that displays on the screen (what other toolkits might call a widget). So, a wxPython programmer will often refer to objects such as buttons or text boxes as windows. This may seem confusing, but the usage dates to the earliest days of the original C++ toolkit, and it's unlikely to change now. In this book, we'll try to avoid the use of window as a generic term, because it's confusing and also because it's the name of a big product from a major corporation. We'll use widget as the generic term. When we're specifically referring to the operating system of similar name, we'll do it with a capital W."
How an app handles an event.
Use a frame when you need a window for your application; Use a panel (within that frame) to place other widgets onto. Don't place (most) widgets right onto the frame itself; there are some problems with that. You can and often will use multiple panels within the same frame.
As an easy way to lay down widgets into a frame, use the wx*Sizer classes (those are the most common):
wxApp → wxFrame → wxPanel → wxControl → wxSizer
A sizer can contain sizers of its own, making it very flexible.
Infos:
wxPython-speak for what is otherwise known in the Windows world as the tab widget, ie. multiple sub-windows on top of each other.
https://docs.wxpython.org/wx.adv.SashWindow.html
This can be used to launch a script when the user clicks on the button, and display information in the label:
Next, a basic editor (from wxPython for newbies):
Here's how to add a menu bar, and display a dialogbox when choosing an item:
Here, we'll build a filled window that contains a plane, ie. not just an empty frame, and add widgets on the plane:
Several tools (py2exe, py2app, PyInstaller, cx_Freeze, bbfreeze, Nuitka) are available to compile a script so that it runs on a computer without the user having to install Python and dependencies. They can even compile all the files into a single binary and avoid forcing the user to run an installer (eg. NSIS, Inno Installer).
Here's how to use PyInstaller: pyinstaller myscript.pyw --onefile --noconsole
Note: Having been published almost twenty years ago with code written in Python2, a bit of tweaking is occasionally required (eg. print(), super(), etc.) The source code of the examples can be found here.
Two objects are required in all wxPython applications.: The application object, which manages the event loop and oversees the application lifecycle; The top-level window, which is the focal point of user interaction with your program.
What we generally call a window in Windows is called a frame in wxWidgets. In wxWidgets, windows refer to any widget within a frame.
A bare frame:
A frame cannot be created before the application object; For this reason, it's recommended to create the top-level frame in the OnInit() method — doing so guarantees that the application already exists.
A more real-life structure:
DEPRECATED: USE app() Subclassing wx.App is useful to create your top-level frame in the OnInit() method, but it's overkill for a single-frame application, in which case use the wx.PySimpleApp class:
To use sys.stdout/sys.stderr: When your application object is created you can decide to have wxPython take control of the standard streams and redirect the output to a window instead. See the example in section "2.3.1 Redirecting output".
You have one hook into the exit process to perform your own cleanup. If defined, the OnExit() method of your wx.App subclass is called after the last window closes but before wxPython’s internal cleanup. You can use this method to clean up any non-wxPython resources you’ve created. Even if the application is closed with wx.Exit(), the OnExit() method is still triggered.
When you create subclasses of wx.Frame, the __init__() method of your class should call the parent constructor wx.Frame.__init__().
The most important use of ID numbers in wxPython is to create a unique relationship between an event that happens to a specific object and a function which is called in response to that event.
In real-life applications, widgets such as push-buttons are not created directly in a frame, but rather in a panel, which acts as a container and is itself created in a frame. A panel usually overlays the entire frame, and helps keep the widgets separate from the toolbar and status bar. When a frame is created with just a single child window, that child window (typically, a panel) is automatically resized to fill the client area of the frame.
Use a sizer to avoid having to specify the position and size of each widget, including when the user resizes the frame/panel.
Common dialogs are available. Here's an example:
If you just need some basic, text input from the user:
If you want to have the user pick an item in a limited list:
p.88
Events are represented as instances of the wx.Event class and its subclasses, such as wx.CommandEvent and wx.MouseEvent. An event handler is a written function or method that is called in response to an event. Also called a handler function or handler method. An event binder is a wxPython object that encapsulates the relationship between a specific widget, a specific event type, and an event handler. In order to be invoked, all event handlers must be registered with an event binder.
PyCrust is a graphical shell program, written in wxPython, that you can use to help analyze your wxPython programs. PyCrust is part of a larger Py package that includes additional programs with related functionality including PyFilling, PyAlaMode, PyAlaCarte, and PyShell.
The key to a successful MVC design is not in making sure that every object knows about every other object. Instead, a successful MVC program explicitly hides knowledge about one part of the program from the other parts. The goal is for the systems to interact minimally, and over a well-defined set of methods. In particular, the Model component should be completely isolated from the View and Controller.
Because both refactoring and the use of an MVC design pattern tend to break your program into smaller pieces, it is easier for you to write specific unit tests targeting individual parts of your program. Since version 2.1, Python has been distributed with the unittest module. The unittest module implements a test framework called PyUnit.
The layout mechanism in wxPython is called a sizer, and the idea is similar to layout managers in Java AWT and other interface toolkits. Each different sizer manages the size and position of its windows based on a set of rules. The sizer belongs to a container window (typically a wx.Panel). Subwindows created inside the parent must be added to the sizer, and the sizer manages the size and position of each widget.
The primary wx.Frame class has several different frame style bits which can change its appearance. In addition, wxPython offers miniframes, and frames that implement the Multiple Document Interface (MDI). Frames can be split into sections using splitter bars, and can encompass panels larger than the frame itself using scrollbars.
A typical wxPython application creates subclasses of wx.Frame and creates instances of those subclasses. This is because of the unique status of wx.Frame—although it defines very little behavior by itself, a subclass with a unique initializer is the most logical place to put information about the layout and behavior of your frame.
A panel is an instance of the class wx.Panel, and is a simple container for other widgets with little functionality of its own. You should almost always use a wx.Panel as the top-level subwidget of your frame. For one thing, the extra level can allow greater code reuse, as the same panel and layout could be used in more than one frame. Using a wx.Panel gives you some of the functionality of a dialog box within the frame. This functionality manifests itself in a couple of ways. One is simply that wx.Panel instances have a different default background color under MS Windows operating systems — white, instead of gray. Secondly, panels can have a default item that is automatically activated when the Enter key is pressed, and panels respond to keyboard events to tab through the items or select the default item in much the same way that a dialog does.
Here's how to create an MDI parent/child interface:
A splitter window is a particular kind of container widget that manages exactly two sub-windows. The two sub-windows can be stacked horizontally or next to each other left and right. In between the two sub-windows is a sash, which is a movable border that changes the size of the two sub-windows. Splitter windows are often used for sidebars to the main window (i.e., a browser).
In wxPython, a wizard is a series of pages controlled by an instance of the class wx.wizard.Wizard. The wizard instance manages the events that take the user through the pages. The pages themselves are instances of either the class wx.wizard.WizardPageSimple or wx.wizard.WizardPage. In both cases, they are merely wx.Panel instances with the additional logic needed to manage the page chain.
A validator is a special wxPython object that simplifies managing data in a dialog. A validator is attached to a specific widget in your system.
The recommended way to deal with complicated layout these days is by using a sizer. A sizer is an automated algorithm for laying out a group of widgets. A sizer is attached to a container, usually a frame or panel. Subwidgets that are created within a parent container must be separately added to the sizer. When the sizer is attached to the container, it then manages the layout of the children contained inside it. The most flexible sizers, the grid bag and box, will be able to do nearly everything you'll want them to. A wxPython sizer is an object whose sole purpose is to manage the layout of a set of widgets within a container. The sizer is not a container or a widget itself. It is just the representation of an algorithm for laying out a screen.
Predefined sizers:
p.325
import wx # Always import wx before
from wx import xrc # any other wxPython
packages,
from wx import html # just to be on the safe side.
Phoenix was the code name for the Python 3 port of wxPython. The previous versions of wxPython are not completely compatible with wxPython 4.
The wx.Frame is the window object that contains all the other widgets. The Panel is a container that also enables the ability to tab between widgets. You can use Panels to group widgets as well.
A most basic app:
To make code more modular, most code is put in classes: The frame class for widgets related to the frame, and the panel class for those grouped into a panel.
A panel should have a parent, which in this case is a Frame. You only want one panel as the sole widget for a frame. The panel will automatically expand to fill the frame as well if it is the only child widget of the frame. wx.Panel widgets enable tabbing between widgets on Windows. So if you want to be able to tab through the widgets in a form you have created, you are required to have a panel as their parent.
Here's how to add a push button:
wxPython supports both absolute positioning of widgets and relative positioning of widgets. Relative positioning of widgets requires the use of special container objects called Sizers or Layouts. A sizer allows you to resize your application and have the widgets resize along with it. It is always recommended that you use sizers as they will make your application much nicer to use on multiple displays and screen sizes.
The wxPython toolkit has several sizers:
Adding the button to the sizer:
Notes:
The book goes on showing different how to write different applications (an image viewer, a database viewer+editor, a calculator, etc.)
wxPySimpleApp()
print()
super()
Event bound to a widget:
sizer.Add(txtctrl,flag=wx.EXPAND | wx.ALL, border=5)
The latter won't work, since self refers to the frame while the panel was assigned to the panel:
This causes the event to be handled by the more general event handler:
A panel is always recommend, at least so that the app works as expected on any platform.
https://www.pythonguis.com/faq/which-python-gui-library/
https://docs.wxpython.org/MigrationGuide.html#possible-locale-mismatch-on-windows
Just run the Windows installer, which will take care of uninstalling the current version, if need be.
Both widgets are located in the same frame. Why use self.button?
Because of need to call button elsewhere in the class?
Why use self.SetSizer() instead of SetSizer()?
Rename the script from .py to .pyw.
"The sizers are a basic tool in wxWidgets to get layouts to be mostly portable to other platforms where the widgets may be of different size. They are less important if you plan to develop for only one platform but its useful to get used to them as they can also aid when adding/removing elements from the design."