import wx DEFAULT_GAP = 5 DEFAULT_SCROLL_RATE = 20 class FlowPanel(wx.ScrolledWindow): """ This panel uses a flow layout algorithm. I've tried implementing it using a custmo sizer, but flow layout is too wierd to make it work corretly. So this panel implements it by directly catching the EVT_SIZE event. """ def __init__(self, parent=None): # Prepare for use both in XRC and in-code creation if parent: wx.ScrolledWindow.__init__(self, parent) else: pre = wx.PreScrolledWindow() self.PostCreate(pre) self.Bind(wx.EVT_SIZE, self.on_size) self.Bind(wx.EVT_WINDOW_CREATE, self.on_create) self.Bind(wx.EVT_WINDOW_DESTROY, self.on_destroy) self.hgap = DEFAULT_GAP self.vgap = DEFAULT_GAP self.prevSize = None self.destroyed = False def set_gaps(self, hgap, vgap): self.hgap = hgap self.vgap = vgap self.Layout() def Layout(self): """ Layout the child controls of the window using the flow layout algorithm. Current scroll location is taken into consideration. """ (windowWidth, windowHeight) = self.GetClientSize() windowWidth -= self.hgap*2 windowHeight -= self.vgap*2 x = self.hgap y = self.vgap rightEnd = 0 bottomEnd = 0 currRowHeight = 0 # Move and resize each item for item in self.GetChildren(): # All items get the size they need (itemWidth, itemHeight) = item.GetBestSize() # If the row has ended - move to the next row if x + itemWidth >= windowWidth: x = self.hgap y += currRowHeight + self.vgap currRowHeight = 0 # Move the child window scrolledX, scrolledY = self.CalcScrolledPosition(x, y) item.SetDimensions(scrolledX, scrolledY, itemWidth, itemHeight) # Update maximum widht and height for use by SetVirtualSize at the end rightEnd = max(rightEnd, x+itemWidth) bottomEnd = max(bottomEnd, y+itemHeight) # Calculate the position of the next child window x = x + itemWidth + self.hgap currRowHeight = max(currRowHeight, itemHeight) # Adjust the virtual size of the window to the total size of all child windows self.SetVirtualSize((rightEnd, bottomEnd)) self.AdjustScrollbars() self.Update() def on_create(self, event): self.SetScrollRate(DEFAULT_SCROLL_RATE, DEFAULT_SCROLL_RATE) def on_destroy(self, event): self.destroyed = True def on_size(self, event): if self.prevSize != event.GetSize(): self.prevSize = event.GetSize() self.Layout() if __name__ == "__main__": class ColorPanel(wx.Window): def __init__(self, parent, color): wx.Window.__init__(self, parent) self.SetBackgroundColour(color) self.SetSize((50,50)) app = wx.App(0) frame = wx.Frame(None, title="FlowSizer Test") import wx.lib.scrolledpanel as scrolled scroll = FlowPanel(frame) for w in range(20): cp = ColorPanel(scroll, "red") button = wx.Button(scroll, label="A Button") s2 = wx.BoxSizer() s2.Add(scroll, 1, wx.EXPAND) frame.SetSizer(s2) scroll.SetFocusIgnoringChildren() frame.Show() app.MainLoop()