# 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.

<!--
Qt diagram GFDL license
https://doc.qt.io/qt-4.8/signalsandslots.html
-->

![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.