So — here’s my jam. I’m SUPER NEW (i.e. days) into Python and have made some pretty good progress on a GUI for a set of python scripts that I run all the time. It basically builds a database of my music collection. When I run it at the command line, it does a stellar job. I figure it’s a great way to learn python — build a front-end for this thing.
So — the below DOES actually work for me. I hit a problem when the Scan/Update button will stay in a ‘down’ state while the scan gets to work. I learned that, yes, I need to multithread this to get the output from STDOUT to self.LogView in the below. That even works, just not in realtime. I posted about it earlier and got linked to another tutorial on the web, but I am just not getting past this and am getting desperate. Dear Stackflow.com — save me from going insane. If you can make the below work, then I can reverse engineer what you did at least as the tutorials are overly simplified and, yes, I’m in over my head.
The are that is heavy lifting in the below is this:
proc = subprocess.Popen([scanCMD],shell=True,stdout=subprocess.PIPE)
for line in proc.stdout:
wx.CallAfter(self.LogWindow.AppendText(line))
In the button def:
def bt_ScanUpdateClick(self, event):
Chow flings a hail mary
#!/usr/bin/python
################################################################################
# myGUI
################################################################################
# THIS WORKS, BUT DOES NOT MULTITHREAD!
################################################################################
import wx
from wxPython.wx import *
import os
import subprocess
import threading
class myGUI(wx.Frame):
def __init__(self, parent, title):
super(myGUI, self).__init__(parent, title=title,
size=(450, 245)) #360 for logwindow (see below)
panel = wx.Panel(self)
sizer = wx.GridBagSizer(6, 5)
self.currentDirectory = os.getcwd()
# [0] Main Database Text, Entry and Browse Button --------------------------
label_MainDatabase = wx.StaticText(panel, label="Database:")
sizer.Add(label_MainDatabase, pos=(0, 0), flag=wx.LEFT|
wx.ALIGN_CENTER_VERTICAL, border=10)
self.tc_MainDatabase = wx.TextCtrl(panel)
sizer.Add(self.tc_MainDatabase, pos=(0, 1), span=(1, 4), flag=wx.TOP|
wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
bt_MainDatabase = wx.Button(panel, label="Browse...")
sizer.Add(bt_MainDatabase, pos=(0, 5), flag=wx.LEFT|wx.RIGHT|
wx.ALIGN_CENTER_VERTICAL, border=10)
bt_MainDatabase.Bind(wx.EVT_BUTTON, self.bt_MainDatabaseClick,
bt_MainDatabase)
# --------------------------------------------------------------------------
# [1] Paths to scan for new Music ------------------------------------------
self.sb_FoldersToScan = wx.StaticBox(panel, label="Folders to Scan:", size=(200,100))
folderBoxSizer = wx.StaticBoxSizer(self.sb_FoldersToScan, wx.VERTICAL)
self.multiText = wx.TextCtrl(panel, -1,"",size=(300, 100), style=wx.TE_MULTILINE|wx.TE_READONLY)
self.multiText.SetInsertionPoint(0)
folderBoxSizer.Add(self.multiText, flag=wx.EXPAND)
sizer.Add(folderBoxSizer, pos=(1, 0), span=(1, 6), flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=10)
bt_FoldersToScanAdd = wx.Button(panel, label="Add")
bt_FoldersToScanClear = wx.Button(panel, label="Clear")
# --------------------------------------------------------------------------
# [2] Buttons to Add Folder, Clear Scan Area -------------------------------
sizer.Add(bt_FoldersToScanAdd, pos=(2,0), span=(1,2), flag=wx.LEFT|wx.RIGHT|
wx.ALIGN_CENTER_VERTICAL, border=10)
bt_FoldersToScanAdd.Bind(wx.EVT_BUTTON, self.bt_FoldersToScanAddClick, bt_FoldersToScanAdd)
sizer.Add(bt_FoldersToScanClear, pos=(2,5), flag=wx.LEFT|wx.RIGHT|
wx.ALIGN_CENTER_VERTICAL, border=10)
bt_FoldersToScanClear.Bind(wx.EVT_BUTTON, self.bt_FoldersToScanClearClick, bt_FoldersToScanClear)
# --------------------------------------------------------------------------
# [3] Separator line -------------------------------------------------------
hl_SepLine1 = wx.StaticLine(panel, 0, (250, 50), (300,1))
sizer.Add(hl_SepLine1, pos=(3, 0), span=(1, 6), flag=wx.EXPAND, border=10)
# --------------------------------------------------------------------------
# [4] Add Scan Options and Scan Button -------------------------------------
bt_ScanUpdate = wx.Button(panel, label="Scan/Update")
bt_ScanUpdate.Bind(wx.EVT_BUTTON, self.bt_ScanUpdateClick, bt_ScanUpdate)
bt_ScanRepair = wx.Button(panel, label="Repair")
bt_ScanRepair.Bind(wx.EVT_BUTTON, self.bt_ScanRepairClick, bt_ScanRepair)
self.ck_ScanVerbose = wx.CheckBox(panel, label="Verbose")
self.ck_ScanLog = wx.CheckBox(panel, label="Log")
sizer.Add(bt_ScanUpdate, pos=(4,0), span=(1,2), flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=10)
sizer.Add(self.ck_ScanVerbose, pos=(4,2), flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=10)
sizer.Add(self.ck_ScanLog, pos=(4,4), flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=10)
sizer.Add(bt_ScanRepair, pos=(4,5), span=(1,2), flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=10)
# --------------------------------------------------------------------------
# [5] Separator line ------------------------------------------------------
hl_SepLine2 = wx.StaticLine(panel, 0, (250, 50), (300,1))
sizer.Add(hl_SepLine2, pos=(5, 0), span=(1, 6), flag=wx.EXPAND, border=10)
# --------------------------------------------------------------------------
# [6] Output/Log Box -------------------------------------------------------
self.LogWindow = wx.TextCtrl(panel, -1,"",size=(100, 100), style=wx.TE_MULTILINE|wx.TE_READONLY)
self.LogWindow.SetInsertionPoint(0)
sizer.Add(self.LogWindow, pos=(6,0), span=(1,6), flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=10)
# DEBUG
self.multiText.Value = "~/Network/Pictures/Test"
sizer.AddGrowableCol(2)
panel.SetSizer(sizer)
self.Centre()
self.Show()
def bt_MainDatabaseClick(self, event):
# Create a list of filters
filters = 'All files (*.*)|*.*|Text files (*.db)|*.db'
dialog = wxFileDialog ( None, message = 'Select Database File...',
wildcard = filters, style = wxOPEN )
# Open Dialog Box and get Selection
if dialog.ShowModal() == wxID_OK:
selected = dialog.GetFilenames()
for selection in selected:
self.tc_MainDatabase.Value = selection
dialog.Destroy()
def bt_FoldersToScanAddClick(self, event):
dialog = wx.DirDialog(self, "Add a Directory...", style=wx.DD_DEFAULT_STYLE)
if dialog.ShowModal() == wx.ID_OK:
self.multiText.Value += "%s" % dialog.GetPath() + "\n"
dialog.Destroy()
def bt_FoldersToScanClearClick(self, event):
self.multiText.Value = ""
def bt_ScanUpdateClick(self, event):
self.SetSizeWH(450,360)
thread = threading.Thread()
thread.setDaemon(True)
thread.start()
## DEBUG
self.tc_MainDatabase.Value = "test.db"
if self.tc_MainDatabase.Value == "":
self.LogWindow.Value += "ERROR:\tNo database name selected!\n"
else:
scanCMD = "./scan -d " + self.tc_MainDatabase.Value + " "
numLines=0
maxLines=(int(self.multiText.GetNumberOfLines()))
if self.multiText.GetLineText(numLines) == "":
self.LogWindow.Value += "ERROR\tNo folder selected to scan!\n"
else:
self.LogWindow.Value += "Running Scan...\n\n"
while (numLines < maxLines):
scanCMD += str(self.multiText.GetLineText(numLines)) + " "
numLines += 1
self.LogWindow.Value += scanCMD
proc = subprocess.Popen([scanCMD],shell=True,stdout=subprocess.PIPE)
for line in proc.stdout:
wx.CallAfter(self.LogWindow.AppendText(line))
# p = subprocess.Popen([scanCMD],shell=True,stdout=subprocess.PIPE)
# self.LogWindow.Value += p.stdout.readline()
def bt_ScanRepairClick(self, event):
print "Repair clicked"
if __name__ == '__main__':
app = wx.App()
myGUI(None, title="myGUI")
app.MainLoop()
I have never needed to set my thread to a daemon before. In fact, I have only ever subclassed Thread. I’m not a threading expert, so I recommend looking at http://wiki.wxpython.org/LongRunningTasks and trying one of the methods there or use the methodology I used here: http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/ I’ve used the latter several times with no problems. Otherwise, you should cross-post to the official wxPython user’s group, which has some very knowledgeable people there that I’m sure could help you.