Making a wx.Slider with steps

Hi guys,
I’m trying to make a wx.Slider with steps. That is, when the user moves the “pointer”, it only moves accordingly to the step value. I’m only working with steps that are multiple of the min and max value, but you can make that work if you want. :smiley:

Well, here’s my code so far. When using the arrow keys, works OK, but not for the mouse.
Thank you!

import wx

class Mywin(wx.Frame):
   def __init__(self, parent, title):
        super(Mywin, self).__init__(parent, title = title, size=(250,150))
  
        self.min = 0
        self.lastValue = self.min
        self.max = 100
        self.step = 25

        self.InitUI()
        self.CenterOnScreen()

   def InitUI(self):
        vbox = wx.BoxSizer(wx.VERTICAL)

        self.sld = wx.Slider(self, value=self.min, minValue=self.min, maxValue=self.max,
            style = wx.SL_HORIZONTAL|wx.SL_LABELS)

        vbox.Add(self.sld, 1, flag = wx.EXPAND | wx.TOP, border=20)

        self.sld.Bind(wx.EVT_SLIDER, self.OnSliderScroll)
        self.SetSizer(vbox)

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

        if val >= self.max or val <= self.min:
            return

        if val > self.lastValue:
            newValue = self.lastValue + self.step
            self.sld.SetValue(newValue)
        else:
            newValue = self.lastValue - self.step
            self.sld.SetValue(newValue)

        self.lastValue = newValue

ex = wx.App()
Mywin(None, 'Slider demo').Show()
ex.MainLoop()

If you keep fidgeting with the scrolling behaviour, you’ll get a lot of flickering I’m afraid.
That said, a “step” slider is interesting, but I’d rather look for a more general solution - a “generic” slider with arbitrary values.
Something like this:

class FancySlider(wx.Panel):
    def __init__(self, parent, id=wx.ID_ANY, 
                 choices=None, # a list of strings
                 pos=wx.DefaultPosition, size=wx.DefaultSize, 
                 style=wx.SL_HORIZONTAL, # either SL_HORIZONTAL or SL_VERTICAL
                 name='FancySlider'):
        wx.Panel.__init__(self, parent, id, pos, size, name=name)
        self.slider = wx.Slider(self, maxValue=len(choices)-1, style=style)
        if style == wx.SL_HORIZONTAL:
            label_sizer = wx.GridSizer(1, len(choices), 0, 0)
            label_align = wx.ALIGN_CENTRE_HORIZONTAL
            root_sizer = wx.BoxSizer(wx.VERTICAL)
        else:
            label_sizer = wx.GridSizer(len(choices), 1, 0, 0)
            label_align = wx.ALIGN_CENTRE_VERTICAL
            root_sizer = wx.BoxSizer(wx.HORIZONTAL)
        for choice in choices:
            label_sizer.Add(wx.StaticText(self, -1, choice), 0, label_align)
        root_sizer.Add(self.slider, 1, wx.EXPAND)
        root_sizer.Add(label_sizer, 0, wx.EXPAND)
        self.SetSizer(root_sizer)
        self.Fit()
        self.choices = choices
        self.slider.Bind(wx.EVT_SLIDER, self.on_slider)

    def on_slider(self, evt):
        # pretend the event was generated by "self", and let propagate
        evt.Skip()
        evt.SetEventObject(self)
        evt.SetId(self.GetId())
        # bonus property, not available from regular sliders!
        evt.SetString(self.GetValue())

    def GetValue(self): # -> str !! 
        return self.choices[self.slider.GetValue()]

    def SetValue(self, value):
        self.slider.SetValue(self.choices.index(value))

Of course, this is just a hack… the “right” thing would be to draw a custom widget from scratch, probably.
Anyway, this is how it can be used:

class MainFrame(wx.Frame):
    def __init__(self, *a, **k):
        wx.Frame.__init__(self, *a, **k)
        p = wx.Panel(self)
        slider_1 = FancySlider(p, choices='😃 😊 😐 😒 😧'.split(), 
                               pos=(100, 10), size=(200, -1), name='slider_1')
        slider_2 = FancySlider(p, choices=['the Bad', 'the Good', 'the Ugly'],
                               pos=(100, 100), size=(200, -1), name='slider_2')
        slider_3 = FancySlider(p, choices=[str(i) for i in range(10, 110, 10)], 
                               pos=(10, 10), size=(-1, 200), 
                               style=wx.SL_VERTICAL, name='slider_3')

        slider_1.Bind(wx.EVT_SLIDER, self.on_slider)
        slider_2.Bind(wx.EVT_SLIDER, self.on_slider)
        slider_3.Bind(wx.EVT_SLIDER, self.on_slider)

    def on_slider(self, evt):
        print(evt.GetEventObject().GetName(), evt.GetString())

app = wx.App()
MainFrame(None).Show()
app.MainLoop()

Here, “slider_3” is the “step slider” you were looking for… just remember that the values are strings now, so you’ll have to convert back to integers yourself.

1 Like

Thanks so much @ricpol
Your code will help me a lot!