Displaying a loading panel over a widget

Didn't think I would be posting again so soon :slight_smile:

I'm having a little trouble getting this working properly,
I have a CustomTreeCtrl which is on one side of a splitter which works fine.
The issue is the tree takes a while to build (2-3seconds) as it makes several
server calls and a few other bits.
What I'm trying to do is display a loading "Please wait.." type panel where
the tree is while it's loading, then switch to the loaded tree.
I've written both the loading panel and the tree but can't get this working.
Do I need a thread to go away and load the tree? or can I simply show the
loading panel, load the tree underneath it then hide the loading panel once
it's done?

I think the second option is preferable but I don't know how to get two
objects(panel and tree) to fill the space in the same side of the splitter.

Thanks in advance!!

You could Hide() the tree and just show the loading the message in it’s place. Then Hide() or Destroy() the loading message and Show() the tree. I’ve used wx.BusyInfo to show a loading message before, although it is centered on the screen. There’s a little demo here on the latter: http://www.blog.pythonlibrary.org/2010/06/26/the-dialogs-of-wxpython-part-1-of-2/

Hopefully one of those suggestions will help.

···

Mike Driscoll

Blog: http://blog.pythonlibrary.org

Mike Driscoll <kyosohma <at> gmail.com> writes:

You could Hide() the tree and just show the loading the message in its place.

Then Hide() or Destroy() the loading message and Show() the tree. I've used
wx.BusyInfo to show a loading message before, although it is centered on the
screen. There's a little demo here on the latter:

2/Hopefully one of those suggestions will help.-------------------Mike
DriscollBlog: http://blog.pythonlibrary.org

Ah thanks, I guess my real question is how I can get both the tree and the
loading panel to fit inside the same splitter nicely.

When I split the splitter (self.splitter.SplitVertically(self.tree,self.lc))
How can I get the panel in there too? I tried making both the tree and the
panel children of another panel which I could put into the splitter but I
couldn't get them to size correctly (the dummy panel scales but the tree and
loading panel sit in little boxes in the top corner :slight_smile: )

Hmmm, I've got them out the square using a sizer, each in their own vertical box
sizer but then I can only call SetSizer on one of them. Is there a way to get
both of them to size up?

You should add your message box and tree into the same sizer and then do the parentPanel.SetSizer(sizer)

and use hide/show and you might need to call layout on the parentPanel after hide/show.

Werner

···

On 10/04/2011 04:59 PM, Paul wrote:

Hmmm, I've got them out the square using a sizer, each in their own vertical box
sizer but then I can only call SetSizer on one of them. Is there a way to get
both of them to size up?

werner <wbruhin <at> free.fr> writes:

> Hmmm, I've got them out the square using a sizer, each in their own vertical

box

> sizer but then I can only call SetSizer on one of them. Is there a way to get
> both of them to size up?
You should add your message box and tree into the same sizer and then do
the parentPanel.SetSizer(sizer)

and use hide/show and you might need to call layout on the parentPanel
after hide/show.

Werner

If they're in the same sizer I wont be able to get them on top of each other
though?

···

On 10/04/2011 04:59 PM, Paul wrote:

No but you don't need that, as you are only showing one at the time, no?

Werner

···

On 10/04/2011 05:18 PM, Paul wrote:

werner<wbruhin<at> free.fr> writes:

On 10/04/2011 04:59 PM, Paul wrote:

Hmmm, I've got them out the square using a sizer, each in their own vertical

box

sizer but then I can only call SetSizer on one of them. Is there a way to get
both of them to size up?

You should add your message box and tree into the same sizer and then do
the parentPanel.SetSizer(sizer)

and use hide/show and you might need to call layout on the parentPanel
after hide/show.

Werner

If they're in the same sizer I wont be able to get them on top of each other
though?

Well, if you Hide one and Show the other, putting them in sizers won’t hurt a thing.

···

Mike Driscoll

Blog: http://blog.pythonlibrary.org

I thought this would work, but when I hide the loader I use the same sizer code
that sizes up the loader on the tree, but nothing happens- the tree shows with
.Show() but is still a little square. Could this be because the program is
already in the Mainloop by this point? (I have to run the tree builder in a
thread so I could get to Frame.Show() and the mainloop to show the loader in the
first place)

I think what you want is:

- create a panel
- add the loader message to this panel
- add the listctrl to the same panel
- listctrl.Hide()
- add both to a sizer and do panel.SetSizer(sizer)
- when you have the data load it into the listctrl
- loader.Hide()
- listctrl.Show()
- panel.Layout()

If listctrl is still small at this point then check your sizer options (expand/propertion) for the listctrl.

Werner

···

On 10/04/2011 07:26 PM, Paul wrote:

I thought this would work, but when I hide the loader I use the same sizer code
that sizes up the loader on the tree, but nothing happens- the tree shows with
.Show() but is still a little square. Could this be because the program is
already in the Mainloop by this point? (I have to run the tree builder in a
thread so I could get to Frame.Show() and the mainloop to show the loader in the
first place)

And one more step, use that panel (the parent of the message and the tree windows) as the window that is put in the splitter. In other words, you are adding an extra layer in between the splitter and the message/tree widgets, and that new layer will be managed by the splitter and it will in turn manage the message or tree widgets.

···

On 10/4/11 10:34 AM, werner wrote:

On 10/04/2011 07:26 PM, Paul wrote:

I thought this would work, but when I hide the loader I use the same
sizer code
that sizes up the loader on the tree, but nothing happens- the tree
shows with
.Show() but is still a little square. Could this be because the
program is
already in the Mainloop by this point? (I have to run the tree builder
in a
thread so I could get to Frame.Show() and the mainloop to show the
loader in the
first place)

I think what you want is:

- create a panel
- add the loader message to this panel
- add the listctrl to the same panel
- listctrl.Hide()
- add both to a sizer and do panel.SetSizer(sizer)
- when you have the data load it into the listctrl
- loader.Hide()
- listctrl.Show()
- panel.Layout()

If listctrl is still small at this point then check your sizer options
(expand/propertion) for the listctrl.

--
Robin Dunn
Software Craftsman

Robin Dunn <robin <at> alldunn.com> writes:

>> I thought this would work, but when I hide the loader I use the same
>> sizer code
>> that sizes up the loader on the tree, but nothing happens- the tree
>> shows with
>> .Show() but is still a little square. Could this be because the
>> program is
>> already in the Mainloop by this point? (I have to run the tree builder
>> in a
>> thread so I could get to Frame.Show() and the mainloop to show the
>> loader in the
>> first place)
> I think what you want is:
>
> - create a panel
> - add the loader message to this panel
> - add the listctrl to the same panel
> - listctrl.Hide()
> - add both to a sizer and do panel.SetSizer(sizer)
> - when you have the data load it into the listctrl
> - loader.Hide()
> - listctrl.Show()
> - panel.Layout()
>
> If listctrl is still small at this point then check your sizer options
> (expand/propertion) for the listctrl.

And one more step, use that panel (the parent of the message and the
tree windows) as the window that is put in the splitter. In other
words, you are adding an extra layer in between the splitter and the
message/tree widgets, and that new layer will be managed by the splitter
and it will in turn manage the message or tree widgets.

I can't get this working at all.. I've followed the steps above plus a bunch of
different variations but to no avail. I've simplified my code just down to the
bits needed to show what I'm trying to do and included it below. I hope this
helps show what I'm doing wrong :slight_smile:

import wx,time,thread
import wx.lib.agw.customtreectrl as CT

class CustomTreeCtrl(CT.CustomTreeCtrl):
  def __init__(self, parent, id, pos, size, style, spanel, loader):
    CT.CustomTreeCtrl.__init__(self, parent, id, pos, size, wx.SIMPLE_BORDER,
style)
    self.spanel= spanel
    self.loader= loader
    
    isz = (16,16)
    il = wx.ImageList(isz[0], isz[1])
    self.fldridx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FOLDER,
wx.ART_OTHER, isz))
    self.fldropenidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN,
wx.ART_OTHER, isz))
    self.fileidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_NORMAL_FILE,
wx.ART_OTHER, isz))

    self.SetImageList(il)
    self.il = il

    thread.start_new_thread(self.loadTree,(None,))
    
  def loadTree(self,junk):
    time.sleep(3)
    self.root_name= "Root"
    self.root = self.AddRoot(self.root_name,ct_type=1)
    self.root.Set3State(True)
    self.SetItemImage(self.root, self.fldridx, wx.TreeItemIcon_Selected)
    self.SetItemImage(self.root, self.fldropenidx, wx.TreeItemIcon_Expanded)

    child= self.addChildNode(self.root,"child 1")
    self.addChildNode(child,"child 2")
    
    self.Expand(self.root)

    self.loader.Hide()
## s1= wx.BoxSizer(wx.VERTICAL)
## s1.Add(self,1,wx.EXPAND)
## self.spanel.SetSizer(s1)
    self.Show()
    
  def addChildNode(self,parent,name):
    child= self.AppendItem(parent,name,ct_type=1)
    child.Set3State(True)
    self.SetItemImage(child, self.fldridx, wx.TreeItemIcon_Normal)
    self.SetItemImage(child, self.fldropenidx, wx.TreeItemIcon_Expanded)
    return child

class Loader(wx.Panel):
  
  def __init__(self, parent):
    """"""
    wx.Panel.__init__(self, parent=parent, style=wx.SIMPLE_BORDER)
    
    self.SetBackgroundColour("White")
    
    font1=wx.Font(14,wx.NORMAL,wx.NORMAL,wx.NORMAL)
    text1="Please wait..."
    
    sizer= wx.BoxSizer(wx.VERTICAL)
    sizer.AddStretchSpacer()
    
    #gif= "loader.gif"
    #spinner= wx.animate.GIFAnimationCtrl(self, -1, gif)
    #spinner.GetPlayer().UseBackgroundColour(True)
    #spinner.Play()
    #sizer.Add(spinner,0,wx.ALIGN_CENTER)
    
    t1= wx.StaticText(self,-1,text1,style=wx.ALIGN_CENTER)
    t1.SetFont(font1)
    sizer.Add(t1,0,wx.ALL|wx.ALIGN_CENTER,10)
    sizer.AddStretchSpacer()
    self.SetSizer(sizer)

class FileSelectorFrame(wx.Frame):
  def __init__(self, parent, id, title):
    wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(800,
600))
    
    p= wx.Panel(self)
    
    self.splitter1 = wx.SplitterWindow(p, -1, style=wx.SP_3D)
    
    spanel= wx.Panel(self.splitter1)
    
    self.loader= Loader(spanel)

    self.tree = CustomTreeCtrl(spanel, -1, wx.DefaultPosition, wx.DefaultSize,
                           wx.TR_DEFAULT_STYLE
                           #wx.TR_HAS_BUTTONS
                           #| wx.TR_EDIT_LABELS
                           #| wx.TR_MULTIPLE
                           #| wx.TR_HIDE_ROOT
                           ,spanel,self.loader)
    self.tree.Hide()
    
    s1= wx.BoxSizer(wx.VERTICAL)
    s1.Add(self.loader,1,wx.EXPAND)
    s1.Add(self.tree,1,wx.EXPAND)
    spanel.SetSizer(s1)
   
    #This is normally something different, I've just put in a panel for
simplicity
    self.p2= wx.Panel(self.splitter1,-1,style=wx.BORDER_SIMPLE)
    self.p2.SetBackgroundColour("white")
                           
    self.splitter1.SplitVertically(spanel, self.p2)
    self.splitter1.SetSashPosition(300)
    self.splitter1.SetMinimumPaneSize(50)
    
    sizer = wx.BoxSizer(wx.VERTICAL)
    sizer.Add(self.splitter1, 1, wx.ALL|wx.EXPAND, 5)
    p.SetSizer(sizer)
    
    self.Centre()

class RApp(wx.App):
   
  def OnInit(self):
    self.frame = FileSelectorFrame(None, -1, "framey")
    self.frame.Show(True)
    self.SetTopWindow(self.frame)
    return True

if __name__ == '__main__':
  app = RApp(False)
  app.MainLoop()

···

On 10/4/11 10:34 AM, werner wrote:
> On 10/04/2011 07:26 PM, Paul wrote:

Ok I noticed if I move the sash the tree appears then, I guess this forces the
frame to redraw. Is there a way to force this when the tree finishes building?

You left out one step, and also broke a cardinal rule of wxPython. The missing step is that you didn't call the parent panel's Layout after hiding the message panel and showing the tree. The broken rule is that you were trying to manipulate GUI items from a worker thread. All creation and manipulation of GUI objects must be done in the GUI thread.

I've attached a modification of your sample that shows the use of wx.CallAfter to invoke the GUI manipulation in the context of the main thread. There are other approaches to dealing with long-running tasks that may fit your needs better. See

http://wiki.wxpython.org/LongRunningTasks
http://wiki.wxpython.org/Non-Blocking_Gui
http://wiki.wxpython.org/CallAfter

paul.py (3.95 KB)

···

On 10/4/11 12:17 PM, Paul wrote:

Robin Dunn<robin<at> alldunn.com> writes:

On 10/4/11 10:34 AM, werner wrote:

On 10/04/2011 07:26 PM, Paul wrote:

I thought this would work, but when I hide the loader I use the same
sizer code
that sizes up the loader on the tree, but nothing happens- the tree
shows with
.Show() but is still a little square. Could this be because the
program is
already in the Mainloop by this point? (I have to run the tree builder
in a
thread so I could get to Frame.Show() and the mainloop to show the
loader in the
first place)

I think what you want is:

- create a panel
- add the loader message to this panel
- add the listctrl to the same panel
- listctrl.Hide()
- add both to a sizer and do panel.SetSizer(sizer)
- when you have the data load it into the listctrl
- loader.Hide()
- listctrl.Show()
- panel.Layout()

If listctrl is still small at this point then check your sizer options
(expand/propertion) for the listctrl.

And one more step, use that panel (the parent of the message and the
tree windows) as the window that is put in the splitter. In other
words, you are adding an extra layer in between the splitter and the
message/tree widgets, and that new layer will be managed by the splitter
and it will in turn manage the message or tree widgets.

I can't get this working at all.. I've followed the steps above plus a bunch of
different variations but to no avail. I've simplified my code just down to the
bits needed to show what I'm trying to do and included it below. I hope this
helps show what I'm doing wrong :slight_smile:

--
Robin Dunn
Software Craftsman

Cheers man! Working perfectly now :slight_smile:
I've moved some of the GUI stuff that was replaced by that sleep before the
thread call, put only the server calls in the thread and then put the other GUI
bits in the CallAfter call. I realised at the time that I wasn't doing it the
best way, I'm sure passing in the spanel and loader references isn't the best
way either but sometimes getting things working is worth breaking some rules :slight_smile:

Thanks again!