tags:

views:

974

answers:

3

I'm looking for the wxPython equivalent to my answer for Tcl/Tk examples?. Specifically, I want to see an example of how to create several buttons, each of which runs some external command when clicked. While the process is running I want the output to go to a scrollable wxPython widget.

While the process is running the GUI should not block. Assume, for example, one of the buttons may kick off a development task like building or running unit tests.

A: 

Start a thread when a button is clicked:

try:
    r = threading.Thread(target=self.mycallback)
    r.setDaemon(1)
    r.start()
except:
    print "Error starting thread"
    return False

Use wx.PostEvent and wx.lib.newevent to send messages from the callbacks to the main thread.

This link may be helpful.

kgiannakakis
I appreciate your advice, but I'm looking for a concrete example using wxPython. Assume I know absolutely nothing about wxPython and you'll be very close to the truth.
Bryan Oakley
A: 

Bryan, try something like this:

import subprocess, sys

def doit(cmd):
    #print cmd
    out = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True).stdout
    return out.read()

So when the button is pressed, the command gets run using the subprocess module, and you get the output as a string. You can assign it to a Text Control's value to show it. You might have to out.readfully() or read multiple times to show text progressively.

If the button & text field aren't familiar, then a quick look at the wxPython demo will show you exactly what to do.

Jim Carroll
This doesn't meet one of my criteria: "While the process is running the GUI should not block". In your example the UI will block while you wait for out.read() to complete.
Bryan Oakley
What makes you say that Bryan? Have you tried it? My understanding is you *can* make it block by doing a Popen.wait(), but if you don't block with the wait command, you'll be able to read whatever stdout is available at the time with the read command.http://docs.python.org/library/subprocess.html
Jim Carroll
Bryan, this python stuff is a lot easier than you think it is... even easier than Tk if you give it a chance.
Jim Carroll
Why do I say ti blocks? Because if there's no output, the read will wait until there is output to read. Or do I misunderstand how read works?
Bryan Oakley
@Jim: python is definitely not easier than tcl and tk (unless you know python and don't know tcl). Python might be equally easy as Tcl in many ways, but wxPython is certainly not as easy as Tk.
Bryan Oakley
Jim Carroll
One more comment... in the wxPython demo, search for process in the search box in the lower left. It does an ASYNC run using wxWidget's approach. I would still use the subprocess module, but other than that it's exactly what you're looking for.
Jim Carroll
+2  A: 

Here you are, a complete working example.

import wx
import functools
import threading
import subprocess
import time

class Frame(wx.Frame):
    def __init__(self):
        super(Frame, self).__init__(None, -1, 'Threading Example')
        # add some buttons and a text control
        panel = wx.Panel(self, -1)
        sizer = wx.BoxSizer(wx.VERTICAL)
        for i in range(3):
            name = 'Button %d' % (i+1)
            button = wx.Button(panel, -1, name)
            func = functools.partial(self.on_button, button=name)
            button.Bind(wx.EVT_BUTTON, func)
            sizer.Add(button, 0, wx.ALL, 5)
        text = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE|wx.TE_READONLY)
        self.text = text
        sizer.Add(text, 1, wx.EXPAND|wx.ALL, 5)
        panel.SetSizer(sizer)
    def on_button(self, event, button):
        # create a new thread when a button is pressed
        thread = threading.Thread(target=self.run, args=(button,))
        thread.setDaemon(True)
        thread.start()
    def on_text(self, text):
        self.text.AppendText(text)
    def run(self, button):
        cmd = ['ls', '-lta']
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        for line in proc.stdout:
            wx.CallAfter(self.on_text, line)

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = Frame()
    frame.Show()
    app.MainLoop()
FogleBird
That's a useful example, though not the same as the Tk example I mentioned in my question. This example ignores the part of my request where I say I want to run external commands. Yes, it's fairly easy to extrapolate from your example but I'm not sure a newbie stumbling on this example could figure it out. My hope in posting the question (besides quickly coming up to speed on wxPython) was to show the same small application in both languages. Thanks anyway.
Bryan Oakley
Refactored to show launching a subprocess.
FogleBird