wxPython: How to make Dialog resize according to the content of the embedded StaticText - dialog

This is my small program for testing:
import wx
class Test(wx.Dialog):
def __init__(self, parent, title="", caption='', btnList=['OK']):
wx.Dialog.__init__(self, parent, -1, title=title)
self.btnList = btnList
self.parent = parent
sizer = wx.BoxSizer()
self.panel = wx.Panel(self, -1)
sizer.Add(self.panel)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.panel.SetSizer(self.mainSizer)
self.caption = caption
self.setCaption()
self.setButton()
self.SetSizerAndFit(sizer)
def setCaption(self):
caplb = wx.StaticText(self.panel, -1, self.caption)
self.mainSizer.Add(caplb, 1, wx.EXPAND | wx.ALL, 10)
capFont = caplb.GetFont()
capFont.SetPointSize(10)
capFont.SetWeight(wx.FONTWEIGHT_BOLD)
caplb.SetFont(capFont)
caplb.SetForegroundColour(wx.RED)
def setButton(self):
self.btnBox = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(self.btnBox, 0, wx.EXPAND | wx.BOTTOM, 10)
self.btnBox.Add((1, 1), 1, wx.EXPAND)
self.btns = []
for i in range(len(self.btnList)):
self.btns.append(wx.Button(self.panel, -1, self.btnList[i]))
self.btnBox.Add(self.btns[i])
self.btnBox.Add((1, 1), 1, wx.EXPAND)
if __name__ == "__main__":
app = wx.App()
msg = ""
for i in range(8):
msg += "%s: ATTENTION! ATTENTION! ATTENTION! ATTENTION!\n" % i
dlg = Test(None, caption=msg, title="WARNING", btnList=['Yes', 'No'])
dlg.CenterOnParent()
val = dlg.ShowModal()
app.MainLoop()
I want the dialog to spread so that all text in the caplb can be seen. In most of the systems I has worked on this piece of code work well. But when I move to the new system, there is something with the gtk limit to show only half of the caplb. I don't know how to force it to show all of the StaticText. Can you please have a look at that and show me the hint? Thanks ahead!!!

Related

Inner Panel doesnt respond

So my goal is do create a settings Window. On the left side you have the different categories. Which you can switch by clicking on them. On the right side is the panel that will switch the content based on what thing you clicked on.
I implemented it by creating a class for every content panel, instantiate it from the frame and Hide all the panels except the one that was choosen.
My problem is that i cant interact with anything inside the panels that i hide and show. Here is a minimum example that you should be able to run.
# -*- coding: utf-8 -*-
"""
Created on Tue Jul 13 15:54:51 2021
#author: Odatas
"""
import wx
import wx.lib.scrolledpanel as scrolled
#First Panel
class settingsConnection(wx.ScrolledWindow):
def __init__(self,parent,size):
wx.ScrolledWindow.__init__(self,parent=parent,size=size)
self.topLevelSizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.topLevelSizer)
self.infoText = wx.StaticText(self
,label="I'm Panel settingsConnection\n\nUnder construction."
,style=wx.ALIGN_CENTRE_HORIZONTAL)
self.topLevelSizer.Add(self.infoText)
#Second Panel
class settingsGeneral(wx.ScrolledWindow):
def __init__(self,parent,size):
wx.ScrolledWindow.__init__(self,parent=parent,size=size)
self.topLevelSizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.topLevelSizer)
self.infoText = wx.StaticText(self
,label="I'm Panel settingsGeneral\n\nUnder construction."
,style=wx.ALIGN_CENTRE_HORIZONTAL)
self.topLevelSizer.Add(self.infoText)
#Third Panel
class settingsABC(scrolled.ScrolledPanel):
def __init__(self,parent,size):
scrolled.ScrolledPanel.__init__(self,parent=parent,size=size)
self.topLevelSizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.topLevelSizer)
self.firstSizer = wx.BoxSizer(wx.VERTICAL)
self.secondSizer = wx.BoxSizer(wx.VERTICAL)
self.thridSizer = wx.BoxSizer(wx.VERTICAL)
self.SetupScrolling()
self.SetAutoLayout(1)
self.autoStartCheckBox=wx.CheckBox(self, label="Button")
self.autoStartCheckBox.SetValue(True)
self.testButton = wx.Button(self,label="Test")
self.topLevelSizer.Add(self.autoStartCheckBox)
self.topLevelSizer.Add(self.testButton)
self.topLevelSizer.Layout()
self.Fit()
#The main frame that holds all the panels
class settingsWindow(wx.Frame):
FLAG_KILL_ME = False
def __init__(self,parent):
wx.Frame.__init__(self,parent=parent.frame,size=(1000,800),style= wx.CAPTION | wx.CLOSE_BOX |wx.STAY_ON_TOP|wx.FRAME_NO_TASKBAR)
#Event for Closing window
self.Bind(wx.EVT_CLOSE,self.onQuit)
#Event for changing the focus
self.Bind(wx.EVT_LIST_ITEM_FOCUSED,self.onFocusChange)
self.parent=parent
self.panel = wx.Panel(self)
self.panelSize=(800,700)
self.panel.SetBackgroundColour(wx.Colour(255,255,255))
#Contains Everything Level 0
self.topLevelSizer = wx.BoxSizer(wx.VERTICAL)
#Contains Top Part Level 01
self.windowSizer=wx.BoxSizer(wx.HORIZONTAL)
#Contains Bottom part Level 01
self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
#Contains Widgets for Mainpart
self.widgetSizer = wx.BoxSizer(wx.HORIZONTAL)
self.panel.SetSizer(self.topLevelSizer)
self.topLevelSizer.Add(self.windowSizer)
self.topLevelSizer.Add(self.buttonSizer)
self.settingsChooser=wx.ListCtrl(self.panel, id=wx.ID_ANY,style=wx.LC_LIST|wx.LC_SINGLE_SEL,size=(100,700))
self.settingsChooser.AppendColumn("Test")
self.windowSizer.Add(self.settingsChooser)
self.windowSizer.Add(self.widgetSizer)
self.panelList=[]
self.settingsChooser.InsertItem(0,"Allgemein")
self.generalPanel=settingsGeneral(self,self.panelSize)
self.panelList.append(self.generalPanel)
self.widgetSizer.Add(self.generalPanel)
self.settingsChooser.InsertItem(1,"Connection")
self.connectionPanel=settingsConnection(self,self.panelSize)
self.panelList.append(self.connectionPanel)
self.widgetSizer.Add(self.connectionPanel)
self.connectionPanel.Hide()
self.settingsChooser.InsertItem(2,"DLT")
self.dltPanel=settingsABC(self,self.panelSize)
self.panelList.append(self.dltPanel)
self.widgetSizer.Add(self.dltPanel)
self.dltPanel.Hide()
self.currentID=0
self.panel.Fit()
self.Centre()
self.Show()
#self.mythread = threading.Thread(target=self.downloadEndPackageMethod)
#self.mythread.start()
while self.FLAG_KILL_ME is False:
wx.GetApp().Yield()
try:
self.Destroy()
except Exception as e:
pass
#Shows the Selected Panel and hides the current shown.
def onFocusChange(self,event=None):
self.panelList[self.currentID].Hide()
self.panelList[event.GetIndex()].Show()
self.currentID=event.GetIndex()
self.panel.Fit()
self.panel.Layout()
def onQuit(self,event=None):
self.FLAG_KILL_ME = True
self.Hide()
#Main wx.Python App
class Testapp(wx.App):
def __init__(self,redirect=False,filename=None):
wx.App.__init__(self,redirect,filename)
self.frame=wx.Frame(None,title="Test")
self.panel=wx.Panel(self.frame,size=(1000,1000))
self.TopLevelSizer=wx.BoxSizer(wx.VERTICAL)
self.panel.SetSizer(self.TopLevelSizer)
self.settingsButton = wx.Button(self.panel,label="Settings")
self.TopLevelSizer.Add(self.settingsButton)
self.settingsButton.Bind(wx.EVT_BUTTON, self.openSettings)
self.frame.Show()
def openSettings(self,event=None):
settingsWindow(self)
if __name__=='__main__':
app=Testapp()
app.MainLoop()
Edit: Adjustest class "Settings Window" für use with wx.Listbook
class settingsWindow(wx.Frame):
FLAG_KILL_ME = False
def __init__(self,parent):
wx.Frame.__init__(self,parent=parent.frame,size=(1000,800),style= wx.CAPTION | wx.CLOSE_BOX |wx.STAY_ON_TOP|wx.FRAME_NO_TASKBAR)
self.Bind(wx.EVT_CLOSE,self.onQuit)
# self.Bind(wx.EVT_LIST_ITEM_FOCUSED,self.onFocusChange)
self.parent=parent
self.panel = wx.Panel(self)
self.panelSize=(800,700)
self.panel.SetBackgroundColour(wx.Colour(255,255,255))
#Contains Everything Level 0
self.topLevelSizer = wx.BoxSizer(wx.VERTICAL)
#Contains Top Part Level 01
self.windowSizer=wx.BoxSizer(wx.HORIZONTAL)
#Contains Bottom part Level 01
self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
#Contains Widgets for Mainpart
self.widgetSizer = wx.BoxSizer(wx.HORIZONTAL)
self.panel.SetSizer(self.topLevelSizer)
self.topLevelSizer.Add(self.windowSizer)
self.topLevelSizer.Add(self.buttonSizer)
self.windowSizer.Add(self.widgetSizer)
self.settingsBook = wx.Listbook(self.panel)
self.widgetSizer.Add(self.settingsBook)
self.settingsBook.InsertPage(0, settingsGeneral(self,self.panelSize), "Settins")
self.settingsBook.InsertPage(1, settingsConnection(self,self.panelSize), "Connections")
self.settingsBook.InsertPage(2, settingsABC(self,self.panelSize), "ABC")
self.panel.Fit()
self.Centre()
self.Show()
while self.FLAG_KILL_ME is False:
wx.GetApp().Yield()
try:
self.Destroy()
except Exception as e:
pass
def onQuit(self,event=None):
self.FLAG_KILL_ME = True
self.Hide()
Here is a standalone Listbook example taken from a working project.
I've hacked out the meat and left the potatoes. :)
Edit:
I have added a simple main frame from where you activate the configuration code. The main frame contains a counter, in an attempt to convince you, that the MainLoop is still active, as you seem to believe it gets hijacked in some way.
Hopefully, it will be of use.
import wx
import wx.lib.scrolledpanel as scrolled
#from os.path import expanduser
class Test(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Main Window", size=(300, 200))
self.cnt = 0
panel = wx.Panel(self)
button = wx.Button(panel, label="Open Config", pos=(10, 10), size=(120, 30))
text = wx.StaticText(panel, wx.ID_ANY, "Is the main loop still active", pos=(10, 50))
self.counter = wx.TextCtrl(panel, wx.ID_ANY, "", pos=(10, 80), size=(120, 30))
self.Bind(wx.EVT_BUTTON, self.OnConfig, button)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.Show()
self.timer.Start(1000)
def OnConfig(self, event):
frame = Config(parent=self)
def OnTimer(self, event):
self.cnt += 1
self.counter.SetValue(str(self.cnt))
class Config(wx.Frame):
def __init__(self,parent):
wx.Frame.__init__(self,parent, wx.ID_ANY,"Configuration",size=(600,400))
#self.home_dir = expanduser("~")
#self.conf_dir = expanduser("~/fs2")
#self.config_file = self.conf_dir+"/fs2.cfg"
self.parent = parent
# Create the first tab and add it to the listbook
self.panel = wx.Panel(self)
self.lb = wx.Listbook(self.panel)
self.tab1 = scrolled.ScrolledPanel(self.lb, -1)
self.tab1.SetupScrolling()
self.tab1.SetBackgroundColour("Grey")
self.lb.AddPage(self.tab1, "Foot Pedal")
t1_1 = wx.StaticText(self.tab1, wx.ID_ANY, ("Pedal Device"))
self.fc_hidfile = wx.TextCtrl(self.tab1, wx.ID_ANY, "",size=(150, -1))
t1_2 = wx.StaticText(self.tab1, wx.ID_ANY, ("Pedal 1 *"))
self.fc_pedal_1 = wx.ComboBox(self.tab1, wx.ID_ANY, choices=[("Jump Back"), ("Play/Pause"), ("Jump Forward"),("Timestamp"),("Unclear"),("Comment Time stamp"),("Bookmark Time stamp"),("OSD Time stamp"),("Pedal De-activated")],style=wx.CB_READONLY)
t1_3 = wx.StaticText(self.tab1, wx.ID_ANY, ("Pedal 2 *"))
self.fc_pedal_2 = wx.ComboBox(self.tab1, wx.ID_ANY, choices=[("Jump Back"), ("Play/Pause"), ("Jump Forward"),("Timestamp"),("Unclear"),("Comment Time stamp"),("Bookmark Time stamp"),("OSD Time stamp"),("Pedal De-activated")],style=wx.CB_READONLY)
t1_4 = wx.StaticText(self.tab1, wx.ID_ANY, ("Pedal 3 *"))
self.fc_pedal_3 = wx.ComboBox(self.tab1, wx.ID_ANY, choices=[("Jump Back"), ("Play/Pause"), ("Jump Forward"),("Timestamp"),("Unclear"),("Comment Time stamp"),("Bookmark Time stamp"),("OSD Time stamp"),("Pedal De-activated")],style=wx.CB_READONLY)
t1_5 = wx.StaticText(self.tab1, wx.ID_ANY, ("Classic"))
self.fc_classic = wx.SpinCtrl(self.tab1, wx.ID_ANY,"0", min=0, max=2)#, ("Classic Play/Pause"))
t1sizer = wx.FlexGridSizer(10,2,5,5)
t1sizer.Add(t1_1, 0, wx.ALL, 5)
t1sizer.Add(self.fc_hidfile, 0, wx.ALL, 5)
t1sizer.Add(t1_2, 0, wx.ALL, 5)
t1sizer.Add(self.fc_pedal_1, 0, wx.ALL, 5)
t1sizer.Add(t1_3, 0, wx.ALL, 5)
t1sizer.Add(self.fc_pedal_2, 0, wx.ALL, 5)
t1sizer.Add(t1_4, 0, wx.ALL, 5)
t1sizer.Add(self.fc_pedal_3, 0, wx.ALL, 5)
t1sizer.Add(t1_5, 0, wx.ALL, 5)
t1sizer.Add(self.fc_classic, 0, wx.ALL, 5)
self.tab1.SetSizer(t1sizer)
self.tab2 = scrolled.ScrolledPanel(self.lb, -1)
self.tab2.SetupScrolling()
self.tab2.SetBackgroundColour("Grey")
self.lb.AddPage(self.tab2, "Control")
t2_1 = wx.StaticText(self.tab2, wx.ID_ANY, ("Include Hours in Time display"))
self.fc_time_disp = wx.ComboBox(self.tab2, wx.ID_ANY, choices=[("H"),("M")],style=wx.CB_READONLY)
t2_2 = wx.StaticText(self.tab2, wx.ID_ANY, ("Jump Length (secs) *"))
self.fc_jump_length = wx.SpinCtrl(self.tab2, wx.ID_ANY, "3", min=1, max=15)
t2_3 = wx.StaticText(self.tab2, wx.ID_ANY, ("Long Jump *"))
self.fc_l_jump_length = wx.SpinCtrl(self.tab2, wx.ID_ANY,value="", min=0, max=60)
t2_4 = wx.StaticText(self.tab2, wx.ID_ANY, ("Extra Long Jump *"))
self.fc_xl_jump_length = wx.SpinCtrl(self.tab2, wx.ID_ANY,value="", min=0, max=600)
t2_5 = wx.StaticText(self.tab2, wx.ID_ANY, ("Pause Jump (secs) *"))
self.fc_pause_jump = wx.SpinCtrl(self.tab2, wx.ID_ANY, "0", min=0, max=5)
t2_6 = wx.StaticText(self.tab2, wx.ID_ANY, ("Instant Loop length *"))
self.fc_ill = wx.SpinCtrl(self.tab2, wx.ID_ANY, "10", min=3, max=20)
t2sizer = wx.FlexGridSizer(10,2,5,5)
t2sizer.Add(t2_1, 0, wx.ALL, 5)
t2sizer.Add(self.fc_time_disp, 0, wx.ALL, 5)
t2sizer.Add(t2_2, 0, wx.ALL, 5)
t2sizer.Add(self.fc_jump_length, 0, wx.ALL, 5)
t2sizer.Add(t2_3, 0, wx.ALL, 5)
t2sizer.Add(self.fc_l_jump_length, 0, wx.ALL, 5)
t2sizer.Add(t2_4, 0, wx.ALL, 5)
t2sizer.Add(self.fc_xl_jump_length, 0, wx.ALL, 5)
t2sizer.Add(t2_5, 0, wx.ALL, 5)
t2sizer.Add(self.fc_pause_jump, 0, wx.ALL, 5)
t2sizer.Add(t2_6, 0, wx.ALL, 5)
t2sizer.Add(self.fc_ill, 0, wx.ALL, 5)
self.tab2.SetSizer(t2sizer)
# Create and add more tabs....
# read the config file and load in the data
# cfg = ConfigObj(infile = self.config_file)
# try:
# r_hidfile = cfg["control"]["HID_FILE_ID"]
# except:
# r_hid_file = "/dev/input/eventx"
# self.fc_hidfile.SetValue(r_hid_file)
#
# For demo purposes set fixed values
self.fc_hidfile.SetValue('/dev/input/eventx')
self.fc_time_disp.SetStringSelection('H')
self.fc_jump_length.SetValue(2)
self.fc_l_jump_length.SetValue(10)
self.fc_xl_jump_length.SetValue(15)
self.fc_pause_jump.SetValue(1)
self.fc_classic.SetValue(0)
self.fc_pedal_1.SetSelection(0)
self.fc_pedal_2.SetSelection(1)
self.fc_pedal_3.SetSelection(2)
self.fc_ill.SetValue(5)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.lb, 1, wx.ALL|wx.EXPAND, 5)
self.fc_Save = wx.Button(self.panel, wx.ID_ANY, "Save")
self.fc_Quit = wx.Button(self.panel, wx.ID_ANY, "Quit")
self.help_button = wx.Button(self.panel, wx.ID_ANY, "Help")
button_sizer = wx.BoxSizer(wx.HORIZONTAL)
button_sizer.Add(self.fc_Save)
button_sizer.Add(self.fc_Quit)
button_sizer.Add(self.help_button)
sizer.Add(button_sizer)
self.panel.SetSizer(sizer)
self.fc_Save.Bind(wx.EVT_BUTTON, self.OnSave)
self.fc_Quit.Bind(wx.EVT_BUTTON, self.OnQuit)
self.help_button.Bind(wx.EVT_BUTTON, self.OnHelp)
self.Layout()
self.Show()
def OnQuit(self,event):
self.Destroy()
self.Close()
def OnHelp(self, event):
# ShowHelp(parent=None,section='fs2_Config1.pdf')
return
def OnSave(self,event):
#cfg = ConfigObj(infile = self.config_file, create_empty=True, write_empty_values=True, list_values=False)
# write back new configuartion values here
#cfg["control"]["FOOTPEDAL_VPID_LOCK"] = footpedal_vpid_lock
#cfg.write()
self.Destroy()
if __name__ == "__main__":
app = wx.App()
frame = Test(None)
app.MainLoop()
You'll notice, that despite the Config window being active, the Main windows counter is still active.

wxglade list tree not showing properly

I am new to wxglade and trying to create a form to show tabs and split window. There are going to be tree lists on the left pane. The issue is that when I open the form, it shows the treelist but the list cannot be collapsed or expanded. When I go to 2nd tab and come back to the first, tree list is shown in small box.
import wx
# Define the tab content as classes:
class TabOne(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.SetSize((400, 300))
self.window_1 = wx.SplitterWindow(self, wx.ID_ANY)
self.window_1_pane_1 = wx.ScrolledWindow(self.window_1, wx.ID_ANY, style=wx.BORDER_RAISED | wx.TAB_TRAVERSAL)
#self.button_1 = wx.Button(self.window_1_pane_1, wx.ID_ANY, "button_1")
#t = wx.StaticText(self, -1, "This is the first tab", (100,100)) start list
il = wx.ImageList(16,16)
self.fldridx = il.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16,16)))
self.fldropenidx = il.Add(wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16,16)))
self.fileidx = il.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16,16)))
self.tree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_EDIT_LABELS)
self.tree.AssignImageList(il)
root = self.tree.AddRoot("Server")
self.tree.SetItemData(root, None)
self.tree.SetItemImage(root, self.fldridx,wx.TreeItemIcon_Normal)
self.tree.SetItemImage(root, self.fldropenidx,wx.TreeItemIcon_Expanded)
tree = ["A","B","C","D","E","F","G","H"]
self.AddTreeNodes(root, tree)
self.tree.Expand(root)
def AddTreeNodes(self, parentItem, items):
for item in items:
if type(item) == str:
newItem = self.tree.AppendItem(parentItem, item)
self.tree.SetItemData(newItem, None)
#self.tree.SetItemImage(newItem, self.fileidx,wx.TreeItemIcon_Normal)
else:
newItem = self.tree.AppendItem(parentItem, item[0])
self.tree.SetItemPyData(newItem, None)
#self.tree.SetItemImage(newItem, self.fldridx,wx.TreeItemIcon_Normal)
self.tree.SetItemImage(newItem, self.fldropenidx,wx.TreeItemIcon_Expanded)
self.AddTreeNodes(newItem, item[1])
#end list
self.window_1_pane_2 = wx.Panel(self.window_1, wx.ID_ANY)
self.window_1_pane_1.SetMinSize((50, 256))
self.window_1_pane_1.SetBackgroundColour(wx.Colour(216, 216, 191))
self.window_1_pane_1.SetScrollRate(10, 10)
self.window_1.SetMinimumPaneSize(20)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
#sizer_2.Add(self.button_1, 0, 0, 0)
sizer1 = wx.BoxSizer(wx.VERTICAL)
sizer1.Add(self.tree, 20, wx.EXPAND)
self.SetSizer(sizer1)
self.window_1_pane_1.SetSizer(sizer_2)
self.window_1.SplitVertically(self.window_1_pane_1, self.window_1_pane_2)
sizer_1.Add(self.window_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
class TabTwo(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
t = wx.StaticText(self, -1, "This is the second tab", (20,20))
class TabThree(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
t = wx.StaticText(self, -1, "This is the third tab", (20,20))
class TabFour(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
t = wx.StaticText(self, -1, "This is the last tab", (20,20))
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="wxPython tabs example #pythonspot.com")
# Create a panel and notebook (tabs holder)
p = wx.Panel(self)
nb = wx.Notebook(p)
# Create the tab windows
tab1 = TabOne(nb)
tab2 = TabTwo(nb)
tab3 = TabThree(nb)
tab4 = TabFour(nb)
# Add the windows to tabs and name them.
nb.AddPage(tab1, "Tab 1")
nb.AddPage(tab2, "Tab 2")
nb.AddPage(tab3, "Tab 3")
nb.AddPage(tab4, "Tab 4")
# Set noteboook in a sizer to create the layout
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
p.SetSizer(sizer)
if __name__ == "__main__":
app = wx.App(redirect=True)
MainFrame().Show()
app.MainLoop()
wx.TreeCtrl has built-in horizontal and vertical scroll bars. Therefore, I guess there is no need to use the wx.ScrolledWindow here. I simplify the code for class TabOne to the following:
class TabOne(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.SetSize((400, 300))
self.window_1 = wx.SplitterWindow(self, wx.ID_ANY)
il = wx.ImageList(16,16)
self.fldridx = il.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16,16)))
self.fldropenidx = il.Add(wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16,16)))
self.fileidx = il.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16,16)))
self.tree = wx.TreeCtrl(self.window_1, style=wx.TR_DEFAULT_STYLE | wx.TR_EDIT_LABELS)
self.tree.AssignImageList(il)
root = self.tree.AddRoot("Server")
self.tree.SetItemData(root, None)
self.tree.SetItemImage(root, self.fldridx,wx.TreeItemIcon_Normal)
self.tree.SetItemImage(root, self.fldropenidx,wx.TreeItemIcon_Expanded)
tree = ["A","B","C","D","E","F","G","H"]
self.AddTreeNodes(root, tree)
self.tree.Expand(root)
self.window_1_pane_2 = wx.Panel(self.window_1, wx.ID_ANY)
self.window_1.SetMinimumPaneSize(20)
self.window_1.SplitVertically(self.tree, self.window_1_pane_2)
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.window_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
def AddTreeNodes(self, parentItem, items):
for item in items:
if type(item) == str:
newItem = self.tree.AppendItem(parentItem, item)
self.tree.SetItemData(newItem, None)
#self.tree.SetItemImage(newItem, self.fileidx,wx.TreeItemIcon_Normal)
else:
newItem = self.tree.AppendItem(parentItem, item[0])
self.tree.SetItemPyData(newItem, None)
#self.tree.SetItemImage(newItem, self.fldridx,wx.TreeItemIcon_Normal)
self.tree.SetItemImage(newItem, self.fldropenidx,wx.TreeItemIcon_Expanded)
self.AddTreeNodes(newItem, item[1])
and now is working as expected.

Set a minimal window size so all sizers fit

I have tried to understand how sizer and sizes work but clearly, I am missing something vital. This is what I get:
Clearly, the inside panels (sizers) do not fit the window. What I want is this:
which was created by a manual re-size.
How can I achieve that?
Note that the real code has many more different wx.StaticBox so a generic explanation of what I am doing wrong would be more welcome than just a fix.
Here is the toy example code:
import wx
import wx.lib.agw.floatspin as FS
class GUIControl(wx.Frame):
def __init__(self, parent, id, title, *args, **kwargs):
super(GUIControl, self).__init__(parent, id, title, *args, **kwargs)
self.radio = {}
self.modes = ['Open loop', 'Current control', 'Voltage control']
panel = wx.Panel(self)
panel.SetBackgroundColour('#5968c3')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self._ui(panel), 1, wx.EXPAND | wx.ALL, 3)
panel.SetSizer(sizer)
print(sizer.ComputeFittingWindowSize(self))
self.SetSize(sizer.ComputeFittingWindowSize(self))
self.Centre()
self.Show(True)
def _ui(self, parent):
panel = wx.Panel(parent)
vbox = wx.BoxSizer(wx.VERTICAL)
flags = wx.EXPAND | wx.SHAPED
vbox.Add(self._ui_prefix(panel), flag=wx.EXPAND)
vbox.Add(self._ui_control_mode(panel), flag=flags)
vbox.Add(self._ui_suffix(panel), flag=wx.EXPAND)
panel.SetSizer(vbox)
return panel
def _ui_prefix(self, parent):
panel = wx.Panel(parent)
panel.SetBackgroundColour('steelblue')
hbox = wx.BoxSizer(wx.HORIZONTAL)
_label = wx.StaticText(panel, label='')
_label.SetLabelMarkup("<b><i><big>Controls</big></i></b>")
hbox.Add(_label, flag=wx.ALIGN_CENTER | wx.ALL)
panel.SetSizer(hbox)
return panel
def _ui_control_mode(self, parent):
box = wx.StaticBox(parent, -1, "Control mode")
_label_modes = wx.StaticText(box, label='')
_label_modes.SetLabelMarkup("<i>Modes</i>")
for item in self.modes:
self.radio[item] = wx.RadioButton(box, -1, item)
_label_duty = wx.StaticText(box, label='')
_label_duty.SetLabelMarkup("<i>Duty cycle</i>")
self.sc1 = wx.SpinCtrl(box, wx.ID_ANY, '0', min=0, max=100)
_units_duty = wx.StaticText(box, label='%')
_label_current = wx.StaticText(box, label='')
_label_current.SetLabelMarkup("<i>Current</i>")
self.sc2 = FS.FloatSpin(box,
wx.ID_ANY,
value=0.0,
min_val=0.0,
max_val=20.0, # FIXME
increment=0.1)
self.sc2.SetFormat("%f")
self.sc2.SetDigits(1)
_units_current = wx.StaticText(box, label='A')
fgs = wx.FlexGridSizer(5, 3, 5, 7)
flags = wx.EXPAND | wx.ALL | wx.SHAPED
fgs.Add(_label_modes, 0, flags)
fgs.Add(self.radio['Open loop'], 0, flags)
fgs.AddStretchSpacer()
fgs.AddStretchSpacer()
fgs.Add(self.radio['Current control'], 0, flags)
fgs.AddStretchSpacer()
fgs.AddStretchSpacer()
fgs.Add(self.radio['Voltage control'], 0, flags)
fgs.AddStretchSpacer()
fgs.Add(_label_duty, 0, flags)
fgs.Add(self.sc1, 0, flags)
fgs.Add(_units_duty, 0, flags)
fgs.Add(_label_current, 0, flags)
fgs.Add(self.sc2, 0, flags)
fgs.Add(_units_current, 0, flags)
fgs.SetSizeHints(box)
fgs.Layout()
box.SetSizer(fgs)
return box
def _ui_suffix(self, parent):
panel = wx.Panel(parent)
panel.SetBackgroundColour('steelblue')
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(wx.BoxSizer(wx.VERTICAL),
flag=wx.RESERVE_SPACE_EVEN_IF_HIDDEN)
_quit = wx.Button(panel, -1, label=" Quit ")
_quit.SetBackgroundColour('#5968c3')
_quit.Bind(wx.EVT_BUTTON, self.OnQuit)
hbox.AddStretchSpacer()
hbox.Add(_quit, flag=wx.ALIGN_CENTER | wx.SHAPED | wx.ALL, border=3)
hbox.AddStretchSpacer()
panel.SetSizer(hbox)
return panel
def OnQuit(self, e):
self.Close()
if __name__ == '__main__':
app = wx.App()
GUIControl(None, -1, title='Control UI test')
app.MainLoop()
The easiest thing to do in this case is to also give the frame a sizer, add the main panel to it, and then call the frame's Fit method. For example, you can replace the call to self.SetSize in GUIControl.__init__ with this:
frameSizer = wx.BoxSizer()
frameSizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(frameSizer)
self.Fit()

wxPython thread that does not block the GUI?

I am new to wxPython and threading. I am trying to move some code into a thread so that it no longer blocks my GUI while executing, and I can push another button to kill the thread. Having done this, however, the code still blocks my GUI. How can I write this code so that my thread does not block the GUI?
import wx, sys
import threading
import time
class mywxframe(wx.Frame):
global sizer2, WorkerThread
def __init__(self):
wx.Frame.__init__(self, None)
pnl = wx.Panel(self)
szr = wx.BoxSizer(wx.VERTICAL)
pnl.SetSizer(szr)
szr2 = sizer2(self, pnl)
szr.Add(szr2, 1, wx.ALL | wx.EXPAND, 10)
log = wx.TextCtrl(pnl, -1,style=wx.TE_MULTILINE, size=(300,-1))
szr.Add(log, 1, wx.ALL, 10)
btn3 = wx.Button(pnl, -1, "Stop")
btn3.Bind(wx.EVT_BUTTON, self.OnStop)
szr.Add(btn3, 0, wx.ALL, 10)
redir = RedirectText(log)
sys.stdout=redir
szr.Fit(self)
self.Show()
def sizer2(self, panel):
sizer = wx.BoxSizer(wx.HORIZONTAL)
btn2 = wx.Button(panel, -1, "OK",)
self.Bind(wx.EVT_BUTTON, self.OnStart, btn2)
sizer.Add(btn2, 0, wx.ALL, 10)
return sizer
def WorkerThread(self):
self.dead = False
while (not self.dead):
for i in range(0,10):
print "printing", i
time.sleep(3)
def OnStart(self, event):
our_thread = threading.Thread(target=WorkerThread(self))
our_thread.start()
def OnStop(self, event):
self.dead = True
class RedirectText(object):
def __init__(self, aWxTextCtrl):
self.out=aWxTextCtrl
def write(self, string):
self.out.WriteText(string)
app = wx.PySimpleApp()
frm = mywxframe()
app.MainLoop()
You've got a couple problems there.
First of all, if you're going to define sizer2 and WorkerThread as methods, you should use them as methods, not globals.
Next, when you were creating the thread you were calling WorkerThread and passing its return value (None) to the thread. This is where you were blocking the GUI.
def OnStart(self, event):
our_thread = threading.Thread(target=WorkerThread(self))
our_thread.start()
Instead you should be passing a callable object (WorkerThread without the ()) to the thread so it will then be able to call it in the context of the new thread.
Finally, since self.out.WriteText manipulates a UI object it must be called in the context of the GUI thread. Using wx.CallAfter is an easy way to do that.
Here is your example updated with these changes:
import wx, sys
import threading
import time
print wx.version()
class mywxframe(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
pnl = wx.Panel(self)
szr = wx.BoxSizer(wx.VERTICAL)
pnl.SetSizer(szr)
szr2 = self.sizer2(pnl)
szr.Add(szr2, 1, wx.ALL | wx.EXPAND, 10)
log = wx.TextCtrl(pnl, -1,style=wx.TE_MULTILINE, size=(300,-1))
szr.Add(log, 1, wx.ALL, 10)
btn3 = wx.Button(pnl, -1, "Stop")
btn3.Bind(wx.EVT_BUTTON, self.OnStop)
szr.Add(btn3, 0, wx.ALL, 10)
redir = RedirectText(log)
sys.stdout=redir
szr.Fit(self)
self.Show()
def sizer2(self, panel):
sizer = wx.BoxSizer(wx.HORIZONTAL)
btn2 = wx.Button(panel, -1, "OK",)
self.Bind(wx.EVT_BUTTON, self.OnStart, btn2)
sizer.Add(btn2, 0, wx.ALL, 10)
return sizer
def WorkerThread(self):
self.dead = False
while (not self.dead):
for i in range(0,10):
print "printing", i
if self.dead:
break
time.sleep(3)
print 'thread exiting'
def OnStart(self, event):
our_thread = threading.Thread(target=self.WorkerThread)
our_thread.start()
def OnStop(self, event):
self.dead = True
class RedirectText(object):
def __init__(self, aWxTextCtrl):
self.out=aWxTextCtrl
def write(self, string):
wx.CallAfter(self.out.WriteText, string)
app = wx.App()
frm = mywxframe()
app.MainLoop()

wxPython: Problems with thread

My program keeps expanding.
In my MainProgram method in the MainPanel class I'm using some functions from an other program. While using this the GUI hangs until that is finished and I want to solve this by using a new thread for this method.
While doing this I get an error when executing OnRun. It says:
Unhandled exception in thread started by <bound method MainPanel.OnIndex of <__main__.MainPanel; proxy of <Swig Object of type 'wxPanel *' at 0x526e238> >>
It think this has something to do with the OnIndex setting som values to self.textOutput. Now, how can I solve this little problem of mine?
Help is much appreciated! =)
import wx, thread
ID_EXIT = 110
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.buttonRun = wx.Button(self, label="Run")
self.buttonRun.Bind(wx.EVT_BUTTON, self.OnRun )
self.buttonExit = wx.Button(self, label="Exit")
self.buttonExit.Bind(wx.EVT_BUTTON, self.OnExit)
self.labelChooseRoot = wx.StaticText(self, label ="Root catalog: ")
self.labelScratchWrk = wx.StaticText(self, label ="Scratch workspace: ")
self.labelMergeFile = wx.StaticText(self, label ="Merge file: ")
self.textChooseRoot = wx.TextCtrl(self, size=(210, -1))
self.textChooseRoot.Bind(wx.EVT_LEFT_UP, self.OnChooseRoot)
self.textScratchWrk = wx.TextCtrl(self, size=(210, -1))
self.textMergeFile = wx.TextCtrl(self, size=(210, -1))
self.textOutput = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)
self.sizerF = wx.FlexGridSizer(3, 2, 5, 5)
self.sizerF.Add(self.labelChooseRoot) #row 1, col 1
self.sizerF.Add(self.textChooseRoot) #row 1, col 2
self.sizerF.Add(self.labelScratchWrk) #row 2, col 1
self.sizerF.Add(self.textScratchWrk) #row 2, col 2
self.sizerF.Add(self.labelMergeFile) #row 3, col 1
self.sizerF.Add(self.textMergeFile) #row 3, col 2
self.sizerB = wx.BoxSizer(wx.VERTICAL)
self.sizerB.Add(self.buttonRun, 1, wx.ALIGN_RIGHT|wx.ALL, 5)
self.sizerB.Add(self.buttonExit, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
self.sizer1 = wx.BoxSizer()
self.sizer1.Add(self.sizerF, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL, 10)
self.sizer1.Add(self.sizerB, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.sizer2 = wx.BoxSizer()
self.sizer2.Add(self.textOutput, 1, wx.EXPAND | wx.ALL, 5)
self.sizerFinal = wx.BoxSizer(wx.VERTICAL)
self.sizerFinal.Add(self.sizer1, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.sizerFinal.Add(self.sizer2, 1, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.SetSizerAndFit(self.sizerFinal)
def OnChooseRoot(self, event):
dlg = wx.DirDialog(self, "Choose a directory:", style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
root_path = dlg.GetPath()
self.textChooseRoot.SetValue(root_path)
dlg.Destroy()
def OnRun(self, event):
#Check first if input values are
thread.start_new_thread(self.OnIndex, ())
def OnIndex(self):
#Do something and post to self.textOutput what you do.
def OnExit(self, event):
self.GetParent().Close()
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="IndexGenerator", size=(430, 330),
style=((wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE |
wx.STAY_ON_TOP) ^ wx.RESIZE_BORDER))
self.CreateStatusBar()
self.fileMenu = wx.Menu()
self.fileMenu.Append(ID_EXIT, "E&xit", "Exit the program")
self.menuBar = wx.MenuBar()
self.menuBar.Append(self.fileMenu, "&File")
self.SetMenuBar(self.menuBar)
wx.EVT_MENU(self, ID_EXIT, self.OnExit)
self.Panel = MainPanel(self)
self.CentreOnScreen()
self.Show()
def OnExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.App(False)
frame = MainWindow()
app.MainLoop()
[EDIT:] Here is an extract of the OnRun and OnIndex methods. Is there a () to much or maybe an , ?
def OnRun(self, Event=None):
#---Check input values, continue if not wrong
if self.CheckValid() == 0:
#Get root directory
root_path = self.textChooseRoot.GetValue()
#Get scratch workspace
scratch_workspace =self.textScratchWrk.GetValue()
#Get merge file
merge_fil = self.textMergeFile.GetValue()
thread.start_new_thread(self.OnIndex, (root_path,scratch_workspace,merge_fil))
def showmsg(self, msg):
self.textOutput.AppendText(msg + "\n")
def OnIndex(self,root_path,scratch_workspace,merge_fil):
#---PUBSUB - publishes a message to the "show.statusbar"
## msg = "Please wait...Program is running..."
## Publisher().sendMessage(("show.statusbar"), msg)
#---START INDEX GENERATOR CODE
gp.overwriteoutput = 1
gp.OutputMFlag = "DISABLED"
gp.OutputZFlag = "DISABLED"
fc_List = {}
#Get log file. For now a constant. Needs to be changed.
logfil = open("C:\\Python26\\Code\\log.txt", mode = "w")
fold_nr = 0
for root_fold, dirs, files in os.walk(root_path):
root_fold_low = root_fold.lower()
if not root_fold_low.find(".gdb") > -1:
fold_nr += 1
tot_t = time.clock()
wx.CallAfter(self.textOutput.AppendText, ("Mappe : " + str(fold_nr) + " : " + root_fold + "\n"))
All interaction with wx object should be in the main thread.
An easy fix would be to use something like wx.CallAfter(self.textOutput.SetValue, "output") instead of self.textOutput.SetValue("output").
wx.CallAfter sends to the main even loop what to execute as soon as it gets around to it and since the main loop is in the main thread everything works out fine.
UPDATE: Working merged code snippets:
import wx, thread, os, time
ID_EXIT = 110
class Dummy:
pass
gp = Dummy()
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.buttonRun = wx.Button(self, label="Run")
self.buttonRun.Bind(wx.EVT_BUTTON, self.OnRun )
self.buttonExit = wx.Button(self, label="Exit")
self.buttonExit.Bind(wx.EVT_BUTTON, self.OnExit)
self.labelChooseRoot = wx.StaticText(self, label ="Root catalog: ")
self.labelScratchWrk = wx.StaticText(self, label ="Scratch workspace: ")
self.labelMergeFile = wx.StaticText(self, label ="Merge file: ")
self.textChooseRoot = wx.TextCtrl(self, size=(210, -1))
self.textChooseRoot.Bind(wx.EVT_LEFT_UP, self.OnChooseRoot)
self.textScratchWrk = wx.TextCtrl(self, size=(210, -1))
self.textMergeFile = wx.TextCtrl(self, size=(210, -1))
self.textOutput = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)
self.sizerF = wx.FlexGridSizer(3, 2, 5, 5)
self.sizerF.Add(self.labelChooseRoot) #row 1, col 1
self.sizerF.Add(self.textChooseRoot) #row 1, col 2
self.sizerF.Add(self.labelScratchWrk) #row 2, col 1
self.sizerF.Add(self.textScratchWrk) #row 2, col 2
self.sizerF.Add(self.labelMergeFile) #row 3, col 1
self.sizerF.Add(self.textMergeFile) #row 3, col 2
self.sizerB = wx.BoxSizer(wx.VERTICAL)
self.sizerB.Add(self.buttonRun, 1, wx.ALIGN_RIGHT|wx.ALL, 5)
self.sizerB.Add(self.buttonExit, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
self.sizer1 = wx.BoxSizer()
self.sizer1.Add(self.sizerF, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL, 10)
self.sizer1.Add(self.sizerB, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.sizer2 = wx.BoxSizer()
self.sizer2.Add(self.textOutput, 1, wx.EXPAND | wx.ALL, 5)
self.sizerFinal = wx.BoxSizer(wx.VERTICAL)
self.sizerFinal.Add(self.sizer1, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.sizerFinal.Add(self.sizer2, 1, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.SetSizerAndFit(self.sizerFinal)
def OnChooseRoot(self, event):
dlg = wx.DirDialog(self, "Choose a directory:", style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
root_path = dlg.GetPath()
self.textChooseRoot.SetValue(root_path)
dlg.Destroy()
def CheckValid(self):
return 0
def OnRun(self, Event=None):
#---Check input values, continue if not wrong
if self.CheckValid() == 0:
#Get root directory
root_path = self.textChooseRoot.GetValue()
#Get scratch workspace
scratch_workspace =self.textScratchWrk.GetValue()
#Get merge file
merge_fil = self.textMergeFile.GetValue()
thread.start_new_thread(self.OnIndex, (root_path,scratch_workspace,merge_fil))
def showmsg(self, msg):
self.textOutput.AppendText(msg + "\n")
def OnIndex(self,root_path,scratch_workspace,merge_fil):
#---PUBSUB - publishes a message to the "show.statusbar"
## msg = "Please wait...Program is running..."
## Publisher().sendMessage(("show.statusbar"), msg)
#---START INDEX GENERATOR CODE
gp.overwriteoutput = 1
gp.OutputMFlag = "DISABLED"
gp.OutputZFlag = "DISABLED"
fc_List = {}
#Get log file. For now a constant. Needs to be changed.
#logfil = open("C:\\Python26\\Code\\log.txt", mode = "w")
fold_nr = 0
for root_fold, dirs, files in os.walk(root_path):
root_fold_low = root_fold.lower()
if not root_fold_low.find(".gdb") > -1:
fold_nr += 1
tot_t = time.clock()
wx.CallAfter(self.textOutput.AppendText, ("Mappe : " + str(fold_nr) + " : " + root_fold + "\n"))
def OnExit(self, event):
self.GetParent().Close()
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="IndexGenerator", size=(430, 330),
style=((wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE |
wx.STAY_ON_TOP) ^ wx.RESIZE_BORDER))
self.CreateStatusBar()
self.fileMenu = wx.Menu()
self.fileMenu.Append(ID_EXIT, "E&xit", "Exit the program")
self.menuBar = wx.MenuBar()
self.menuBar.Append(self.fileMenu, "&File")
self.SetMenuBar(self.menuBar)
wx.EVT_MENU(self, ID_EXIT, self.OnExit)
self.Panel = MainPanel(self)
self.CentreOnScreen()
self.Show()
def OnExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.App(False)
frame = MainWindow()
app.MainLoop()
You just need to use a threadsafe method to send information back to the main thread. As the others have stated, you cannot interact with the main thread directly or weird things will happen. Here's a really good article on the various ways you can accomplish this feat:
http://wiki.wxpython.org/LongRunningTasks
And here's another article on the subject:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
You cannot interact with the GUI from another thread. Your callback should work like this:
def OnRun(self, event):
<get any data you need from the GUI>
thread.start_new_thread(self.WorkerThread, (parameter1, parameter2, ...))
def WorkerThread(self, parameter1, parameter2, ...):
# do time-consuming work here. To send data to
# the GUI use CallAfter:
wx.CallAfter(self.textOutput.AppendText, "whatever")
The key is to do all the non-time-consuming GUI interaction in the main thread before spawning the worker thread. Once the worker thread starts it should have all the data it needs. It can then proceed to do its work, and use CallAfter to communicate back to the main thread.

Resources