I’m writing todo-list application and need a list with collapsible items. I know about wx.CollapsiblePane
and wx.lib.agw.foldpanelbar.FoldPanelBar
, but neither let’s you customize the collapsed item label in a complex way. I want to have the simple todo title together with a clickable checkbox as the collapsed view and, upon clicking on the checkboxe’s label, it expands to show a note, a deadline date, etc. I’ve created a protype using static text, but even that one is not working. Using the checkboxes will only make it more complicated by adding more mouse event listeners.
If you just use StaticText
windows in ItemPanel
(comented out), it works. Why does it not work for self defined panel classes?
import wx
PADDING = 10
class App(wx.App):
def OnInit(self):
self.data = [
{'title':'1st', 'content':'First'},
{'title':'2nd', 'content':'Second'},
{'title':'3rd', 'content':'Third'},
]
frame = ListFrame()
frame.Show()
self.SetTopWindow(frame)
return True
class ListFrame(wx.Frame):
def __init__(self, title="List", size=(250, 500)):
wx.Frame.__init__(self, None, -1, title=title, size=size)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(wx.StaticText(panel, label='List Title'), flag=wx.EXPAND|wx.ALL, border=PADDING)
sizer.Add(wx.StaticLine(panel, style=wx.LI_HORIZONTAL), flag=wx.EXPAND)
sizer.Add(ListPanel(panel), proportion=1, flag=wx.EXPAND)
panel.SetSizer(sizer)
class ListPanel(wx.ScrolledWindow):
def __init__(self, parent):
super().__init__(parent)
self.data = wx.GetApp().data
self.SetScrollRate(0,5)
self.sizer = wx.BoxSizer(wx.VERTICAL)
for item in self.data:
item_panel = ItemPanel(self, item)
self.sizer.Add(item_panel, flag=wx.ALIGN_LEFT|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=PADDING)
self.SetSizer(self.sizer)
class ItemPanel(wx.Control):
def __init__(self, parent, item):
self.parent = parent
super().__init__(self.parent)
self.collapsed = True
self.item = item
self.sizer = wx.BoxSizer(wx.VERTICAL)
#self.panel_collapsed = wx.StaticText(self, label='> '+item['title'])
#self.panel_expanded = wx.StaticText(self, label='v '+item['title']+'\n\t'+item['content'])
self.panel_collapsed = ItemPanelCollapsed(self)
self.panel_expanded = ItemPanelExpanded(self)
self.panel_collapsed.Bind(wx.EVT_LEFT_UP, self.OnToggleView)
self.panel_expanded.Bind(wx.EVT_LEFT_UP, self.OnToggleView)
#self.Bind(wx.EVT_LEFT_UP, self.OnToggleView)
self.sizer.Add(self.panel_collapsed, proportion=1, flag=wx.EXPAND)
self.sizer.Add(self.panel_expanded, proportion=1, flag=wx.EXPAND)
self.sizer.Hide(self.panel_expanded)
self.SetSizer(self.sizer)
def OnToggleView(self, event):
if self.collapsed:
self.sizer.Hide(self.panel_collapsed)
self.sizer.Show(self.panel_expanded)
else:
self.sizer.Hide(self.panel_expanded)
self.sizer.Show(self.panel_collapsed)
self.collapsed = not self.collapsed
self.sizer.Layout()
self.parent.sizer.Layout()
class ItemPanelCollapsed(wx.Control):
def __init__(self, parent):
super().__init__(parent, style=wx.BORDER_NONE)
sizer = wx.BoxSizer(wx.VERTICAL)
#sizer.Add(wx.CheckBox(self, label=parent.item['title']), proportion=1, flag=wx.EXPAND)
sizer.Add(wx.StaticText(self, label='> '+parent.item['title']), proportion=1, flag=wx.EXPAND)
self.SetSizer(sizer)
class ItemPanelExpanded(wx.Control):
def __init__(self, parent):
super().__init__(parent, style=wx.BORDER_NONE)
sizer = wx.BoxSizer(wx.VERTICAL)
#sizer.Add(wx.CheckBox(self, label=parent.item['title']), proportion=1, flag=wx.EXPAND)
sizer.Add(wx.StaticText(self, label='v '+parent.item['title']), proportion=1, flag=wx.EXPAND)
sizer.Add(wx.StaticText(self, label=parent.item['content']), proportion=1, flag=wx.EXPAND)
self.SetSizer(sizer)
if __name__ == '__main__':
app = App(False)
app.MainLoop()
I’ve also tried binding the mouse event to the ItemPanel
(as comented out) instead of the child windows, but then it didn’t work again. So I guess it has something to do with how the mouse events are propagated up the chain. So I also tried letting all my custom classes inherit from wx.Panel
or wx.Window
instead, but that didn’t change the behaviour.
Optional: Since I’m new to wxPython, it would be kind, if you could maybe answer these related style questions
- Is this the correct way of achieving the task? Should I rather be modifying the ItemPanel directly instead of hiding/unhiding nested elements?
- If this method is somewhat right, whats the best way to “tricke up” the layout changes from
ItemPane.OnToggleView
? Calling allparent.sizer.Layout()
s up the chain (imagining even more nesting) seems somewhat cumbersome… - Is this the right way to do Model/View separation? Having the data in
App.data
and accessing it from the windows usingwx.GetApp()
. How do I automatically update the views whenApp.data
changes? - What’s the right way to define a global style? I’ve used a module wide constant
PADDING
, but maybe putting it into theApp
and accessing it viawx.GetApp()
would be better? But then basically every window calls this function? - What’s the correct class to inherit from in this case? I went with
wx.Panel
for the “high-level” elements andwx.Control
for more elementary widgets. I don’t really understand the difference betweenwx.Window
,wx.Panel
,wx.control
(that’s why I called itListPanel
when it’s inheriting fromwx.ScrolledWindow
…)