views:

52

answers:

2

Can a wx.PyControl contain a wx.Sizer?

Note that what I am ultimately trying to do here (spinner with float values) is already answered in another question. I am particularly interested in layouting widgets within a wx.PyControl, a skill which might prove useful if I come across a need to make my own custom widgets. I already read through CreatingCustomControls, but it didn't use sizers within the wx.PyControl subclass.

Using my code below, my CustomWidget just doesn't look right. I'm not yet doing a DoGetBestSize because I think that applies to a wx.Sizer acting on a widget. I am actually having a wx.Sizer doing its thing inside a CustomWidget.

Here is my code (without the event bindings between sub-widgets):
EDIT: Here is my corrected class code, thanks to Steven Sproat:

import wx

class CustomWidget(wx.PyControl):
  def __init__(self, parent):
    wx.PyControl.__init__(self, parent=parent, style=wx.NO_BORDER) # Style added.
    text = wx.TextCtrl(parent=self)
    spin = wx.SpinButton(parent=self, style=wx.SP_VERTICAL)
    sizer = wx.GridBagSizer()
    self.layout(text, spin, sizer)
    self.OnInit(text, sizer)

  def OnInit(self, text, sizer):
    text.SetValue(u"0.000")

  def layout(self, text, spin, sizer):
    self.SetSizer(sizer)
    sizer.Add(text, pos=(0, 0), flag=wx.ALIGN_CENTER)
    sizer.Add(spin, pos=(0, 1), flag=wx.ALIGN_CENTER)
    self.Fit()
    self.Layout() # This is what I lacked. I needed to call .Layout()
    self.CenterOnParent()
+1  A: 

Yes, it can. You just need to call Layout() to tell the sizer to recalculate/layout its children.

import wx

class Frame(wx.Frame):
  def __init__(self):
    wx.Frame.__init__(self, None)
    blah  = CustomWidget(self)
    self.Show(True)

class CustomWidget(wx.PyControl):
  def __init__(self, parent):
    wx.PyControl.__init__(self, parent=parent)
    text = wx.TextCtrl(parent=self)
    spin = wx.SpinButton(parent=self, style=wx.SP_VERTICAL)
    sizer = wx.GridBagSizer()
    self.layout(text, spin, sizer)
    self.OnInit(text, sizer)

  def OnInit(self, text, sizer):
    text.SetValue(u"0.000")

  def layout(self, text, spin, sizer):
    self.SetSizer(sizer)
    sizer.Add(text, pos=(0, 0), flag=wx.ALIGN_CENTER)
    sizer.Add(spin, pos=(0, 1), flag=wx.ALIGN_CENTER)
    self.Fit()
    self.Layout()
    self.CenterOnParent()

app = wx.App()
f = Frame()
app.MainLoop()

By the way, it would be nice in the future if you could attach a runnable sample as above :)

Steven Sproat
@Steven: This is excellent! And thanks for the community tip about the runnable sample. I'll do so next time :) I also edited my question to include your solution.
Kit
@Steven: I did a few more experiments with this one, but I think it breaks down when the `CustomWidget` is a child of a `wx.Panel`, instead of a `wx.Frame`. Your thoughts?
Kit
did you try as Robin suggested?
Steven Sproat
Yes I tried that, too. I composed a [new question here](http://stackoverflow.com/questions/3308485/wxpython-wx-pycontrol-layout-problem-when-it-is-a-child-of-a-wx-panel)
Kit
+1  A: 

Since classes derived from wxControl are not normally used for containing other widgets the auto-layout code is not present and so it will not call Layout() when it gets the EVT_SIZE event. You can easily add that ability to your class by Bind()ing a handler for EVT_SIZE and calling self.Layout() from there. Then it will act just like a panel does when it gets the size event and has children and a sizer.

RobinDunn