how to use wx.Slider with SELRANGE?

1.8k views Asked by At

In the docs for wx.Slider (wxPython for py2, wxPython for py3, wxWidgets), there is listed a widget control named wx.SL_SELRANGE, defined to allow "the user to select a range on the slider (MSW only)". To me, this speaks of a twin-control, two sliders on the same axis in order to define a low/high range. I can't get it to show two controls.

Basic code to get it started. I'm not even worried yet about methods, events, or whatnot at this point, just to show something.

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # ... sizers and other stuff
        self.myslider = wx.Slider(self.notebook_1_pane_2, wx.ID_ANY, 0, -100, 100, style=wx.SL_SELRANGE)
        # ...
        self.myslider.SetSelection(10, 90)

With all of that, the most I've been able to get it to show is a blue line spanning about where I would expect things to be.

enter image description here

The wxPython docs all talk about it but how is the user supposed to be able to "select a range on the slider", like shown here (taken from shiny)?

enter image description here

What am I missing? Are there any reasonable public examples of a wxPython wx.Slider in the wild with this functionality?

PS:

  • One page I found speaks of WinXP only, but since that page hasn't been updated in seven years, I don't consider it authoritative on the version restriction.
  • I've been using wxGlade for gui layout, but I'm certainly willing/able to go into the code after export and muck around.

System: win81_64, python-2.7.10, wxPython-3.0.2.0

3

There are 3 answers

0
shiftyscales On BEST ANSWER

I have made a custom implementation for this, partly using a code from this question. Left click on the slider area sets the left border of the range, right click sets the right border. Dragging the slider moves the selection. left_gap and right_gap indicates what is the empty space between edges of the widget and actual start of the drawn slider. As in the source, these must be found out by experimentation.

class RangeSlider(wx.Slider):
def __init__(self, left_gap, right_gap, *args, **kwargs):
    wx.Slider.__init__(self, *args, **kwargs)
    self.left_gap = left_gap
    self.right_gap = right_gap
    self.Bind(wx.EVT_LEFT_UP, self.on_left_click)
    self.Bind(wx.EVT_RIGHT_UP, self.on_right_click)
    self.Bind(wx.EVT_SCROLL_PAGEUP, self.on_pageup)
    self.Bind(wx.EVT_SCROLL_PAGEDOWN, self.on_pagedown)
    self.Bind(wx.EVT_SCROLL_THUMBTRACK, self.on_slide)

    self.slider_value=self.Value
    self.is_dragging=False

def linapp(self, x1, x2, y1, y2, x):
    proportion=float(x - x1) / (x2 - x1)
    length = y2 - y1
    return round(proportion*length + y1)

# if left click set the start of selection
def on_left_click(self, e):

    if not self.is_dragging: #if this wasn't a dragging operation
        position = self.get_position(e)
        if position <= self.SelEnd:
            self.SetSelection(position, self.SelEnd)
        else:
            self.SetSelection(self.SelEnd, position)
    else:
        self.is_dragging = False
    e.Skip()

# if right click set the end of selection
def on_right_click(self, e):
    position = self.get_position(e)
    if position >= self.SelStart:
        self.SetSelection(self.SelStart, position)
    else:
        self.SetSelection(position, self.SelStart)
    e.Skip()

# drag the selection along when sliding
def on_slide(self, e):
    self.is_dragging=True
    delta_distance=self.Value-self.slider_value
    self.SetSelection(self.SelStart+delta_distance, self.SelEnd+delta_distance)
    self.slider_value=self.Value

# disable pageup and pagedown using following functions
def on_pageup(self, e):
    self.SetValue(self.Value+self.PageSize)

def on_pagedown(self, e):
    self.SetValue(self.Value-self.PageSize)

# get click position on the slider scale
def get_position(self, e):
    click_min = self.left_gap #standard size 9
    click_max = self.GetSize()[0] - self.right_gap #standard size 55
    click_position = e.GetX()
    result_min = self.GetMin()
    result_max = self.GetMax()
    if click_position > click_min and click_position < click_max:
        result = self.linapp(click_min, click_max,
                             result_min, result_max,
                             click_position)
    elif click_position <= click_min:
        result = result_min
    else:
        result = result_max

    return result
0
Pasa On

I had this same problem before and couldn't find a good solution. What I ended up doing was creating my own custom RangeSlider widget with two actual thumbs. Code is available in this answer, or in this GitHub gist.

Range slider demo.

0
VZ. On

Better late than never: to answer the original question, wxSL_SELRANGE does work but it only results in the expected appearance if it's combined with wxSL_LABELS. With both of these styles (and the selection set to 20..80 for the total range 0..100) the control appears like this: screenshot of wxSlider