Dies ist die Support Website des Buches:

Das Python Praxisbuch
Der große Profi-Leitfaden für Programmierer
Farid Hajji
Addison Wesley / Pearson Education
ISBN 978-3-8273-2543-3 (Sep 2008), 1298 Seiten.

16. GUI-Toolkits

wxPython

Screenshots:

Das wxPython Logo

URLs:

wxPython installieren

Installation unter Unix

Installation unter Windows

Erste Schritte in wxPython

Wie finde ich mich in wxPython zurecht?

#!/usr/bin/env python
# wx_app1.py -- A first wxPython application.

import wx

app = wx.App()
frame = wx.Frame(None, wx.ID_ANY, title="A frame", size=(400, 200))
frame.Show(True)
app.MainLoop()

wx_app1.py

Screenshots:

Ein Fenster mit einem Button

#!/usr/bin/env python
# wx_app2.py -- A window with one button.

import wx

app = wx.App()

frame = wx.Frame(None, wx.ID_ANY, title="A frame", size=(400, 200))
button = wx.Button(frame, wx.ID_EXIT, label="Exit")
button.Bind(event=wx.EVT_BUTTON, handler=lambda evt: frame.Close())

frame.Show(True)
app.MainLoop()

wx_app2.py

#!/usr/bin/env python
# wx_app2a.py -- A window with one button, using inheritance

import wx

class FrameWithOneButton(wx.Frame):
    def __init__(self, *args, **kw):
        wx.Frame.__init__(self, *args, **kw)
        button = wx.Button(self, wx.ID_EXIT, label="Exit")
        button.Bind(event=wx.EVT_BUTTON, handler=lambda evt: self.Close())

if __name__ == '__main__':
    app = wx.App()
    bframe = FrameWithOneButton(None, id=wx.ID_ANY,
                                title="A frame with one button",
                                size=(400, 200))
    bframe.Show(True)
    app.MainLoop()

wx_app2a.py

Screenshots:

Sizer oder: ein Fenster mit zwei Buttons

#!/usr/bin/env python
# wx_app3.py -- A window with two buttons

import wx

class FrameWithTwoButtons(wx.Frame):
    def __init__(self, *args, **kw):
        wx.Frame.__init__(self, *args, **kw)
        self.panel = wx.Panel(self, wx.ID_ANY)
        self.sizer = wx.BoxSizer(wx.HORIZONTAL)

        button1 = wx.Button(self.panel, wx.ID_EXIT, label="Exit 1")
        button1.Bind(event=wx.EVT_BUTTON, handler=lambda evt: self.Close())
        self.sizer.Add(button1, wx.EXPAND)

        button2 = wx.Button(self.panel, wx.ID_EXIT, label="Exit 2")
        button2.Bind(event=wx.EVT_BUTTON, handler=lambda evt: self.Close())
        self.sizer.Add(button2, wx.EXPAND)

        self.panel.SetSizer(self.sizer)
        self.sizer.Fit(self)

if __name__ == '__main__':
    app = wx.App()
    bframe = FrameWithTwoButtons(None, id=wx.ID_ANY,
                                 title="A frame with two buttons",
                                 size=(400, 200))
    bframe.Show(True)
    app.MainLoop()

wx_app3.py

Screenshots:

Ein Button und ein Dialogfenster

#!/usr/bin/env python
# wx_app4.py -- A button and a dialog window

import wx

class FrameWithButtonAndDialog(wx.Frame):
    def __init__(self, *args, **kw):
        wx.Frame.__init__(self, *args, **kw)

        button = wx.Button(self, wx.ID_ANY, label="Click me")
        button.Bind(event=wx.EVT_BUTTON, handler=self.OnClick)

    def OnClick(self, evt):
        dialog = wx.MessageDialog(self, message="Thank you for clicking me",
                                  caption="Button clicked",
                                  style=wx.OK | wx.ICON_INFORMATION)
        dialog.ShowModal()

if __name__ == '__main__':
    app = wx.App()
    bdframe = FrameWithButtonAndDialog(None, id=wx.ID_ANY,
                                       title="A button and a dialog",
                                       size=(400, 200))
    bdframe.Show(True)
    app.MainLoop()

wx_app4.py

Screenshots:

Ein Dialogfenster als Passwortabfrage

#!/usr/bin/env python
# wx_app5.py -- Querying a password with a TextEntryDialog window

import wx

class QueryFrame(wx.Frame):
    def __init__(self, *args, **kw):
        wx.Frame.__init__(self, *args, **kw)

        button = wx.Button(self, wx.ID_ANY, label="Click me")
        button.Bind(event=wx.EVT_BUTTON, handler=self.OnClick)

    def OnClick(self, evt):
        dialog = wx.TextEntryDialog(self, message="Password, please?",
                                    caption="Password query",
                                    style=wx.OK | wx.CANCEL | wx.TE_PASSWORD)
        dialog.SetValue("Your password here...")
        result = dialog.ShowModal()

        if result == wx.ID_OK:
            passwd = dialog.GetValue()
        elif result == wx.ID_CANCEL:
            passwd = "No password supplied"
        else:
            passwd = "Eh?"

        dialog.Destroy()

        wx.MessageDialog(self, message="Your password: " + passwd,
                         caption="Query result", style=wx.OK).ShowModal()

if __name__ == '__main__':
    app = wx.App()
    qframe = QueryFrame(None, id=wx.ID_ANY,
                        title="A query frame", size=(400, 200))
    qframe.Show(True)
    app.MainLoop()

wx_app5.py

Screenshots:

Responsive GUIs

Das Problem des eingefrorenen GUI

#!/usr/bin/env python
# wx_app7.py -- simulating a non-responsive GUI

import time
import wx

class NonResponsiveGUI(wx.Frame):
    def __init__(self, *args, **kw):
        wx.Frame.__init__(self, *args, **kw)
        
        self.sb = wx.StatusBar(self)
        self.sb.SetStatusText("Ready")
        self.SetStatusBar(self.sb)
        
        self.button = wx.Button(self, id=100, label="Start slow_op")
        self.Bind(event=wx.EVT_BUTTON, handler=self.handler, id=100)
        
        self.count = 0L
        
    def handler(self, evt):
        "Simulate a slow operation"
        time.sleep(10)

        self.count = self.count + 1L
        self.sb.SetStatusText(str(self.count))

if __name__ == '__main__':
    app = wx.App()
    frame = NonResponsiveGUI(None, title='Freezing GUI',
                             size=(400, 100))
    frame.Show(True)
    app.MainLoop()

wx_app7.py

Screenshots:

Timer

#!/usr/bin/env python
# wx_app7a.py -- simulating a responsive GUI with wx.CallLater

import wx
from wx_app7 import NonResponsiveGUI

class CallLaterGUI(NonResponsiveGUI):
    def __init__(self, protect=True, *args, **kw):
        NonResponsiveGUI.__init__(self, *args, **kw)
        self.protect = protect
        
    def handler(self, evt):
        "Simulate a slow operation"
        if self.protect:
            self.button.Enable(False)
        self.timer = wx.CallLater(10000, self.OnTimer)

    def OnTimer(self):
        self.count = self.count + 1L
        self.sb.SetStatusText(str(self.count))
        if self.protect:
            self.button.Enable(True)

if __name__ == '__main__':
    import sys
    protectValue = (sys.argv[1] == 'True')
    
    app = wx.App()
    frame = CallLaterGUI(protect=protectValue,
                         parent=None, title='CallLater GUI', size=(400, 100))
    frame.Show(True)
    app.MainLoop()

wx_app7a.py

#!/usr/bin/env python
# wx_app7b.py -- simulating a responsive GUI with wx.Timer and wx.EVT_TIMER

import wx
from wx_app7 import NonResponsiveGUI

class EVT_TIMER_GUI(NonResponsiveGUI):
    def __init__(self, *args, **kw):
        NonResponsiveGUI.__init__(self, *args, **kw)
        self.Bind(wx.EVT_TIMER, self.OnTimer)
        
    def handler(self, evt):
        "Simulate a slow operation"
        self.button.Enable(False)
        self.timer = wx.Timer(self)
        self.timer.Start(10000)

    def OnTimer(self, evt):
        self.timer.Stop()
        self.count = self.count + 1L
        self.sb.SetStatusText(str(self.count))
        self.button.Enable(True)

if __name__ == '__main__':
    app = wx.App()
    frame = EVT_TIMER_GUI(None, title='EVT_TIMER GUI', size=(400, 100))
    frame.Show(True)
    app.MainLoop()

wx_app7b.py

#!/usr/bin/env python
# wx_app7c.py -- simulating a responsive GUI with deriving from wx.Timer

import wx
from wx_app7 import NonResponsiveGUI

class NotifyTimerGUI(NonResponsiveGUI):
    def __init__(self, *args, **kw):
        NonResponsiveGUI.__init__(self, *args, **kw)
        
    def handler(self, evt):
        "Simulate a slow operation"
        self.button.Enable(False)
        self.timer = NotifyTimer(self)
        self.timer.Start(10000)

    def OnTimer(self):
        self.timer.Stop()
        self.count = self.count + 1L
        self.sb.SetStatusText(str(self.count))
        self.button.Enable(True)

class NotifyTimer(wx.Timer):
    def __init__(self, theFrame):
        wx.Timer.__init__(self)
        self.theFrame = theFrame

    def Notify(self):
        "Will be triggered with the timer ticks"
        self.theFrame.OnTimer()

if __name__ == '__main__':
    app = wx.App()
    frame = NotifyTimerGUI(None, title='NotifyTimer GUI', size=(400, 100))
    frame.Show(True)
    app.MainLoop()

wx_app7c.py

Delayed Results

#!/usr/bin/env python
# wx_app7d.py -- simulating a responsive GUI with DelayedResults

import time
import wx
import wx.lib.delayedresult as delayedresult
from wx_app7 import NonResponsiveGUI

class DelayedResultsGUI(NonResponsiveGUI):
    def __init__(self, protect=True, *args, **kw):
        NonResponsiveGUI.__init__(self, *args, **kw)
        self.protect = protect
        
    def handler(self, evt):
        "Called when self.button is clicked"
        if self.protect:
            self.button.Enable(False)
        delayedresult.startWorker(self.consumeResult, self.produceResult)
    
    def produceResult(self):
        "Simulates a slow operation running in its own thread"
        time.sleep(10)
        return self.count + 1L
    
    def consumeResult(self, delayedResult):
        "Collects results from worker thread, running in main thread"
        self.count = delayedResult.get()
        self.sb.SetStatusText(str(self.count))
        if self.protect:
            self.button.Enable(True)

if __name__ == '__main__':
    import sys
    protectValue = (sys.argv[1] == 'True')
    
    app = wx.App()
    frame = DelayedResultsGUI(protect=protectValue,
                              parent=None, title='DelayedResults GUI',
                              size=(400, 100))
    frame.Show(True)
    app.MainLoop()

wx_app7d.py

#!/usr/bin/env python
# wx_app8.py -- simulating a responsive GUI with DelayedResults and Cancel

import time
import wx
import wx.lib.delayedresult as delayedresult

class DelayedResultsCancelGUI(wx.Frame):
    def __init__(self, *args, **kw):
        wx.Frame.__init__(self, *args, **kw)

        self.count = 0L
        self.is_running = False
        self.Bind(wx.EVT_CLOSE, self.OnClose)

        self.sb = wx.StatusBar(self)
        self.sb.SetStatusText("Ready")
        self.SetStatusBar(self.sb)

        self.panel = wx.Panel(self, wx.ID_ANY)
        self.sizer = wx.BoxSizer(wx.HORIZONTAL)

        self.buttonStart = wx.Button(self.panel, id=100, label="Start Thread")
        self.buttonStop  = wx.Button(self.panel, id=101, label="Stop Thread")
        
        self.buttonStart.Enable(True)
        self.buttonStop.Enable(False)
        
        self.sizer.Add(self.buttonStart, wx.EXPAND)
        self.sizer.Add(self.buttonStop,  wx.EXPAND)
        
        self.Bind(event=wx.EVT_BUTTON, handler=self.handlerStart, id=100)
        self.Bind(event=wx.EVT_BUTTON, handler=self.handlerStop,  id=101)
        
        self.panel.SetSizer(self.sizer)
        self.sizer.Fit(self)

    def OnClose(self, evt):
        self.is_running = False
        self.Destroy()
        
    def handlerStart(self, evt):
        "Called when buttonStart is clicked"
        self.is_running = True
        self.buttonStart.Enable(False)
        self.buttonStop.Enable(True)
        delayedresult.startWorker(self.consumeResult, self.produceResult)

    def handlerStop(self, evt):
        "Called when buttonStop is clicked"
        self.is_running = False
        self.buttonStop.Enable(False)
    
    def produceResult(self):
        "Simulates a slow operation running in its own thread"
        time.sleep(10)
        if self.is_running:
            return self.count + 1L
        else:
            return "Aborted"
    
    def consumeResult(self, delayedResult):
        "Collects results from worker thread, running in main thread"
        result = delayedResult.get()
        if result != "Aborted":
            self.count = result
        self.sb.SetStatusText(str(result))
        self.buttonStart.Enable(True)
        self.buttonStop.Enable(False)

if __name__ == '__main__':
    app = wx.App()
    frame = DelayedResultsCancelGUI(None, title='DelayedResultsCancel GUI',
                                    size=(400, 100))
    frame.Show(True)
    app.MainLoop()

wx_app8.py

Screenshots:

Threads

Das folgenden Programm wird stückweise in diesem Abschnitt vorgestellt. Hier nochmal als Ganzes:

#!/usr/bin/env python
# wx_app9.py -- Multiple Python Threads and one GUI

from counterthread import CounterThread
import wx
import wx.lib.newevent

# This creates a new Event class and a EVT binder function
(UpdateTextEvent, EVT_UPDATE_TEXT) = wx.lib.newevent.NewEvent()

class CounterGUI(wx.Frame):
    def __init__(self, numThreads=5, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.fields  = []       # a list of wx.TextCtrl fields for the threads
        self.buttons = []       # a list of (startButton, stopButton) objects
        self.numThreads = numThreads         # number of worker threads
        self.threads = self.createThreads()  # a list of worker threads
        
        self.BuildGUI()
        self.Bind(EVT_UPDATE_TEXT, self.OnUpdate)

    def createThreads(self):
        threads = []
        for i in range(self.numThreads):
            thr = CounterThread(self.callbackFn, 3+i)
            thr.setName("<Thread %d>" % (i,))
            threads.append(thr)
        return threads
        
    def BuildGUI(self):
        self.panel = wx.Panel(self, wx.ID_ANY)
        self.sizer = wx.GridSizer(self.numThreads, 4, 2, 10) # rows, cols,
                                                             # vgap, hgab
        
        for i in range(self.numThreads):
            textfield = wx.TextCtrl(self.panel, id=wx.ID_ANY)
            textfield.Enable(False)  # read only text field
            self.fields.append(textfield)

            buttonStart = wx.Button(self.panel, label="Start Thread")
            self.Bind(wx.EVT_BUTTON, handler=self.startThread,
                      source=buttonStart)

            buttonStop  = wx.Button(self.panel, label="Stop Thread")
            self.Bind(wx.EVT_BUTTON, handler=self.stopThread,
                      source=buttonStop)
            buttonStop.Enable(False)

            self.buttons.append((buttonStart, buttonStop))

            self.sizer.Add(wx.StaticText(self.panel, label="Thread %d" % (i,)))
            self.sizer.Add(textfield, wx.EXPAND)
            self.sizer.Add(buttonStart)
            self.sizer.Add(buttonStop)

        self.panel.SetSizer(self.sizer)
        self.sizer.Fit(self)

    def callbackFn(self, theThread, theCounter):
        "Called to update the GUI. Runs in calling worker thread."
        threadID = self.threads.index(theThread)
        evt = UpdateTextEvent(threadNum=threadID, value=theCounter)
        wx.PostEvent(self, evt)

    def OnUpdate(self, evt):
        "Updates the GUI. Runs in the main GUI thread."
        threadID     = evt.threadNum
        counterValue = evt.value
        self.fields[threadID].SetValue(str(counterValue))
    
    def startThread(self, evt):
        startButton = evt.GetEventObject()
        buttonID = [lst[0] for lst in self.buttons].index(startButton)
        thread = self.threads[buttonID]
        
        startButton.Enable(False)               # can't start thread anew
        self.buttons[buttonID][1].Enable(True)  # enable stop button
        thread.start()

    def stopThread(self, evt):
        stopButton = evt.GetEventObject()
        buttonID = [lst[1] for lst in self.buttons].index(stopButton)
        thread = self.threads[buttonID]
        
        stopButton.Enable(False)
        thread.stop()                           # may take some seconds
                                                # to be picked up by thread
        
if __name__ == '__main__':
    import sys
    number_of_threads = int(sys.argv[1])
    
    app = wx.App()
    frame = CounterGUI(parent=None, title='Threads controlling a GUI',
                       numThreads=number_of_threads)
    frame.Show(True)
    app.MainLoop()

wx_app9.py

#!/usr/bin/env python
# counterthread.py -- A Thread which acts like a counter and a timer.

from threading import Thread
import time

class CounterThread(Thread):
    def __init__(self, callbackFn, pace=1):
        Thread.__init__(self)
        self.callbackFn = callbackFn
        self.pace = pace
        self.counter = 0L
        self.keepGoing = True
    
    def run(self):
        while self.keepGoing:
            time.sleep(self.pace)
            self.counter = self.counter + 1L
            self.callbackFn(self, self.counter)
    
    def stop(self):
        self.keepGoing = False

counterthread.py

Screenshots:

Externe Prozesse

Das folgende Programm wird stückweise in diesem Abschnitt vorgestellt. Hier nochmal als Ganzes:

#!/usr/bin/env python
# wx_app10.py -- running a program in a wxPython GUI

import wx
import wx.richtext as rt

class RunProcess(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.size    = kwargs.get('size', ((1024,768)))
        self.process = None
        self.pid     = None
        
        self.createControls()
        self.bindEvents()
        self.createLayout()

    def createControls(self):
        "Create the UI controls / widgets for the GUI shell"
        
        self.normalfont = wx.Font(pointSize=10,
                                  family=wx.FONTFAMILY_TELETYPE,
                                  style=wx.FONTSTYLE_NORMAL,
                                  weight=wx.FONTWEIGHT_NORMAL)
        
        self.panel   = wx.Panel(self)
        
        self.command = wx.TextCtrl(self.panel, value='telnet localhost 80')
        self.command.SetFont(self.normalfont)
        self.runBtn  = wx.Button(self.panel,   label='Run!')
        
        self.output  = rt.RichTextCtrl(self.panel, value='',
                         style=rt.RE_MULTILINE | rt.RE_READONLY)
        self.output.SetFont(self.normalfont)
        
        self.input   = wx.TextCtrl(self.panel, value='',
                                   style=wx.TE_PROCESS_ENTER)
        self.input.SetFont(self.normalfont)
        self.closeBtn  = wx.Button(self.panel, label='Close stream')
        self.ctrlCBtn  = wx.Button(self.panel, label='Ctrl-C')
        self.kill9Btn  = wx.Button(self.panel, label='Kill -9')
        
        self.input.Enable(False)    # will be enabled in OnRunBtn
        self.closeBtn.Enable(False) # will be enabled in OnRunBtn
        self.ctrlCBtn.Enable(False) # will be enabled in OnRunBtn
        self.kill9Btn.Enable(False) # will be enabled in OnRunBtn
        self.command.SetFocus()

    def bindEvents(self):
        "Associate events to callback functions"

        self.Bind(wx.EVT_IDLE, self.OnIdle)
        self.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        
        self.Bind(wx.EVT_BUTTON, self.OnRunBtn, self.runBtn)
        self.Bind(wx.EVT_BUTTON, self.OnCloseStream, self.closeBtn)
        self.Bind(wx.EVT_BUTTON, self.OnCtrlC, self.ctrlCBtn)
        self.Bind(wx.EVT_BUTTON, self.OnKill9, self.kill9Btn)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnSendText, self.input)

    def createLayout(self):
        "Layout the UI widgets into a nice shell" 
        
        runbox = wx.BoxSizer(wx.HORIZONTAL)
        runbox.Add(self.command, 1, wx.EXPAND | wx.ALL)
        runbox.Add(self.runBtn, 0)
        
        inpbox = wx.BoxSizer(wx.HORIZONTAL)
        inpbox.Add(self.input, 1, wx.EXPAND | wx.ALL)
        inpbox.Add(self.closeBtn, 0)
        inpbox.Add(self.ctrlCBtn, 0)
        inpbox.Add(self.kill9Btn, 0)
        
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(runbox, 0, wx.EXPAND | wx.ALL)
        vbox.Add(self.output, 1, wx.EXPAND | wx.ALL)
        vbox.Add(inpbox, 0, wx.EXPAND | wx.ALL)
        
        self.panel.SetSizer(vbox)
        vbox.Fit(self)
        self.SetSize(self.size)

    def OnRunBtn(self, evt):
        "The user wants to start a new process"
        
        cmdline = self.command.GetValue().strip()

        self.process = wx.Process(self)
        self.process.Redirect()
        self.pid = wx.Execute(cmdline, wx.EXEC_ASYNC, self.process)

        self.command.Enable(False)
        self.runBtn.Enable(False)
        self.input.Enable(True)
        self.closeBtn.Enable(True)
        self.ctrlCBtn.Enable(True)
        self.kill9Btn.Enable(True)
        self.output.SetValue('')
        self.input.SetFocus()

    def OnSendText(self, evt):
        "The user wants to send a line to the process"
        
        text = self.input.GetValue()
        self.input.SetValue('')

        self.output.BeginBold()
        self.output.AppendText(text + '\n')
        self.output.EndBold()

        self.process.GetOutputStream().write(text + '\n')
        self.input.SetFocus()

    def OnCloseStream(self, evt):
        "The user wants to close the stream to the process"
        
        if self.process is not None:
            self.process.CloseOutput()
            self.input.Enable(False)

    def OnCtrlC(self, evt):
        "The user clicked on Ctrl-C; send SIGINT to process"
        if self.process is not None:
            wx.Process.Kill(pid=self.pid, sig=wx.SIGINT,
                            flags=wx.KILL_NOCHILDREN)

    def OnKill9(self, evt):
        "The user clicked on Kill -9; send SIGKILL to process"
        if self.process is not None:
            wx.Process.Kill(pid=self.pid, sig=wx.SIGKILL,
                            flags=wx.KILL_NOCHILDREN)

    def OnIdle(self, evt):
        "When idling, read from the process"

        if self.process is not None:
            for stream in (self.process.GetInputStream(),
                           self.process.GetErrorStream()):
                if stream.CanRead():
                    text = stream.read()
                    self.output.AppendText(text)

    def OnCloseWindow(self, evt):
        "The window is closing; destroy the process"
        
        if self.process is not None:
            self.process.Destroy()
            self.process = None
        self.Destroy()
    
    def OnProcessEnded(self, evt):
        "The process terminated; react accordingly"

        stream = self.process.GetInputStream()
        if stream.CanRead():
            text = stream.read()
            self.output.AppendText(text)

        self.process.Destroy()
        self.process = None
        self.pid = None

        wx.MessageDialog(self, message='Process ended, pid:%s, exitcode:%s' % \
                                           (evt.GetPid(), evt.GetExitCode()),
                         caption='Process ended',
                         style=wx.OK | wx.ICON_INFORMATION).ShowModal()

        self.command.Enable(True)
        self.runBtn.Enable(True)
        self.input.Enable(False)
        self.closeBtn.Enable(False)
        self.ctrlCBtn.Enable(False)
        self.kill9Btn.Enable(False)
        self.command.SetFocus()
    
if __name__ == '__main__':
    app = wx.App()
    frame = RunProcess(parent=None, title='Run process')
    frame.Show(True)
    app.MainLoop()

wx_app10.py

Screenshots:

Integration mit Twisted

#!/usr/bin/env python
# wxdemo.py -- Integrating Twisted and wxPython event loops with wxreactor
# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
# Adapted from ${twisted_src}/doc/core/examples/wxdemo.py

import sys
import wx

from twisted.python import log
from twisted.internet import wxreactor
wxreactor.install()

# import twisted.internet.reactor only after installing wxreactor:
from twisted.internet import reactor, defer

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, size=(300,200), *args, **kwargs)
        menu = wx.Menu()
        menu.Append(wx.ID_EXIT, "E&xit", "Terminate the program")
        menuBar = wx.MenuBar()
        menuBar.Append(menu, "&File")
        self.SetMenuBar(menuBar)
        self.Bind(wx.EVT_MENU, self.DoExit, id=wx.ID_EXIT)
		
        # make sure reactor.stop() is used to stop event loop:
        self.Bind(wx.EVT_CLOSE, lambda evt: reactor.stop())

    def DoExit(self, event):
        reactor.stop()

class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, id=wx.ID_ANY, title="Hello, world")
        frame.Show(True)
        self.SetTopWindow(frame)
        # look, we can use twisted calls!
        reactor.callLater(10, self.tenSecondsPassed)
        return True
		
    def tenSecondsPassed(self):
        print "ten seconds passed"

def demo():
    log.startLogging(sys.stdout)

    # register the wxApp instance with Twisted:
    app = MyApp(0)
    reactor.registerWxApp(app)

    # start the event loop:
    reactor.run()

if __name__ == '__main__':
    demo()

wxdemo.py

Screenshots:

Schnelle Entwicklung mit RAD-Tools

wxGlade

URLs:

Screenshots:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6.3 on Fri Jun 06 02:40:48 2008

import wx

# begin wxGlade: extracode
# end wxGlade

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.themainpanel = wx.Panel(self, -1)
        self.label_name = wx.StaticText(self.themainpanel, -1, "Username")
        self.text_ctrl_username = wx.TextCtrl(self.themainpanel, -1, "")
        self.label_password = wx.StaticText(self.themainpanel, -1, "Password")
        self.text_ctrl_password = wx.TextCtrl(self.themainpanel, -1, "",
                                              style=wx.TE_PASSWORD)
        self.button_OK = wx.Button(self.themainpanel, wx.ID_OK, "")
        self.button_Cancel = wx.Button(self.themainpanel, wx.ID_CANCEL, "")
        self.thestatusbar = self.CreateStatusBar(2, 0)
        
        # Menu Bar
        self.themenubar = wx.MenuBar()
        self.fileMenu = wx.Menu()
        self.newMenu = wx.MenuItem(self.fileMenu, wx.ID_NEW,
                                   "&New", "", wx.ITEM_NORMAL)
        self.fileMenu.AppendItem(self.newMenu)
        self.openMenu = wx.MenuItem(self.fileMenu, wx.ID_OPEN,
                                    "&Open", "", wx.ITEM_NORMAL)
        self.fileMenu.AppendItem(self.openMenu)
        self.saveMenu = wx.MenuItem(self.fileMenu, wx.ID_SAVE,
                                    "&Save", "", wx.ITEM_NORMAL)
        self.fileMenu.AppendItem(self.saveMenu)
        self.saveMenu = wx.MenuItem(self.fileMenu, wx.ID_SAVEAS,
                                    "Save &As...", "", wx.ITEM_NORMAL)
        self.fileMenu.AppendItem(self.saveMenu)
        self.fileMenu.AppendSeparator()
        self.quitMenu = wx.MenuItem(self.fileMenu, wx.ID_EXIT,
                                    "&Quit", "", wx.ITEM_NORMAL)
        self.fileMenu.AppendItem(self.quitMenu)
        self.themenubar.Append(self.fileMenu, "&File")
        self.helpMenu = wx.Menu()
        self.themenubar.Append(self.helpMenu, "&Help")
        self.SetMenuBar(self.themenubar)
        # Menu Bar end

        self.__set_properties()
        self.__do_layout()
        # end wxGlade

    def __set_properties(self):
        # begin wxGlade: MyFrame.__set_properties
        self.SetTitle("My first wxGlade Demo")
        self.button_OK.SetDefault()
        self.thestatusbar.SetStatusWidths([-1, 80])
        # statusbar fields
        thestatusbar_fields = ["Created with wxGlade!", "Ready"]
        for i in range(len(thestatusbar_fields)):
            self.thestatusbar.SetStatusText(thestatusbar_fields[i], i)
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame.__do_layout
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        grid_sizer_1 = wx.GridSizer(3, 2, 2, 3)
        grid_sizer_1.Add(self.label_name, 0, 0, 0)
        grid_sizer_1.Add(self.text_ctrl_username, 0, 0, 0)
        grid_sizer_1.Add(self.label_password, 0, 0, 0)
        grid_sizer_1.Add(self.text_ctrl_password, 0, 0, 0)
        grid_sizer_1.Add(self.button_OK, 0, 0, 0)
        grid_sizer_1.Add(self.button_Cancel, 0, 0, 0)
        self.themainpanel.SetSizer(grid_sizer_1)
        sizer_1.Add(self.themainpanel, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_1)
        sizer_1.Fit(self)
        self.Layout()
        # end wxGlade

# end of class MyFrame

if __name__ == "__main__":
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame_1 = MyFrame(None, -1, "")
    app.SetTopWindow(frame_1)
    frame_1.Show()
    app.MainLoop()

myApp.py

<?xml version="1.0"?>
<!-- generated by wxGlade 0.6.3 on Fri Jun 06 02:40:44 2008 -->

<application path="E:\Documents and Settings\farid\myApp.py" name="" class="" option="0" language="python" top_window="frame_1" encoding="UTF-8" use_gettext="0" overwrite="1" use_new_namespace="1" for_version="2.8" is_template="0">
    <object class="MyFrame" name="frame_1" base="EditFrame">
        <style>wxDEFAULT_FRAME_STYLE</style>
        <title>My first wxGlade Demo</title>
        <menubar>1</menubar>
        <statusbar>1</statusbar>
        <object class="wxBoxSizer" name="sizer_1" base="EditBoxSizer">
            <orient>wxVERTICAL</orient>
            <object class="sizeritem">
                <flag>wxEXPAND</flag>
                <border>0</border>
                <option>1</option>
                <object class="wxPanel" name="themainpanel" base="EditPanel">
                    <style>wxTAB_TRAVERSAL</style>
                    <object class="wxGridSizer" name="grid_sizer_1" base="EditGridSizer">
                        <hgap>3</hgap>
                        <rows>3</rows>
                        <cols>2</cols>
                        <vgap>2</vgap>
                        <object class="sizeritem">
                            <border>0</border>
                            <option>0</option>
                            <object class="wxStaticText" name="label_name" base="EditStaticText">
                                <attribute>1</attribute>
                                <label>Username</label>
                            </object>
                        </object>
                        <object class="sizeritem">
                            <border>0</border>
                            <option>0</option>
                            <object class="wxTextCtrl" name="text_ctrl_username" base="EditTextCtrl">
                            </object>
                        </object>
                        <object class="sizeritem">
                            <border>0</border>
                            <option>0</option>
                            <object class="wxStaticText" name="label_password" base="EditStaticText">
                                <attribute>1</attribute>
                                <label>Password</label>
                            </object>
                        </object>
                        <object class="sizeritem">
                            <border>0</border>
                            <option>0</option>
                            <object class="wxTextCtrl" name="text_ctrl_password" base="EditTextCtrl">
                                <style>wxTE_PASSWORD</style>
                            </object>
                        </object>
                        <object class="sizeritem">
                            <border>0</border>
                            <option>0</option>
                            <object class="wxButton" name="button_OK" base="EditButton">
                                <stockitem>OK</stockitem>
                                <default>1</default>
                                <label>&amp;OK</label>
                            </object>
                        </object>
                        <object class="sizeritem">
                            <border>0</border>
                            <option>0</option>
                            <object class="wxButton" name="button_Cancel" base="EditButton">
                                <stockitem>CANCEL</stockitem>
                                <label>&amp;Cancel</label>
                            </object>
                        </object>
                    </object>
                </object>
            </object>
        </object>
        <object class="wxStatusBar" name="thestatusbar" base="EditStatusBar">
            <fields>
                <field width="-1">Created with wxGlade!</field>
                <field width="80">Ready</field>
            </fields>
        </object>
        <object class="wxMenuBar" name="themenubar" base="EditMenuBar">
            <menus>
                <menu name="fileMenu" label="&amp;File">
                    <item>
                        <label>&amp;New</label>
                        <id>wx.ID_NEW</id>
                        <name>newMenu</name>
                    </item>
                    <item>
                        <label>&amp;Open</label>
                        <id>wx.ID_OPEN</id>
                        <name>openMenu</name>
                    </item>
                    <item>
                        <label>&amp;Save</label>
                        <id>wx.ID_SAVE</id>
                        <name>saveMenu</name>
                    </item>
                    <item>
                        <label>Save &amp;As...</label>
                        <id>wx.ID_SAVEAS</id>
                        <name>saveMenu</name>
                    </item>
                    <item>
                        <label>---</label>
                        <id>---</id>
                        <name>---</name>
                    </item>
                    <item>
                        <label>&amp;Quit</label>
                        <id>wx.ID_EXIT</id>
                        <name>quitMenu</name>
                    </item>
                </menu>
                <menu name="helpMenu" itemid="wx.ID_HELP" label="&amp;Help">
                </menu>
            </menus>
        </object>
    </object>
</application>

myApp.wxg

XRC-Ressourcen

<?xml version="1.0" encoding="UTF-8"?>
<!-- generated by wxGlade 0.6.3 on Fri Jun 06 03:05:06 2008 -->

<resource version="2.3.0.1">
    <object class="wxFrame" name="frame_1" subclass="MyFrame">
        <style>wxDEFAULT_FRAME_STYLE</style>
        <title>My first wxGlade Demo</title>
        <object class="wxBoxSizer">
            <orient>wxVERTICAL</orient>
            <object class="sizeritem">
                <option>1</option>
                <flag>wxEXPAND</flag>
                <object class="wxPanel" name="themainpanel">
                    <style>wxTAB_TRAVERSAL</style>
                    <object class="wxGridSizer">
                        <hgap>3</hgap>
                        <rows>3</rows>
                        <cols>2</cols>
                        <vgap>2</vgap>
                        <object class="sizeritem">
                            <object class="wxStaticText" name="label_name">
                                <label>Username</label>
                            </object>
                        </object>
                        <object class="sizeritem">
                            <object class="wxTextCtrl" name="text_ctrl_username">
                            </object>
                        </object>
                        <object class="sizeritem">
                            <object class="wxStaticText" name="label_password">
                                <label>Password</label>
                            </object>
                        </object>
                        <object class="sizeritem">
                            <object class="wxTextCtrl" name="text_ctrl_password">
                                <style>wxTE_PASSWORD</style>
                            </object>
                        </object>
                        <object class="sizeritem">
                            <object class="wxButton" name="wxID_OK">
                                <default>1</default>
                            </object>
                        </object>
                        <object class="sizeritem">
                            <object class="wxButton" name="wxID_CANCEL">
                            </object>
                        </object>
                    </object>
                </object>
            </object>
        </object>
        <object class="wxStatusBar" name="thestatusbar">
            <fields>2</fields>
            <widths>-1, 80</widths>
        </object>
        <object class="wxMenuBar" name="themenubar">
            <object class="wxMenu" name="fileMenu">
                <label>_File</label>
                <object class="wxMenuItem" name="newMenu">
                    <label>_New</label>
                </object>
                <object class="wxMenuItem" name="openMenu">
                    <label>_Open</label>
                </object>
                <object class="wxMenuItem" name="saveMenu">
                    <label>_Save</label>
                </object>
                <object class="wxMenuItem" name="saveMenu">
                    <label>Save _As...</label>
                </object>
                <object class="separator"/>
                <object class="wxMenuItem" name="quitMenu">
                    <label>_Quit</label>
                </object>
            </object>
            <object class="wxMenuItem" name="helpMenu">
                <label>_Help</label>
            </object>
        </object>
    </object>
</resource>

myApp.xrc

URLs:

PyQt4

PyQt4 installieren

URLs:

Installation unter Unix

Erste Schritte in PyQt4

Screenshots:

Wie finde ich mich in PyQt4 und Qt4 zurecht?

Screenshots:

Ein Fenster mit einem Button

#!/usr/bin/env python
# pyqt4_tutorial_t2.py -- PyQt tutorial 2
# From: /usr/local/share/examples/py-qt4/tutorial/t2.py

import sys
from PyQt4 import QtCore, QtGui

app = QtGui.QApplication(sys.argv)

quit = QtGui.QPushButton("Quit")
quit.resize(75, 30)
quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))

QtCore.QObject.connect(quit, QtCore.SIGNAL("clicked()"),
                       app, QtCore.SLOT("quit()"))

quit.show()
sys.exit(app.exec_())

pyqt4_tutorial_t2.py

Screenshots:

Geometrie, Signale und Slots

#!/usr/bin/env python
# pyqt4_tutorial_t5.py -- PyQt tutorial 5
# From: /usr/local/share/examples/py-qt4/tutorial/t5.py

import sys
from PyQt4 import QtCore, QtGui

class MyWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        quit = QtGui.QPushButton("Quit")
        quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))

        lcd = QtGui.QLCDNumber(2)

        slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        slider.setRange(0, 99)
        slider.setValue(0)

        self.connect(quit, QtCore.SIGNAL("clicked()"),
                     QtGui.qApp, QtCore.SLOT("quit()"))
        self.connect(slider, QtCore.SIGNAL("valueChanged(int)"),
                     lcd, QtCore.SLOT("display(int)"))

        layout = QtGui.QVBoxLayout()
        layout.addWidget(quit);
        layout.addWidget(lcd);
        layout.addWidget(slider);
        self.setLayout(layout);

if __name__ == '__main__':
	app = QtGui.QApplication(sys.argv)
	widget = MyWidget()
	widget.show()
	sys.exit(app.exec_())

pyqt4_tutorial_t5.py

Screenshots:

Responsive GUIs

Ein HTTP-Downloader

Das folgende Programm wird stückweise in diesem Abschnitt eingeführt und erklärt. Hier steht es nochmal als Ganzes:

#!/usr/bin/env python
# pyqt4_network_http.py -- PyQt4 port of the network/http example from Qt v4.x
# From: /usr/local/share/examples/py-qt4/network/http.py

import sys
from PyQt4 import QtCore, QtGui, QtNetwork

class HttpWindow(QtGui.QDialog):
    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        
        self.urlLineEdit = QtGui.QLineEdit("http://www.ietf.org/iesg/1rfc_index.txt")
    
        self.urlLabel = QtGui.QLabel(self.tr("&URL:"))
        self.urlLabel.setBuddy(self.urlLineEdit)
        self.statusLabel = QtGui.QLabel(self.tr("Please enter the URL of a file "
                                                "you want to download."))
    
        self.quitButton = QtGui.QPushButton(self.tr("Quit"))
        self.downloadButton = QtGui.QPushButton(self.tr("Download"))
        self.downloadButton.setDefault(True)
    
        self.progressDialog = QtGui.QProgressDialog(self)
    
        self.http = QtNetwork.QHttp(self)
        self.outFile = None
        self.httpGetId = 0
        self.httpRequestAborted = False
    
        self.connect(self.urlLineEdit, QtCore.SIGNAL("textChanged(QString &)"),
                     self.enableDownloadButton)
        self.connect(self.http, QtCore.SIGNAL("requestFinished(int, bool)"),
                     self.httpRequestFinished)
        self.connect(self.http, QtCore.SIGNAL("dataReadProgress(int, int)"),
                     self.updateDataReadProgress)
        self.connect(self.http, QtCore.SIGNAL("responseHeaderReceived(QHttpResponseHeader &)"),
                     self.readResponseHeader)
        self.connect(self.progressDialog, QtCore.SIGNAL("canceled()"),
                     self.cancelDownload)
        self.connect(self.downloadButton, QtCore.SIGNAL("clicked()"),
                     self.downloadFile)
        self.connect(self.quitButton, QtCore.SIGNAL("clicked()"),
                     self, QtCore.SLOT("close()"))
    
        topLayout = QtGui.QHBoxLayout()
        topLayout.addWidget(self.urlLabel)
        topLayout.addWidget(self.urlLineEdit)
    
        buttonLayout = QtGui.QHBoxLayout()
        buttonLayout.addStretch(1)
        buttonLayout.addWidget(self.downloadButton)
        buttonLayout.addWidget(self.quitButton)
    
        mainLayout = QtGui.QVBoxLayout()
        mainLayout.addLayout(topLayout)
        mainLayout.addWidget(self.statusLabel)
        mainLayout.addLayout(buttonLayout)
        self.setLayout(mainLayout)
    
        self.setWindowTitle(self.tr("HTTP"))
        self.urlLineEdit.setFocus()

    def enableDownloadButton(self):
        self.downloadButton.setEnabled(not self.urlLineEdit.text().isEmpty())

    def downloadFile(self):
        url = QtCore.QUrl(self.urlLineEdit.text())
        fileInfo = QtCore.QFileInfo(url.path())
        fileName = QtCore.QString(fileInfo.fileName())
    
        if QtCore.QFile.exists(fileName):
            QtGui.QMessageBox.information(self, self.tr("HTTP"), self.tr(
                                          "There already exists a file called %1 "
                                          "in the current directory.").arg(fileName))
            return

        self.outFile = QtCore.QFile(fileName)
        if  not self.outFile.open(QtCore.QIODevice.WriteOnly):
            QtGui.QMessageBox.information(self, self.tr("HTTP"),
                                          self.tr("Unable to save the file %1: %2.")
                                          .arg(fileName).arg(self.outFile.errorString()))
            self.outFile = None
            return
        
        if url.port() != -1:
            self.http.setHost(url.host(), url.port())
        else:
            self.http.setHost(url.host(), 80)
        if  not url.userName().isEmpty():
            self.http.setUser(url.userName(), url.password())
    
        self.httpRequestAborted = False
        self.httpGetId = self.http.get(url.path(), self.outFile)
    
        self.progressDialog.setWindowTitle(self.tr("HTTP"))
        self.progressDialog.setLabelText(self.tr("Downloading %1.").arg(fileName))
        self.downloadButton.setEnabled(False)

    def cancelDownload(self):
        self.statusLabel.setText(self.tr("Download canceled."))
        self.httpRequestAborted = True
        self.http.abort()
        self.downloadButton.setEnabled(True)

    def readResponseHeader(self, responseHeader):
        if responseHeader.statusCode() != 200:
            QtGui.QMessageBox.information(self, self.tr("HTTP"),
                                          self.tr("Download failed: %1.")
                                          .arg(responseHeader.reasonPhrase()))
            self.httpRequestAborted = True
            self.progressDialog.hide()
            self.http.abort()
            return

    def updateDataReadProgress(self, bytesRead, totalBytes):
        if self.httpRequestAborted:
            return
    
        self.progressDialog.setMaximum(totalBytes)
        self.progressDialog.setValue(bytesRead)

    def httpRequestFinished(self, requestId, error):
        if self.httpRequestAborted:
            if self.outFile is not None:
                self.outFile.close()
                self.outFile.remove()
                self.outFile = None

            self.progressDialog.hide()
            return

        if requestId != self.httpGetId:
            return
    
        self.progressDialog.hide()
        self.outFile.close()
    
        if error:
            self.outFile.remove()
            QtGui.QMessageBox.information(self, self.tr("HTTP"),
                                          self.tr("Download failed: %1.")
                                          .arg(self.http.errorString()))
        else:
            fileName = QtCore.QFileInfo(QtCore.QUrl(self.urlLineEdit.text()).path()).fileName()
            self.statusLabel.setText(self.tr("Downloaded %1 to current directory.").arg(fileName))

        self.downloadButton.setEnabled(True)
        self.outFile = None
		
if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    httpWin = HttpWindow()
    sys.exit(httpWin.exec_())

pyqt4_network_http.py

Screenshots:

Integration mit Twisted

URLs:

Schnelle Entwicklung mit RAD-Tools

Qt Designer

Screenshots:

<ui version="4.0" >
 <author></author>
 <comment></comment>
 <exportmacro></exportmacro>
 <class>DemoDialog</class>
 <widget class="QDialog" name="DemoDialog" >
  <property name="geometry" >
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle" >
   <string>PyUIC4 Demo Dialog</string>
  </property>
  <layout class="QGridLayout" >
   <property name="margin" >
    <number>9</number>
   </property>
   <property name="spacing" >
    <number>6</number>
   </property>
   <item row="2" column="0" >
    <spacer>
     <property name="orientation" >
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" >
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
   <item row="0" column="0" >
    <spacer>
     <property name="orientation" >
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" >
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
   <item rowspan="3" row="0" column="1" >
    <widget class="QListWidget" name="list" />
   </item>
   <item row="1" column="0" >
    <layout class="QVBoxLayout" >
     <property name="margin" >
      <number>0</number>
     </property>
     <property name="spacing" >
      <number>6</number>
     </property>
     <item>
      <widget class="QPushButton" name="button1" >
       <property name="text" >
        <string>Add items</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="button2" >
       <property name="text" >
        <string>Clear list</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
   <item row="3" column="0" colspan="2" >
    <layout class="QHBoxLayout" >
     <property name="margin" >
      <number>0</number>
     </property>
     <property name="spacing" >
      <number>6</number>
     </property>
     <item>
      <spacer>
       <property name="orientation" >
        <enum>Qt::Horizontal</enum>
       </property>
       <property name="sizeHint" >
        <size>
         <width>131</width>
         <height>31</height>
        </size>
       </property>
      </spacer>
     </item>
     <item>
      <widget class="QPushButton" name="okButton" >
       <property name="text" >
        <string>OK</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <pixmapfunction></pixmapfunction>
 <resources/>
 <connections>
  <connection>
   <sender>okButton</sender>
   <signal>clicked()</signal>
   <receiver>DemoDialog</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel" >
     <x>369</x>
     <y>256</y>
    </hint>
    <hint type="destinationlabel" >
     <x>96</x>
     <y>254</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>button2</sender>
   <signal>clicked()</signal>
   <receiver>list</receiver>
   <slot>clear()</slot>
   <hints>
    <hint type="sourcelabel" >
     <x>92</x>
     <y>112</y>
    </hint>
    <hint type="destinationlabel" >
     <x>279</x>
     <y>123</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>

pyqt4_demo.ui

.ui-Dateien laden

Aus pyqt4_demo.ui wird mit uic die Datei pyqt4_demo.cpp erzeugt:

/********************************************************************************
** Form generated from reading ui file 'demo.ui'
**
** Created: Mon Jun 9 04:34:50 2008
**      by: Qt User Interface Compiler version 4.3.4
**
** WARNING! All changes made in this file will be lost when recompiling ui file!
********************************************************************************/

#ifndef UI_DEMO_H
#define UI_DEMO_H

#include <QtCore/QVariant>
#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QButtonGroup>
#include <QtGui/QDialog>
#include <QtGui/QGridLayout>
#include <QtGui/QHBoxLayout>
#include <QtGui/QListWidget>
#include <QtGui/QPushButton>
#include <QtGui/QSpacerItem>
#include <QtGui/QVBoxLayout>

class Ui_DemoDialog
{
public:
    QGridLayout *gridLayout;
    QSpacerItem *spacerItem;
    QSpacerItem *spacerItem1;
    QListWidget *list;
    QVBoxLayout *vboxLayout;
    QPushButton *button1;
    QPushButton *button2;
    QHBoxLayout *hboxLayout;
    QSpacerItem *spacerItem2;
    QPushButton *okButton;

    void setupUi(QDialog *DemoDialog)
    {
    if (DemoDialog->objectName().isEmpty())
        DemoDialog->setObjectName(QString::fromUtf8("DemoDialog"));
    DemoDialog->resize(400, 300);
    gridLayout = new QGridLayout(DemoDialog);
#ifndef Q_OS_MAC
    gridLayout->setSpacing(6);
#endif
#ifndef Q_OS_MAC
    gridLayout->setMargin(9);
#endif
    gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
    spacerItem = new QSpacerItem(20, 40, QSizePolicy::Minimum,
                                 QSizePolicy::Expanding);

    gridLayout->addItem(spacerItem, 2, 0, 1, 1);

    spacerItem1 = new QSpacerItem(20, 40, QSizePolicy::Minimum,
	                              QSizePolicy::Expanding);

    gridLayout->addItem(spacerItem1, 0, 0, 1, 1);

    list = new QListWidget(DemoDialog);
    list->setObjectName(QString::fromUtf8("list"));

    gridLayout->addWidget(list, 0, 1, 3, 1);

    vboxLayout = new QVBoxLayout();
#ifndef Q_OS_MAC
    vboxLayout->setSpacing(6);
#endif
    vboxLayout->setMargin(0);
    vboxLayout->setObjectName(QString::fromUtf8("vboxLayout"));
    button1 = new QPushButton(DemoDialog);
    button1->setObjectName(QString::fromUtf8("button1"));

    vboxLayout->addWidget(button1);

    button2 = new QPushButton(DemoDialog);
    button2->setObjectName(QString::fromUtf8("button2"));

    vboxLayout->addWidget(button2);


    gridLayout->addLayout(vboxLayout, 1, 0, 1, 1);

    hboxLayout = new QHBoxLayout();
#ifndef Q_OS_MAC
    hboxLayout->setSpacing(6);
#endif
    hboxLayout->setMargin(0);
    hboxLayout->setObjectName(QString::fromUtf8("hboxLayout"));
    spacerItem2 = new QSpacerItem(131, 31, QSizePolicy::Expanding,
	                              QSizePolicy::Minimum);

    hboxLayout->addItem(spacerItem2);

    okButton = new QPushButton(DemoDialog);
    okButton->setObjectName(QString::fromUtf8("okButton"));

    hboxLayout->addWidget(okButton);


    gridLayout->addLayout(hboxLayout, 3, 0, 1, 2);


    retranslateUi(DemoDialog);
    QObject::connect(okButton, SIGNAL(clicked()), DemoDialog, SLOT(accept()));
    QObject::connect(button2, SIGNAL(clicked()), list, SLOT(clear()));

    QMetaObject::connectSlotsByName(DemoDialog);
    } // setupUi

    void retranslateUi(QDialog *DemoDialog)
    {
    DemoDialog->setWindowTitle(QApplication::translate("DemoDialog",
	           "PyUIC4 Demo Dialog", 0, QApplication::UnicodeUTF8));
    button1->setText(QApplication::translate("DemoDialog",
	           "Add items", 0, QApplication::UnicodeUTF8));
    button2->setText(QApplication::translate("DemoDialog",
	           "Clear list", 0, QApplication::UnicodeUTF8));
    okButton->setText(QApplication::translate("DemoDialog",
	           "OK", 0, QApplication::UnicodeUTF8));
    Q_UNUSED(DemoDialog);
    } // retranslateUi
};

namespace Ui {
    class DemoDialog: public Ui_DemoDialog {};
} // namespace Ui

#endif // UI_DEMO_H

pyqt4_demo.cpp

Und pyuic4 erzeugt daraus die Datei pyqt4_demo.py, die so aussieht:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'demo.ui'
#
# Created: Mon Jun 09 04:45:23 2008
#      by: PyQt4 UI code generator 4.3.4-snapshot-20080328
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

class Ui_DemoDialog(object):
    def setupUi(self, DemoDialog):
        DemoDialog.setObjectName("DemoDialog")
        DemoDialog.resize(QtCore.QSize(QtCore.QRect(0,0,400,300).size())
		                      .expandedTo(DemoDialog.minimumSizeHint()))

        self.gridlayout = QtGui.QGridLayout(DemoDialog)
        self.gridlayout.setMargin(9)
        self.gridlayout.setSpacing(6)
        self.gridlayout.setObjectName("gridlayout")

        spacerItem = QtGui.QSpacerItem(20,40,QtGui.QSizePolicy.Minimum,
		                               QtGui.QSizePolicy.Expanding)
        self.gridlayout.addItem(spacerItem,2,0,1,1)

        spacerItem1 = QtGui.QSpacerItem(20,40,QtGui.QSizePolicy.Minimum,
		                                QtGui.QSizePolicy.Expanding)
        self.gridlayout.addItem(spacerItem1,0,0,1,1)

        self.list = QtGui.QListWidget(DemoDialog)
        self.list.setObjectName("list")
        self.gridlayout.addWidget(self.list,0,1,3,1)

        self.vboxlayout = QtGui.QVBoxLayout()
        self.vboxlayout.setMargin(0)
        self.vboxlayout.setSpacing(6)
        self.vboxlayout.setObjectName("vboxlayout")

        self.button1 = QtGui.QPushButton(DemoDialog)
        self.button1.setObjectName("button1")
        self.vboxlayout.addWidget(self.button1)

        self.button2 = QtGui.QPushButton(DemoDialog)
        self.button2.setObjectName("button2")
        self.vboxlayout.addWidget(self.button2)
        self.gridlayout.addLayout(self.vboxlayout,1,0,1,1)

        self.hboxlayout = QtGui.QHBoxLayout()
        self.hboxlayout.setMargin(0)
        self.hboxlayout.setSpacing(6)
        self.hboxlayout.setObjectName("hboxlayout")

        spacerItem2 = QtGui.QSpacerItem(131,31,QtGui.QSizePolicy.Expanding,
		                                QtGui.QSizePolicy.Minimum)
        self.hboxlayout.addItem(spacerItem2)

        self.okButton = QtGui.QPushButton(DemoDialog)
        self.okButton.setObjectName("okButton")
        self.hboxlayout.addWidget(self.okButton)
        self.gridlayout.addLayout(self.hboxlayout,3,0,1,2)

        self.retranslateUi(DemoDialog)
        QtCore.QObject.connect(self.okButton,QtCore.SIGNAL("clicked()"),
		                       DemoDialog.accept)
        QtCore.QObject.connect(self.button2,QtCore.SIGNAL("clicked()"),
		                       self.list.clear)
        QtCore.QMetaObject.connectSlotsByName(DemoDialog)

    def retranslateUi(self, DemoDialog):
        DemoDialog.setWindowTitle(QtGui.QApplication.translate("DemoDialog",
		        "PyUIC4 Demo Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.button1.setText(QtGui.QApplication.translate("DemoDialog",
		        "Add items", None, QtGui.QApplication.UnicodeUTF8))
        self.button2.setText(QtGui.QApplication.translate("DemoDialog",
		        "Clear list", None, QtGui.QApplication.UnicodeUTF8))
        self.okButton.setText(QtGui.QApplication.translate("DemoDialog",
		        "OK", None, QtGui.QApplication.UnicodeUTF8))

pyqt4_demo.py

#!/usr/bin/env python
# pyqt4_pyuic_load_ui1.py -- dynamically load demo.py
# From: /usr/local/share/examples/py-qt4/pyuic/load_ui1.py

import sys
from PyQt4 import QtGui, uic

app = QtGui.QApplication(sys.argv)
widget = uic.loadUi("demo.ui")
widget.show()
app.exec_()

pyqt4_pyuic_load_ui1.py

#!/usr/bin/env python
# pyqt4_pyuic_load_ui2.py -- Load demo.ui and merge it into a `QDialog` class
# From: /usr/local/share/examples/py-qt4/pyuic/load_ui2.py

import sys
from PyQt4 import QtCore, QtGui, uic

class DemoImpl(QtGui.QDialog):
    def __init__(self, *args):
        QtGui.QWidget.__init__(self, *args)
        uic.loadUi("demo.ui", self)

    @QtCore.pyqtSignature("")
    def on_button1_clicked(self):
        for s in "This is a demo".split(" "):
            self.list.addItem(s)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    widget = DemoImpl()
    widget.show()
    app.exec_()

pyqt4_pyuic_load_ui2.py

#!/usr/bin/env python
# pyqt4_pyuic_compile_on_the_fly.py -- load and compile demo.ui into a class

import sys
from PyQt4 import QtCore, QtGui, uic

app = QtGui.QApplication(sys.argv)
form_class, base_class = uic.loadUiType("demo.ui")

class DemoImpl(QtGui.QDialog, form_class):
    def __init__(self, *args):
        QtGui.QWidget.__init__(self, *args)

        self.setupUi(self)
    
    @QtCore.pyqtSignature("")
    def on_button1_clicked(self):
        for s in "This is a demo".split(" "):
            self.list.addItem(s)

if __name__ == '__main__':
	form = DemoImpl()
	form.show()
	app.exec_()

pyqt4_pyuic_compile_on_the_fly.py

Tkinter

Erste Schritte in Tkinter

#!/usr/bin/env python
# tksimple.py -- A simple 2 button application

import Tkinter
from Tkconstants import *

def build_gui():
    root = Tkinter.Tk()
    
    button1 = Tkinter.Button(root, text="Click Me!", command=button_pressed)
    button2 = Tkinter.Button(root, text="Exit",      command=root.destroy)
    
    button1.pack(side=RIGHT, fill=BOTH, expand=True)
    button2.pack(side=RIGHT, fill=BOTH, expand=True)
    
    return root

def button_pressed():
    print "Button pressed!"

root = build_gui()
root.mainloop()

tksimple.py

Screenshots:

Wie findet man sich in Tkinter zurecht?

URLs:

Note

Die Python-Shell IDLE ist in Tkinter geschrieben. Daher lassen sich im IDLE-Quellcode eine Menge guter Anwendungsbeispiele für Tkinter finden. Übrigens: da IDLE Teil der Python Standard Library ist, lassen sich Komponenten daraus ebenfalls für eigene Projekte selbstverständlich wiederverwenden. (15.08.2008)

Text-basierte GUIs

pythondialog

URLs:

#!/usr/bin/env python
# pydialog_input_password.py -- inputbox, passwordbox, msgbox

import dialog

def verify_credentials(d):
    "Get username / password, and verify credentials"
    (code, username) = d.inputbox("What is your name?")
    (code, password) = d.passwordbox("What is your password?")

    if username == "Falken" and password == "CPE 1704 TKS":
        d.msgbox("You authenticated successfully.")
        return True
    else:
        d.msgbox("Sorry. Authorized personnel only.")
        return False

if __name__ == '__main__':
    d = dialog.Dialog(dialog="cdialog")
    result = verify_credentials(d)
    print "Credentials:", result

pydialog_input_password.py

#!/usr/bin/env python
# pydialog_yesno.py -- yes/no box

import dialog

the_question = '''You are about to nuke the planet.

Do you really, absolutely without any doubts want to do it? Do you
realize that there is no way to undo this action? Please confirm that
you intend to nuke the whole planet now!'''

d = dialog.Dialog(dialog="cdialog")
result = d.yesno(text=the_question, width=40, height=12)
print "Result:", result == d.DIALOG_OK

pydialog_yesno.py

#!/usr/bin/env python
# pydialog_fselect.py -- A file selector box

import dialog

d = dialog.Dialog(dialog="cdialog")
(code, path) = d.fselect(filepath='/usr/local/lib/python2.5/site-packages/',
                         height=10, width=50)

ret = d.msgbox(text="You selected (%d, %s)" % (code, path), width=50)

pydialog_fselect.py

Screenshots:

Low-level APIs

Ein Blick unter die Haube: Rohe Events

GUI-Toolkits vereinfachen eine komplexe API

Signatur der Funktion XCreateWindow:

extern Window XCreateWindow(
    Display*            /* display */,
    Window              /* parent */,
    int                 /* x */,
    int                 /* y */,
    unsigned int        /* width */,
    unsigned int        /* height */,
    unsigned int        /* border_width */,
    int                 /* depth */,
    unsigned int        /* class */,
    Visual*             /* visual */,
    unsigned long       /* valuemask */,
    XSetWindowAttributes*       /* attributes */
);

Signatur der Funktion CreateWindow:

HWND CreateWindow(
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam
);

Zusammenfassung