# PyQt vs. wxPython by Seth Kenlon Python is a popular language capable of scripting as well as object oriented programming. There are several frameworks providing a graphical user interface (GUI) for Python, and most of them are good at something, whether it's simplicity or efficiency or flexibility. Two of the most popular are [wxPython](http://docs.wxwidgets.org/trunk/overview_python.html) and [PyQt](http://pyqt.sourceforge.net/Docs/PyQt5/), but how do they compare? More importantly, which should you choose for your project? ## Cross-platform Both wxPython and PyQt support Linux, Windows, and Mac, so they're perfect for the famously cross-platform Python. However, don't let the term "cross-platform" fool you, there are still platform-specific adjustments you have to make in your Python code. Your GUI toolkit can't adjust path formats to data directories, so you still have to exercise best practises within Python, using `os.path.join` and a few different `exit` methods, and so on. Your choice of GUI toolkit will not magically abstracted from platform. Using PyQt, I have yet to find any platform-specific changes required in the GUI code. As long as you follow sensible cross-platform aware coding guidelines, your GUI code stays the same regardless of OS. It's a luxury you'll come to appreciate and admire. In wxPython, there are a few platform-specific changes you may need for even your GUI code, depending on what you're programming. For instance, to prevent flickering of some elements on Microsoft Windows, the `USE_BUFFERED_DC` attribute must be set to `True` so that graphics are double buffered. This isn't a default, even though it can be done unconditionally for all platforms, so it may have drawbacks in some use cases, but it's a good example of the allowances you have to make for wxPython. ### Install That's on the code side. You also have to consider the install process that your users have to do to get your application running. Installing Qt on any platform is as simple as installing any other application. Give your users a link to download, tell them to install the downloaded package, and they're using your application in no time. This is true on all supported platforms. The install process for wxPython is just as simple on Linux and Windows, but it's problematic on macOS. The downloadable packages are out of date, another victim of Apple's disinterest in backward compatibility. A [bug ticket](http://trac.wxwidgets.org/ticket/17203) exists with a fix, but the packages haven't been updated, so chances are low that an average user will find and implement the patch themselves. The solution right now is to package wxPython yourself and distribute it to your macOS users yourself. ## Look and feel The wxPython toolkit has the unique feature that its core libraries, written in C++, are wrappers around the native widgets of its host system. When you write code for a button widget in your GUI, you don't get something that looks like it belongs on another OS, nor do get a mere approximation, you get the same object as you do if you coded with native tools. ![Thunar and WxPython on Linux.](wxbutton.png) This is different from PyQt, based on the famous Qt toolkit. It's also written in C++, but it does not use native widgets, creating approximations of widgets depending on what OS it detects. It's a good approximation, and I've never had a user, even at an art school where the users were famously pedantic about appearance, complain that an application didn't look and feel native. If you're using KDE, there are additional PyKDE libraries available to bridge the gap between raw PyQt and the appearance of your Plasma desktop, but that obviously adds new dependencies. ![KDE and Qt on Linux.](qtbutton.png) ## Widgets and features Both PyQt and wxPython have all the usual widgets you expect from a GUI toolkit. There are buttons and check boxes and drop-down menus and more. Both support drag-and-drop actions, tabbed interfaces, dialog boxes, and the creation of custom widgets. PyQt has the advantage of flexibility. Qt panels can be rearranged, floated, closed, and restored by the user, at runtime, giving every application a highly configurable usability-centric interface. ![Moving Qt panels](panelmove.png) Those features come built-in, so as long as you're using the right widgets, you don't have to reinvent fancy tricks to provide friendly features for your power users. ## Gears and pulleys A GUI application is made up of lots of smaller visual elements, usually called "widgets". For a GUI application to function smoothly, widgets need to communicate with another so that, for instance, a pane that's meant to display an image knows which thumbnail the user has selected. Most GUI toolkits, wxPython included, deal with internal communication with callbacks. A **callback** is a pointer to some piece of code (a function). When you want to make something happen when, for instance, a button widget is clicked, you write a function for the action you want to occur. Then, when the button is clicked, you call the function in your code and the action occurs. It works well enough, and as long as you couple it with lambdas, it's a pretty flexible solution. Sometimes, depending on just how elaborate you want the communication to be, you do end up with a lot more code than you had expected, but it does work. Qt, on the other hand, is famous for its **signals and slots** mechanism. If you imagine wxPython's internal communications network as an old-style telephone switchboard, then imagine PyQt's communication as unlimited free WiFi. ![Signals and Slots in Qt](abstract-connections.png) With signals and slots, everything gets a signature. A widgit that emits a signal doesn't need to know what slot its message is destined for or even whether it's destined for any slot at all. As long as you connect a signal to a slot, the slot will be called with the signal's parameters when the signal is broadcast. Slots can be set to listen for any number of signals, and signals can be set to broadcast to any number of slots. You can even connect a signal to another signal to create a chain reaction of signals. You don't ever have to go back into your code a "wire" things together manually. Signals and slots can take any number of arguments, of any type. You don't have to write the code to filter out the things you do or not want under certain conditions. Better still, slots aren't just listeners, they're normal functions that can do useful things with or without a signal. Just as an object doesn't know if anything is listening for its signal, a slot doesn't know whether it is listening for a signal. No block of code is ever reliant upon a connection existing, it just gets triggered at different times if there is a connection. ## Layout When you program a GUI app, you have to design its layout so that all the widgets know where to appear in your application window. Like a web page, you might choose to design your application to be resized, or you might constrain it to a fixed size. In some ways, this is the GUI-est part of GUI programming. In Qt, everything is pretty logical. Widgets are sensibly named (`QPushButton`, `QDial`, `QCheckbox`, `QLabel`, and even `QCalendarWidget`) and are easy to invoke. The documentation is excellent, so as long as you refer back to it frequently, it's easy to discover cool features. There are some potential points of confusion, mostly in the base-level GUI elements. For instance, if you're writing an application, do you start with a `QMainWindow` or `QWidget` to form your parent window? Both can serve as a window for your application, so the answer is, as it so often is in computing, "it depends". `QWidget` is a raw, empty container. It gets used by all other widgets, but that means it can also be used as-is to form the parent window into which you place more widgets. `QMainwindow`, like all other widgets, uses `QWidget`, but it adds lots of convenience features that most applications need, like a toolbar along the top, a status bar at the bottom, and so on. ![QMainWindow](qmainwindow.png) A small text editor using `QMainwindow` in just over 100 lines of Python code: #!/usr/bin/env python # a minimal text editor to demo PyQt5 # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import sys import os import pickle from PyQt5 import * from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class TextEdit(QMainWindow): def __init__(self): super(TextEdit, self).__init__() #font = QFont("Courier", 11) #self.setFont(font) self.filename = False self.Ui() def Ui(self): quitApp = QAction(QIcon('/usr/share/icons/breeze-dark/actions/32/application-exit.svg'), 'Quit', self) saveFile = QAction(QIcon('/usr/share/icons/breeze-dark/actions/32/document-save.svg'), 'Save', self) newFile = QAction('New', self) openFile = QAction('Open', self) copyText = QAction('Copy', self) pasteText = QAction('Yank', self) newFile.setShortcut('Ctrl+N') newFile.triggered.connect(self.newFile) openFile.setShortcut('Ctrl+O') openFile.triggered.connect(self.openFile) saveFile.setShortcut('Ctrl+S') saveFile.triggered.connect(self.saveFile) quitApp.setShortcut('Ctrl+Q') quitApp.triggered.connect(self.close) copyText.setShortcut('Ctrl+K') copyText.triggered.connect(self.copyFunc) pasteText.setShortcut('Ctrl+Y') pasteText.triggered.connect(self.pasteFunc) menubar = self.menuBar() menubar.setNativeMenuBar(True) menuFile = menubar.addMenu('&File') menuFile.addAction(newFile) menuFile.addAction(openFile) menuFile.addAction(saveFile) menuFile.addAction(quitApp) menuEdit = menubar.addMenu('&Edit') menuEdit.addAction(copyText) menuEdit.addAction(pasteText) toolbar = self.addToolBar('Toolbar') toolbar.addAction(quitApp) toolbar.addAction(saveFile) self.text = QTextEdit(self) self.setCentralWidget(self.text) self.setMenuWidget(menubar) self.setMenuBar(menubar) self.setGeometry(200,200,480,320) self.setWindowTitle('TextEdit') self.show() def copyFunc(self): self.text.copy() def pasteFunc(self): self.text.paste() def unSaved(self): destroy = self.text.document().isModified() print(destroy) if destroy == False: return False else: detour = QMessageBox.question(self, "Hold your horses.", "File has unsaved changes. Save now?", QMessageBox.Yes|QMessageBox.No| QMessageBox.Cancel) if detour == QMessageBox.Cancel: return True elif detour == QMessageBox.No: return False elif detour == QMessageBox.Yes: return self.saveFile() return True def saveFile(self): self.filename = QFileDialog.getSaveFileName(self, 'Save File', os.path.expanduser('~')) f = self.filename[0] with open(f, "w") as CurrentFile: CurrentFile.write(self.text.toPlainText() ) CurrentFile.close() def newFile(self): if not self.unSaved(): self.text.clear() def openFile(self): filename, _ = QFileDialog.getOpenFileName(self, "Open File", '', "All Files (*)") try: self.text.setText(open(filename).read()) except: True def closeEvent(self, event): if self.unSaved(): event.ignore() else: exit def main(): app = QApplication(sys.argv) editor = TextEdit() sys.exit(app.exec_()) if __name__ == '__main__': main() The foundational widget in wxPython is the `wx.Window`. Everything in wxPython, whether it's an actual window or just a button or checkbox or text label, is based upon the `wx.Window` class. If they had awards for the most erroneously named class, `wx.Window` would be overlooked because it's *so* badly named that no one would suspect it of being wrong. I've been told it takes years to get used `wx.Widget` not being a window, and that must be true, because it's a mistake I make every time I use `wxWidgets`. It's the `wx.Frame` class that plays the traditional role of what you and I think of as a window. Use `wx.Frame` to create an empty window: #!/usr/bin/env python # -*- coding: utf-8 -*- import wx class Myframe(wx.Frame): def __init__(self, parent, title): super(Myframe, self).__init__(parent, title=title, size=(520, 340)) self.Centre() self.Show() if __name__ == '__main__': app = wx.App() Myframe(None, title='Just an empty frame') app.MainLoop() Place other widgets inside of a `wx.Frame` window, and then you're building a GUI application. For example, the `wx.Panel` widget is similar to a `div` in HTML with absolute size constraints, so you would use it to create panels within your main window (except it's not a window, it's a `wx.Frame`). There are fewer convenience functions in wxPython in comparison to PyQt. For instance, copy and paste functionality is built right into PyQt, while it has to be coded by hand in wxPython (and is still partially subject to the platform it runs on). Some of these are handled graciously by a a good desktop with built-in features, but for feature parity with a PyQt app, there's a little more manual work involved. ![wx.Frame](wxframe.png) A simple text editor in wxPython: #!/usr/bin/env python # a minimal text editor to demo wxPython # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import wx import os class TextEdit(wx.Frame): def __init__(self,parent,title): wx.Frame.__init__(self,parent,wx.ID_ANY, title, size=(520, 340)) menuBar = wx.MenuBar() menuFile = wx.Menu() menuBar.Append(menuFile,"&File") menuFile.Append(1,"&Open") menuFile.Append(2,"&Save") menuFile.Append(3,"&Quit") self.SetMenuBar(menuBar) wx.EVT_MENU(self,1,self.openAction) wx.EVT_MENU(self,2,self.saveAction) wx.EVT_MENU(self,3,self.quitAction) self.p1 = wx.Panel(self) self.initUI() def initUI(self): self.text = wx.TextCtrl(self.p1,style=wx.TE_MULTILINE) vbox = wx.BoxSizer(wx.VERTICAL ) vbox.Add( self.p1, 1, wx.EXPAND | wx.ALIGN_CENTER ) self.SetSizer(vbox) self.Bind(wx.EVT_SIZE, self._onSize) self.Show() def _onSize(self, e): e.Skip() self.text.SetSize(self.GetClientSizeTuple()) def quitAction(self,e): if self.text.IsModified(): dlg = wx.MessageDialog(self,"Quit? All changes will be lost.","",wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: self.Close(True) else: self.saveAction(self) else: exit() def openAction(self,e): dlg = wx.FileDialog(self, "File chooser", os.path.expanduser('~'), "", "*.*", wx.OPEN) if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetFilename() dir = dlg.GetDirectory() f = open(os.path.join(dir, filename),'r') self.text.SetValue(f.read()) f.close() dlg.Destroy() def saveAction(self,e): dlg = wx.FileDialog(self, "Save as", os.path.expanduser('~'), "", "*.*", wx.SAVE | wx.OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: filedata = self.text.GetValue() filename = dlg.GetFilename() dir = dlg.GetDirectory() f = open(os.path.join(dir, filename),'w') f.write(filedata) f.close() dlg.Destroy() def main(): app = wx.App(False) view = TextEdit(None, "TextEdit") app.MainLoop() if __name__ == '__main__': main() ## Which one should you use? Both the PyQt and wxPython GUI toolkits have their strengths. wxPython is simple, and when it's not simple, it's intuitive to a Python programmer who's not afraid to hack a solution together. There aren't that many instances of a "wxWidget way" that you have to be indoctrinated into. It's a toolkit with bits and bobs that you can use to throw together a GUI. If you're targeting a user space that you know already has GTK installed, then wxPython taps into that with minimal dependencies. As a bonus, it uses native widgets, so your applications ought to look no different than the applications that come preinstalled on your target computers. Don't take wxPython's claim of being cross-platform too much to heart, though. It's got install issues on some platforms, some of the time, and it hasn't got that many layers of abstraction to shield you from differences between platforms. PyQt is big, and will almost always require some installation of dependencies (especially on non-Linux and non-BSD targets). Along with all that hefty code comes a lot of convenience. Qt does its best to shield you from differences in platforms, it provides you with a staggering number of prebuilt functions and widgets and abstractions. It's well-supported, with plenty of companies relying on it as their foundational framework, and some of the most significant open source projects use and contribute to it. If you're just starting out, you should try a little of each to see which one appeals to you. If you're an experienced programmer, try one you haven't used yet, and see what you think. It's open source, so you don't have to choose just one. The important thing is to know when to use which solution. Happy hacking.