GUI Programming in Python

DesertPy 4/22/2015

@ftlphys
Michael Gilbert

GUI programming is a mix of science and art...

A myriad of options...

  • TCL/TK
    • Comes with your python distribution
    • Good for basic diaglogues and simple user interaction
  • WxPython
    • Built on WxWidgets written in C++
    • Feature rich and extensible
    • Full 2.7 support, 3+ support in the works
  • QT
    • PyQT: Feature rich, modern UI, QT5 support
    • PySide: Feature rich, only QT4 support
  • Kivy
    • Built on OpenGL ES 2 in Cython
    • Cross Platform to mobile and desktop
  • ...

The Basics in Python

  • Most GUI frameworks use OOP
  • Class structures define objects like Windows, frames, dialogues, buttons, etc.
  • These are all defined to run within a main GUI loop or event loop

Some tips for getting started...

  • Documenation as always is your friend
  • Stackoverflow and WxPython Phoenix Doc site
  • Get a Good IDE! (Eclipse with Pydev, Sublime, Komodo, PyCharm, etc.)
  • Make your life easier by starting from pre-existing examples or using things like WxGlade
  • Try a mockup utility like the opensource Pencil or a real pencil. :-)
  • Keep in mind that your code base will likely increase 2-10x in size

WxPython

  • We start with a GUI main loop
  • We usually begin by building a class that inherits from "Frame"
    • Upon inheriting, we can set certain attributes such as size, title, etc.
  • WxPython is designed to have attributes either set upon inheritance or edited later, but the design flow is strictly linear
  • We will divide things into 4 parts
    • Define widgets (buttons, stat text, input text, checkbox, listctrl, etc.)
    • Define properties & bindings
    • Define layout
    • Define methods

Boxes Boxes Everywhere...

Note: Everything in WxPython is contained within some sort of box sizer. The benefit is that your layout and sizing are done relative to your content

Hello World! in WxPython

In [1]:
import wx

app = wx.App(False)  # Create a new app, don't redirect stdout/stderr to a window.
frame = wx.Frame(None, wx.ID_ANY, "Hello World") # A Frame is a top-level window.
frame.Show(True)     # Show the frame.
app.MainLoop()

Let's Add a few things for the User to interact with...

In [ ]:
import wx
import datetime
import math

class HelloWorld(wx.Frame):
    def __init__(self, parent, id=-1, title="", pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE |
                                            wx.SUNKEN_BORDER |
                                            wx.CLIP_CHILDREN):
        wx.Frame.__init__(self, parent, id, title, pos, size, style)
        
        diagnal=math.sqrt(3200**2+1800**2)
        display_res=wx.GetDisplaySize()
        self.multiplier=math.sqrt(display_res[0]**2+display_res[1]**2)/diagnal
        
        self.master_panel = wx.Panel(self, -1)
        self.hello_button = wx.Button(self.master_panel, -1 ,"Hello World")
        self.slider_label = wx.StaticText(self.master_panel, -1, 
                                          "Select frequency of hellos")
        self.slider = wx.Slider(self.master_panel, -1, 25, 1, 100, 
                                size = (int(self.multiplier*250), -1), 
                                style = wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | 
                                wx.SL_LABELS 
                                )
        self.hellos_to_send=25
        
        self.__set_properties()
        self.__do_layout()
        
    def __set_properties(self):
        self.menu_bar = wx.MenuBar()
        self.help = wx.Menu()
        self.about_me = wx.MenuItem(self.help, wx.NewId(), "About...")
        self.help.AppendItem(self.about_me)
        self.menu_bar.Append(self.help, "Help")
        self.SetMenuBar(self.menu_bar)
        
        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetStatusText(datetime.datetime.now().
                                     strftime("%d %b %Y %H:%M:%S"))
        
        self.Bind(wx.EVT_MENU,self.about_program,self.about_me)
        self.Bind(wx.EVT_BUTTON, self.output_hellos, self.hello_button)
        self.Bind(wx.EVT_SCROLL_CHANGED, self.hello_count, self.slider)
        
    def __do_layout(self):
        self.panel_sizer = wx.BoxSizer(wx.VERTICAL)
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        
        self.main_sizer.Add(self.hello_button, 0, 
                            wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 
                            int(self.multiplier*10))
        self.main_sizer.Add(self.slider_label, 0, 
                            wx.BOTTOM | wx.LEFT | wx.RIGHT | 
                            wx.ALIGN_CENTER_HORIZONTAL, int(self.multiplier*10))
        self.main_sizer.Add(self.slider, 0, wx.BOTTOM | 
                            wx.ALIGN_CENTER_HORIZONTAL, int(self.multiplier*10))
        
        self.master_panel.SetSizer(self.main_sizer)
        self.main_sizer.Fit(self.master_panel)
        self.panel_sizer.Add(self.master_panel, 1, wx.EXPAND)
        self.SetSizer(self.panel_sizer)
        self.panel_sizer.Fit(self)
        self.Layout()
        
    def about_program(self, evt):
        dlg = wx.MessageDialog(self, 'This program says hello to the world.',
                               'Greetings!',
                               wx.OK | wx.ICON_INFORMATION
                               )
        dlg.ShowModal()
        dlg.Destroy()
        
    def hello_count(self, evt):
        self.hellos_to_send=int(evt.EventObject.GetValue())
    
    def output_hellos(self, evt):
        for i in range(self.hellos_to_send):
            print(i+1, 'hello!')
            
if __name__ == '__main__':
    app = wx.App()
    HelloWorldApp = HelloWorld(None, title = 'Hello World')
    HelloWorldApp.Show()
    app.MainLoop()
        

Kivy

  • We have a main GUI loop just like WxPython
  • We divide our code between python and KV lang
    • This allows for separation of layout and logic
    • MVC architecture (Model - View - Control)
  • KV lang provides a pythonic domain specific language for GUI design
  • We can deploy applications to Android and iOS
    • This is made possible only under Linux (at the moment) using Buildozer
    • Pyler: Generic Python API for all platforms (wraps Pyjnius & Pyobjus)

Hello World! in Kivy

In [4]:
from kivy.app import App
from kivy.uix.button import Button

class TestApp(App):
    def build(self):
        return Button(text='Hello World')

TestApp().run()

Hello World! in Kivy with kv lang

In [ ]:
from kivy.app import App

class TestApp(App):
    pass

if __name__ == '__main__':
    TestApp().run()
In [ ]:
Button:
    text: 'Hello from test.kv'

Kivy Resources

General Tips & Tricks

  • Use MVC whenever possible
  • Avoid pixel declarations where possible and/or account for resolution on development platform
  • Allow the GUI system to do all layouts in a relative fashion and fit to your content

Questions?

PyCon 2015 Montreal!

Some Interesting Take aways...