wxPython: Dynamically Flow Buttons to Next Row on Window-Resize
Question: The following wxPython sample code is meant to create some buttons and then
add them to a horizontal panel in such a way that the buttons should flow to a
new row when they no longer fit in the panel.

In addition, the buttons should change position (increase/decrease the number
of rows) as the user resizes the frame/window. (please see <http://wxpython-
users.1045709.n5.nabble.com/Button-wrap-td2365760.html>).

    
    
        import wx
    
    words = ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur',
    'adipisicing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt',
    'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua', 'ut', 'enim', 'ad',
    'minim', 'veniam', 'quis', 'nostrud', 'exercitation', 'ullamco',
    'laboris', 'nisi', 'ut', 'aliquip', 'ex', 'ea', 'commodo',
    'consequat', 'duis', 'aute', 'irure', 'dolor', 'in', 'reprehenderit',
    'in', 'voluptate', 'velit', 'esse', 'cillum', 'dolore', 'eu',
    'fugiat', 'nulla', 'pariatur', 'excepteur', 'sint', 'occaecat',
    'cupidatat', 'non', 'proident', 'sunt', 'in', 'culpa', 'qui',
    'officia', 'deserunt', 'mollit', 'anim', 'id', 'est', 'laborum']
    
    class Example(wx.Frame):
    
        def __init__(self, *args, **kwargs):
            super(Example, self).__init__(*args, **kwargs) 
            self.InitUI()
            self.Ctrls()
            i = 0
            for word in words:
                i += 1
                print " word = ",word, "  ",i
                self.gridSizer.SetCols(5)
                button = wx.Button(self.panel, -1, word, size=wx.Size(70,25))
                self.gridSizer.Add(button, 1, wx.EXPAND)
            self.panel.Layout()
    
        def InitUI(self):
    
            self.SetSize((800, 400))
            self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
            self.Centre()
            self.Show(True)
    
        def Sizers(self):
    
            self.gridSizer = wx.GridSizer(cols=0, hgap=1, rows=1, vgap=1)
            self.panel.SetSizer(self.gridSizer)
    
        def Ctrls(self):
    
            self.Bind(wx.EVT_SIZE, self.OnFrameSize)
            self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
            self.Sizers()
    
        def OnFrameSize(self, event):
    
            self.UpdateButtonLayout()
            event.Skip()
    
        def UpdateButtonLayout(self):
    
            panelwidth = self.panel.GetSize().width
            print panelwidth
            factor = panelwidth / 70
            print "Factor: ", factor
            self.gridSizer.SetCols(factor)
            self.panel.Layout()
    
    def main():
    
        ex = wx.App()
        Example(None)
        ex.MainLoop()
    
    if __name__ == '__main__':
        main()
    

When I run this example I get the following error;

    
    
    Traceback (most recent call last):
      File "test.py", line 68, in <module>
        main()
      File "test.py", line 64, in main
        Example(None)
      File "test.py", line 26, in __init__
        self.gridSizer.Add(button, 1, wx.EXPAND)
      File "/usr/local/lib/wxPython-3.0.2.0/lib/python2.7/site-packages/wx-3.0-osx_cocoa/wx/_core.py", line 14457, in Add
        return _core_.Sizer_Add(*args, **kwargs)
    wx._core.PyAssertionError: C++ assertion "Assert failure" failed at /BUILD/wxPython-src-3.0.2.0/src/common/sizer.cpp(1401) in DoInsert(): too many items (6 > 5*1) in grid sizer (maybe you should omit the number of either rows or columns?)
    

The problem is with line number 26. When I comment that out I see only a
single button. Can someone suggest some changes to make it work? Thanks.


Answer: What you are looking for is the `wx.WrapSizer`. I updated your code a bit to
use it instead:

    
    
    import wx
    
    words = ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur',
    'adipisicing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt',
    'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua', 'ut', 'enim', 'ad',
    'minim', 'veniam', 'quis', 'nostrud', 'exercitation', 'ullamco',
    'laboris', 'nisi', 'ut', 'aliquip', 'ex', 'ea', 'commodo',
    'consequat', 'duis', 'aute', 'irure', 'dolor', 'in', 'reprehenderit',
    'in', 'voluptate', 'velit', 'esse', 'cillum', 'dolore', 'eu',
    'fugiat', 'nulla', 'pariatur', 'excepteur', 'sint', 'occaecat',
    'cupidatat', 'non', 'proident', 'sunt', 'in', 'culpa', 'qui',
    'officia', 'deserunt', 'mollit', 'anim', 'id', 'est', 'laborum']
    
    class Example(wx.Frame):
    
        def __init__(self, *args, **kwargs):
            super(Example, self).__init__(*args, **kwargs) 
            self.InitUI()
            self.Ctrls()
            i = 0
            for word in words:
                i += 1
                print " word = ",word, "  ",i
                button = wx.Button(self.panel, -1, word, size=wx.Size(70,25))
                self.wrapSizer.Add(button, 1, wx.EXPAND)
            self.Show(True)
            self.panel.Layout()
    
        def InitUI(self):
            self.SetSize((800, 400))
            self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
            self.Centre()
    
    
        def Sizers(self):
            self.wrapSizer = wx.WrapSizer()
            self.panel.SetSizer(self.wrapSizer)
    
        def Ctrls(self):
            self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
            self.Sizers()
    
    
    
    def main():
    
        ex = wx.App()
        Example(None)
        ex.MainLoop()
    
    if __name__ == '__main__':
        main()
    

Note that you cannot set the number of columns. You can read more about this
fun widget at the following:

  * <http://www.blog.pythonlibrary.org/2014/01/22/wxpython-wrap-widgets-with-wrapsizer/>
  * <http://wxpython.org/Phoenix/docs/html/WrapSizer.html>

