views:

49

answers:

1

wx.SpinCtrl is limited to spinning across integers, and not floats. Therefore, I am building a wx.TextCtrl + wx.SpinButton combo class which enables me to spin across floats. I am able to size and layout both of them programmatically so that the combo looks exactly the same as an ordinary wx.SpinCtrl.

I am subclassing this combo from the wx.TextCtrl because I want its parent panel to catch wx.EVT_TEXT events. I would appreciate if you can improve on this argument of mine.

The wx.EVT_SPIN_UP and wx.EVT_SPIN_DOWN events from the wx.SpinButton are both internal implementations and the parent frame doesn't care about these events.

Now, I just hit a brick wall. My combo class doesn't work well with sizers. After .Add()ing the combo class to a wx.GridBagSizer, only the wx.TextCtrl is laid out within the wx.GridBagSizer. The wx.SpinButton is left on its own by itself. The wx.EVT_SPIN* bindings work very well, though.

My problem is the layout. How should I write the class if I want the wx.GridBagSizer to treat it as one widget?

Here is my combo class code:

class SpinnerSuper(wx.TextCtrl):
  def __init__(self, parent, max):
    wx.TextCtrl.__init__(self, parent=parent, size=(48, -1))
    spin = wx.SpinButton(parent=parent, style=wx.SP_VERTICAL, size=(-1, 21))
    self.OnInit()
    self.layout(spin)
    self.internalBindings(spin)
    self.SizerFlag = wx.ALIGN_CENTER

    self.min = 0
    self.max = max

  def OnInit(self):
    self.SetValue(u"0.000")

  def layout(self, spin):
    pos = self.GetPosition()
    size = self.GetSize()
    RightEdge = pos[0] + size[0]
    TopEdge = pos[1] - (spin.GetSize()[1]/2 - size[1]/2)
    spin.SetPosition((RightEdge, TopEdge))

  def internalBindings(self, spin):
    spin.Bind(wx.EVT_SPIN_UP, self.handlerSpinUp(self), spin)
    spin.Bind(wx.EVT_SPIN_DOWN, self.handlerSpinDown(self), spin)

  def handlerSpinUp(CallerObject, *args):
    def handler(CallerObject, *data):
        text = data[0]
        prev = text.GetValue()
        next = float(prev) + 0.008
        text.SetValue("{0:0.3f}".format(next))
    return lambda event: handler(CallerObject, *args)

  def handlerSpinDown(CallerObject, *args):
    def handler(CallerObject, *data):
        text = data[0]
        prev = text.GetValue()
        next = float(prev) - 0.008
        text.SetValue("{0:0.3f}".format(next))
    return lambda event: handler(CallerObject, *args)
A: 

You need to override DoGetBestSize() if you want your control to be correctly managed by sizers. Have a look at CreatingCustomControls.

You could also have a look at FloatSpin that ships with wxPython (in wx.lib.agw) from version 2.8.9.2 upwards.

In response to your comments:

  • Implementing DoGetBestSize() does not require drawing bitmaps directly. You just need to find a way, how you can determine the best size of your new widget. Typically you'd just use the sizes of the two widgets it is composed of (text + spinner) as basis.
  • To let sizers treat two widgets as one, you can place them in another sizer.
  • The recommended way to implement a custom widget with wxPython is to derive your new widget from wx.PyControl, add a sizer to it and add the two widgets you want to combine to that sizer.
Ralph
Okay I'll take a look at `wx.lib.agw.FloatSpin`. About the other link you gave me (CreatingCustomControls), it requires drawing bitmaps directly, which I'm not willing to do at the moment. Is there a procedure in wxPython which enables a sizer to treat two widgets as one? It should be similar to the group function when drawing many objects in MS Word.
Kit
`FloatSpin` looks promising :) But this `wx.PyControl` with a `wx.Sizer` containing my `wx.TextCtrl` and `wx.SpinButton` is really interesting. I'm running into problems with `.SetSizer(sizer)`ing the `wx.PyControl`, but that would be another question. Thanks for the time, Ralph!
Kit