Ipse Lium
Ipse Lium

Reputation: 1080

wxPython : Issues on windows using multiple sizers, but works well on linux

I am writing a small app that works very well on linux, but I have some trouble on windows. Here is the code sample:

import wx
#####################################################################    
class Main(wx.Frame):
    def __init__(self):

        wx.Frame.__init__(self, None, title="StackOverflow",  pos=wx.DefaultPosition, size=(800,600))
        self.SetMinSize( self.GetSize() )

        p = wx.Panel(self)
        nb = wx.Notebook(p)

        page1 = AddToCollection(nb)
        page2 = CollectionStatistics(nb)
        nb.AddPage(page1, "Page 1")
        nb.AddPage(page2, "Page 2")

        # finally, put the notebook in a sizer for the panel to manage
        # the layout
        sizer = wx.BoxSizer()
        sizer.Add(nb, 1, wx.EXPAND)
        p.SetSizer(sizer)
#########################################################################
class CollectionStatistics(wx.Panel):
    def __init__(self, parent):          

        wx.Panel.__init__(self, parent)
#########################################################################
class AddToCollection(wx.Panel):
    def __init__(self, parent):          

        wx.Panel.__init__(self, parent)

        self.v1_qty_list = [str(x) for x in range(9)]
        self.v2_qty_list = [str(x) for x in range(9)]

        self.sizername = wx.GridBagSizer(5, 5)
        self.sizername.AddGrowableCol(0,0)

        self.name_txt = wx.StaticText(self, label="Enter Name :")
        self.sizername.Add(self.name_txt,(2,0),(1,1),wx.EXPAND)

        self.name = wx.TextCtrl(self,style=wx.TE_PROCESS_ENTER,value=u"")
        self.sizername.Add(self.name,(3,0),(1,1),wx.EXPAND)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnPressEnter, self.name)

        self.SetSizerAndFit(self.sizername)
        self.SetSizeHints(-1,self.GetSize().y,-1,self.GetSize().y )
##########################################################################
    def OnPressEnter(self,event):
        self.selected_name = self.name.GetValue()
        self.AddToCol()
##########################################################################
    def AddToCol(self):

        self.sizerAdd = wx.GridBagSizer(5, 5)
        self.sizerAdd.AddGrowableCol(0, 0)

        self.name.Enable(False)

        ### Expansion
        self.expansion = wx.Choice(self, -1, choices=['test 1', 'test 2'])
        self.expansion.SetSelection(0)
        self.sizerAdd.Add(self.expansion,(5,0),(1,6),wx.EXPAND)

        ### Quantities txt
        self.v1_txt = wx.StaticText(self, label="V1 Quantity :")
        self.sizerAdd.Add(self.v1_txt,(7,0),(1,1),wx.EXPAND)

        self.v2_txt = wx.StaticText(self, label="V2 Quantity :")
        self.sizerAdd.Add(self.v2_txt,(8,0),(1,1),wx.EXPAND)

        ### Quantities choices
        self.v1_qty = wx.Choice(self, -1, choices=self.v1_qty_list)
        self.v1_qty.SetSelection(0)
        self.sizerAdd.Add(self.v1_qty,(7,5),(1,1),wx.EXPAND)

        self.v2_qty = wx.Choice(self, -1, choices=self.v1_qty_list)
        self.v2_qty.SetSelection(0)
        self.sizerAdd.Add(self.v2_qty,(8,5),(1,1),wx.EXPAND)

        ### Ok Button        
        self.Add_btn = wx.Button(self, -1, "Add")
        self.Add_btn.Bind(wx.EVT_BUTTON, self.OnAdd)
        self.sizerAdd.Add(self.Add_btn,(9,5),(1,1),wx.EXPAND)        

        ### Reset Button        
        self.Reset_btn = wx.Button(self, -1, "Reset")
        self.Reset_btn.Bind(wx.EVT_BUTTON, self.OnResetPanel)
        self.sizerAdd.Add(self.Reset_btn,(9,4),(1,1),wx.EXPAND)             

        self.SetSizerAndFit(self.sizerAdd)
        self.SetSizeHints(-1,self.GetSize().y,-1,self.GetSize().y )     
######################################################################
    def OnResetPanel(self,event):
        ### Kill all children
        self.expansion.Destroy()
        self.v1_txt.Destroy()
        self.v1_qty.Destroy()
        self.v2_txt.Destroy()
        self.v2_qty.Destroy()
        self.Add_btn.Destroy()
        self.Reset_btn.Destroy()
        ### Reinitialise sizer
        self.name.Enable(True)
        self.name.SetValue("") 
######################################################################
    def OnAdd(self,event):
        print 'Add'
        self.OnResetPanel(self)
######################################################################
######################################################################
if __name__ == "__main__":
    app = wx.App()
    Main().Show()
    app.MainLoop()

Basically, I have a TextCtrl in a first sizer which is waiting for an entry. Once the user hits enter, several objects appear in a second sizer.

The issue on windows seems to come from the use of the two gridbagsizers (sizername and sizerAdd). After pressing enter (waited event in the __init__), the objects defined within the sizerAdd do not appear. When I extend the window where the script is running, these objects appear magically !

Any idea ?

EDIT : The code is now runnable

Upvotes: 0

Views: 338

Answers (1)

Luke Woodward
Luke Woodward

Reputation: 65054

I think the problem in your code is these two lines at the end of your AddToCol method:

        self.SetSizerAndFit(self.sizerAdd)
        self.SetSizeHints(-1,self.GetSize().y,-1,self.GetSize().y )     

At this point, you're changing the sizer of the AddToCollection panel from self.sizername to self.sizerAdd. The Enter Name: label and the textbox however are still within the self.sizername sizer. However, this sizer isn't the sizer for any window, nor has it been added to any other sizer.

Generally, in wxPython, every sizer should be set as the sizer for a window, or be added to another sizer. This other sizer would then be the sizer for a window, or be contained within another sizer, and so on. In your case, your self.sizername sizer ends up being neither, and in this situation I would expect unpredictable behaviour. If your code works on Linux then I would say that it happens to work by accident.

I can think of a few things you could do here:

  1. Add self.sizerAdd as a child of self.sizername. This can be done by replacing the two lines above with

            self.sizername.Add(self.sizerAdd,(4,0),(1,1),wx.EXPAND)
            self.sizername.Layout()
    
  2. In AddToCol, add the widgets directly to the self.sizername sizer instead of adding them to self.sizerAdd.

  3. Create a wx.BoxSizer() with vertical orientation, set that to be the sizer for the AddToCollection panel, and add the self.sizername and self.sizerAdd sizers to your BoxSizer.

In all three cases, after creating the new widgets you will need to call the Layout() method on the top-level sizer, be it either self.sizername or the top-level BoxSizer. The code snippet under option 1 includes this line already.

Additionally, you may need to modify your OnResetPanel() method. If you chose options 1 or 3, you will need to remove the self.sizerAdd sizer from whichever sizer you added it to. For example, in option 1, you would add the line

        self.sizername.Remove(self.sizerAdd)

Another approach would be for your AddToCol method to create all the widgets within a Panel and add that to the main panel at the end. Your AddToCol method would then need to create a child panel, add the extra controls as children of this panel instead of the main panel (self), set the sizer of the child panel to self.sizerAdd and finally add this panel to the self.sizername sizer.

    def AddToCol(self):

        self.sizerAdd = wx.GridBagSizer(5, 5)
        self.sizerAdd.AddGrowableCol(0, 0)

        self.name.Enable(False)

        self.child_panel = wx.Panel(self)

        ### Expansion
        self.expansion = wx.Choice(self.child_panel, -1, choices=['test 1', 'test 2'])
        self.expansion.SetSelection(0)
        self.sizerAdd.Add(self.expansion,(5,0),(1,6),wx.EXPAND)

        # Create other widgets as before but with the parent set to self.child_panel
        # instead of self.

        self.child_panel.SetSizer(self.sizerAdd)
        self.sizername.Add(self.child_panel,(4,0),(1,1),wx.EXPAND)
        self.sizername.Layout()

You would then also need to replace the line

       self.sizername.Remove(self.sizerAdd)

in OnResetPanel() with the two lines:

       self.sizername.Remove(self.child_panel)
       self.child_panel.Destroy()

One thing which bugged me about my approach 1 above was that I saw the widgets briefly appear in the top-left corner before appearing in the correct place. This adaptation fixes this problem and so makes the GUI behave itself a bit better. I couldn't reproduce your black area issue you mention in your comment, but hopefully this approach fixes your problem as well.

Upvotes: 1

Related Questions