Search code examples
python-3.xwxpython

wxPython - generate handler functions on the fly


this is my first question on StackOverFlow.

Python version: 3.6.1, wxPython version: 4.0.1, OS: Windows 10, 64 bits.

I have been exploring wxPython for a scientific project. I need to generate sliders on the fly. This I can do along with StaticText elements to display the current value of the xth slider position. I have a function "CreateSliderFun" that returns a handler function. I bind the xth handler function to the xth slider widget. I would expect everything to be fine, but a typeerror occurs:

TypeError: fun() missing 1 required positional argument: 'e'

If i provide the hardcoded_fun the first added slider works as expected. Any extra sliders will be linked to the first generated StaticText element.

Picture of app and errors.

import wx

def main():
  app = wx.App(redirect=False)
  frame = MyApp(None, 'Hello World!')
  frame.Show(True)
  app.MainLoop()

class MyApp(wx.Frame):
    def __init__(self, parent, title):
      wx.Frame.__init__(self, parent, title=title, pos=(0,0), size=(400,600))

      self.panel = wx.Panel(self, pos=(0,0), size=wx.Size(400,600))

      self.AddSliderButton = wx.Button(self.panel, -1, "AddSlider", size=(80,20), pos=(120,80))
      self.AddSliderButton.Bind(wx.EVT_BUTTON, self.AddSliderHandler)

      self.sld = wx.Slider(self.panel, value=200,
        minValue=200-100,
        maxValue=200+100,
        pos=(60, 10),
        size=(250, -1), style=wx.SL_HORIZONTAL)

      self.txt = wx.StaticText(self.panel, label='200', pos=(20, 10))
      self.sld.Bind(wx.EVT_SCROLL, self.OnSliderScroll)

      self.AddSliderButton = wx.Button(self.panel, -1, "AddSlider", size=(80,20), pos=(120,80))
      self.AddSliderButton.Bind(wx.EVT_BUTTON, self.AddSliderHandler)

      self.statusbar = self.CreateStatusBar()
      self.statusbar.SetStatusText("Moin")

      self.ms = ManySliders(self.panel)

    def OnSliderScroll(self, e):
      obj = e.GetEventObject()
      val = obj.GetValue()
      self.txt.SetLabel(str(val))

    def AddSliderHandler(self, event):
      self.ms.AddSlider()
      self.statusbar.SetStatusText("Added slider number " + str(self.ms.NumberOfSliders))

class ManySliders:
  def __init__(self, controlpanel ):
    self.controlpanel = controlpanel
    self.txts = []
    self.sliders = []
    self.SliderFun = []
    self.size = (250, -1)
    self.style = wx.SL_HORIZONTAL
    self.NumberOfSliders = 0
  def AddSlider(self):
    i = self.NumberOfSliders
    txt = wx.StaticText(self.controlpanel, label=str(self.NumberOfSliders), pos=(20, 120 + i*20))
    self.txts.append(txt)
    sld = wx.Slider(self.controlpanel, value=self.NumberOfSliders,
      minValue=self.NumberOfSliders-100,
      maxValue=self.NumberOfSliders+100,
      pos=(60, 120+i*20),
      size=self.size, style=self.style)
    self.sliders.append(sld)
    sliderfun = self.CreateSliderFun(self.NumberOfSliders)
    self.SliderFun.append(sliderfun)
    self.sliders[i].Bind(wx.EVT_SCROLL, self.SliderFun[i])
    self.NumberOfSliders += 1

  def hardcoded_fun(self, e):
    """ if self.SliderFun[i] is replaced by hardcoded_fun, the first added slider will work,
    and all extra sliders added will be linked to the first created StaticText element
    """
    obj = e.GetEventObject()
    val = obj.GetValue()
    self.txts[0].SetLabel(str(val))

  def CreateSliderFun(self, i):
    def fun(self, e):
      print("but this is never shown due to TypeError: fun() missing 1 required positional argument: 'e'")
      obj = e.GetEventObject()
      val = obj.GetValue()
      self.txts[i].SetLabel(str(val))
    print("we create a new slider function... =",fun)
    return fun

main()

Solution

  • Your CreateSliderFun returns a function, that takes 2 parameters: self and e. You need this:

    def CreateSliderFun(self, i):
        def fun(e):
            ....
    

    You may consider not making handler on the fly, but doing

    self.Bind(wx.EVT_SCROLL, self.common_handler)
    

    and

    def common_handler(self, event):
        source = event.GetEventObject() # this is your slider
        txt_widget = self.txts[self.sliders.index(source)] 
        # I did not test it but you get the idea, right?