草庐IT

python - 在 Linux 上使用范围创建 wxSlider

coder 2023-05-27 原文

我正在尝试使用 Python 中的 wxSlider 创建一个带有范围选择选项的 slider 。它有一个可选的范围参数,但问题是:

SL_SELRANGE:允许用户选择 slider 上的范围。仅限 Windows。

我正在使用 Linux。 我想我可以继承 wxSlider 并让它在 Linux 上运行,或者我自己创建一个自定义小部件。问题是我不确定如何选择这两种方法。 任何想法/指针/指出我正确的方向将不胜感激。

我尝试了类似的方法:

range_slider = wx.Slider(parent, wx.ID_ANY, 0, 0, 100, style=wx.SL_HORIZONTAL | wx.SL_LABELS | wx.SL_SELRANGE)

但是“SL_SELRANGE”在 Linux 上什么都不做(应该提供两个句柄来选择范围)。

最佳答案

我知道这个问题已有好几年的历史了,但即使为时已晚无法帮助您,它也可能对其他人有所帮助,因为我最近遇到了同样的问题。

问题

即使在 Windows 中,wx.SL_SELRANGE 样式也不会像预期的那样运行,它创建了两个独立的“拇指”或句柄,这将允许用户选择一个范围(参见 this similar questiondocumentation )。相反,它实际上所做的是在轨迹栏中绘制一个静态带,它不与单个用户控制的拇指交互。据我所知,无法将现有的 wx.Slider 控件自定义为具有两个拇指,因为该控件是操作系统的原生控件。

解决方案

在我正在构建的一个应用程序中,我需要使用一个可以满足您需求的控件,但也无法在网上找到任何好的替代品。我最终做的是创建自己的自定义 RangeSlider 小部件,它模仿常规 wx.Slider 的行为和功能,但有两个拇指:

但请注意,RangeSlider 类自己处理所有图形渲染,我让它模仿 Windows 10 的外观。因此 slider 的外观不会与不同操作系统的风格相匹配,但它仍然可以在 Linux 或 OSX 中工作。如有必要,您可以通过更改颜色和形状来自定义外观(我所做的只是绘制矩形和多边形)。

小部件有一些限制,它目前不支持样式(例如,没有刻度或垂直 slider )或验证器,但我确实实现了 wx.EVT_SLIDER 事件,所以其他如果值发生变化,可以通知控件(这是我用来在用户移动拇指时使用 slider 值动态更新文本的方法)。

您可以在下面找到一个工作示例的代码(它也可以在此 GitHub gist 中找到,我可能会随着时间的推移进行改进)。

import wx


def fraction_to_value(fraction, min_value, max_value):
    return (max_value - min_value) * fraction + min_value


def value_to_fraction(value, min_value, max_value):
    return float(value - min_value) / (max_value - min_value)


class SliderThumb:
    def __init__(self, parent, value):
        self.parent = parent
        self.dragged = False
        self.mouse_over = False
        self.thumb_poly = ((0, 0), (0, 13), (5, 18), (10, 13), (10, 0))
        self.thumb_shadow_poly = ((0, 14), (4, 18), (6, 18), (10, 14))
        min_coords = [float('Inf'), float('Inf')]
        max_coords = [-float('Inf'), -float('Inf')]
        for pt in list(self.thumb_poly) + list(self.thumb_shadow_poly):
            for i_coord, coord in enumerate(pt):
                if coord > max_coords[i_coord]:
                    max_coords[i_coord] = coord
                if coord < min_coords[i_coord]:
                    min_coords[i_coord] = coord
        self.size = (max_coords[0] - min_coords[0],
                     max_coords[1] - min_coords[1])

        self.value = value
        self.normal_color = wx.Colour((0, 120, 215))
        self.normal_shadow_color = wx.Colour((120, 180, 228))
        self.dragged_color = wx.Colour((204, 204, 204))
        self.dragged_shadow_color = wx.Colour((222, 222, 222))
        self.mouse_over_color = wx.Colour((23, 23, 23))
        self.mouse_over_shadow_color = wx.Colour((132, 132, 132))

    def GetPosition(self):
        min_x = self.GetMin()
        max_x = self.GetMax()
        parent_size = self.parent.GetSize()
        min_value = self.parent.GetMin()
        max_value = self.parent.GetMax()
        fraction = value_to_fraction(self.value, min_value, max_value)
        pos = (fraction_to_value(fraction, min_x, max_x), parent_size[1] / 2 + 1)
        return pos

    def SetPosition(self, pos):
        pos_x = pos[0]
        # Limit movement by the position of the other thumb
        who_other, other_thumb = self.GetOtherThumb()
        other_pos = other_thumb.GetPosition()
        if who_other == 'low':
            pos_x = max(other_pos[0] + other_thumb.size[0]/2 + self.size[0]/2, pos_x)
        else:
            pos_x = min(other_pos[0] - other_thumb.size[0]/2 - self.size[0]/2, pos_x)
        # Limit movement by slider boundaries
        min_x = self.GetMin()
        max_x = self.GetMax()
        pos_x = min(max(pos_x, min_x), max_x)

        fraction = value_to_fraction(pos_x, min_x, max_x)
        self.value = fraction_to_value(fraction, self.parent.GetMin(), self.parent.GetMax())
        # Post event notifying that position changed
        self.PostEvent()

    def GetValue(self):
        return self.value

    def SetValue(self, value):
        self.value = value
        # Post event notifying that value changed
        self.PostEvent()

    def PostEvent(self):
        event = wx.PyCommandEvent(wx.EVT_SLIDER.typeId, self.parent.GetId())
        event.SetEventObject(self.parent)
        wx.PostEvent(self.parent.GetEventHandler(), event)

    def GetMin(self):
        min_x = self.parent.border_width + self.size[0] / 2
        return min_x

    def GetMax(self):
        parent_size = self.parent.GetSize()
        max_x = parent_size[0] - self.parent.border_width - self.size[0] / 2
        return max_x

    def IsMouseOver(self, mouse_pos):
        in_hitbox = True
        my_pos = self.GetPosition()
        for i_coord, mouse_coord in enumerate(mouse_pos):
            boundary_low = my_pos[i_coord] - self.size[i_coord] / 2
            boundary_high = my_pos[i_coord] + self.size[i_coord] / 2
            in_hitbox = in_hitbox and (boundary_low <= mouse_coord <= boundary_high)
        return in_hitbox

    def GetOtherThumb(self):
        if self.parent.thumbs['low'] != self:
            return 'low', self.parent.thumbs['low']
        else:
            return 'high', self.parent.thumbs['high']

    def OnPaint(self, dc):
        if self.dragged or not self.parent.IsEnabled():
            thumb_color = self.dragged_color
            thumb_shadow_color = self.dragged_shadow_color
        elif self.mouse_over:
            thumb_color = self.mouse_over_color
            thumb_shadow_color = self.mouse_over_shadow_color
        else:
            thumb_color = self.normal_color
            thumb_shadow_color = self.normal_shadow_color
        my_pos = self.GetPosition()

        # Draw thumb shadow (or anti-aliasing effect)
        dc.SetBrush(wx.Brush(thumb_shadow_color, style=wx.BRUSHSTYLE_SOLID))
        dc.SetPen(wx.Pen(thumb_shadow_color, width=1, style=wx.PENSTYLE_SOLID))
        dc.DrawPolygon(points=self.thumb_shadow_poly,
                       xoffset=my_pos[0] - self.size[0]/2,
                       yoffset=my_pos[1] - self.size[1]/2)
        # Draw thumb itself
        dc.SetBrush(wx.Brush(thumb_color, style=wx.BRUSHSTYLE_SOLID))
        dc.SetPen(wx.Pen(thumb_color, width=1, style=wx.PENSTYLE_SOLID))
        dc.DrawPolygon(points=self.thumb_poly,
                       xoffset=my_pos[0] - self.size[0] / 2,
                       yoffset=my_pos[1] - self.size[1] / 2)


class RangeSlider(wx.Panel):
    def __init__(self, parent, id=wx.ID_ANY, lowValue=None, highValue=None, minValue=0, maxValue=100,
                 pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.SL_HORIZONTAL, validator=wx.DefaultValidator,
                 name='rangeSlider'):
        if style != wx.SL_HORIZONTAL:
            raise NotImplementedError('Styles not implemented')
        if validator != wx.DefaultValidator:
            raise NotImplementedError('Validator not implemented')
        super().__init__(parent=parent, id=id, pos=pos, size=size, name=name)
        self.SetMinSize(size=(max(50, size[0]), max(26, size[1])))
        if minValue > maxValue:
            minValue, maxValue = maxValue, minValue
        self.min_value = minValue
        self.max_value = maxValue
        if lowValue is None:
            lowValue = self.min_value
        if highValue is None:
            highValue = self.max_value
        if lowValue > highValue:
            lowValue, highValue = highValue, lowValue
        lowValue = max(lowValue, self.min_value)
        highValue = min(highValue, self.max_value)

        self.border_width = 8

        self.thumbs = {
            'low': SliderThumb(parent=self, value=lowValue),
            'high': SliderThumb(parent=self, value=highValue)
        }
        self.thumb_width = self.thumbs['low'].size[0]

        # Aesthetic definitions
        self.slider_background_color = wx.Colour((231, 234, 234))
        self.slider_outline_color = wx.Colour((214, 214, 214))
        self.selected_range_color = wx.Colour((0, 120, 215))
        self.selected_range_outline_color = wx.Colour((0, 120, 215))

        # Bind events
        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
        self.Bind(wx.EVT_LEFT_UP, self.OnMouseUp)
        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
        self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseLost)
        self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
        self.Bind(wx.EVT_SIZE, self.OnResize)

    def Enable(self, enable=True):
        super().Enable(enable)
        self.Refresh()

    def Disable(self):
        super().Disable()
        self.Refresh()

    def SetValueFromMousePosition(self, click_pos):
        for thumb in self.thumbs.values():
            if thumb.dragged:
                thumb.SetPosition(click_pos)

    def OnMouseDown(self, evt):
        if not self.IsEnabled():
            return
        click_pos = evt.GetPosition()
        for thumb in self.thumbs.values():
            if thumb.IsMouseOver(click_pos):
                thumb.dragged = True
                thumb.mouse_over = False
                break
        self.SetValueFromMousePosition(click_pos)
        self.CaptureMouse()
        self.Refresh()

    def OnMouseUp(self, evt):
        if not self.IsEnabled():
            return
        self.SetValueFromMousePosition(evt.GetPosition())
        for thumb in self.thumbs.values():
            thumb.dragged = False
        if self.HasCapture():
            self.ReleaseMouse()
        self.Refresh()

    def OnMouseLost(self, evt):
        for thumb in self.thumbs.values():
            thumb.dragged = False
            thumb.mouse_over = False
        self.Refresh()

    def OnMouseMotion(self, evt):
        if not self.IsEnabled():
            return
        refresh_needed = False
        mouse_pos = evt.GetPosition()
        if evt.Dragging() and evt.LeftIsDown():
            self.SetValueFromMousePosition(mouse_pos)
            refresh_needed = True
        else:
            for thumb in self.thumbs.values():
                old_mouse_over = thumb.mouse_over
                thumb.mouse_over = thumb.IsMouseOver(mouse_pos)
                if old_mouse_over != thumb.mouse_over:
                    refresh_needed = True
        if refresh_needed:
            self.Refresh()

    def OnMouseEnter(self, evt):
        if not self.IsEnabled():
            return
        mouse_pos = evt.GetPosition()
        for thumb in self.thumbs.values():
            if thumb.IsMouseOver(mouse_pos):
                thumb.mouse_over = True
                self.Refresh()
                break

    def OnMouseLeave(self, evt):
        if not self.IsEnabled():
            return
        for thumb in self.thumbs.values():
            thumb.mouse_over = False
        self.Refresh()

    def OnResize(self, evt):
        self.Refresh()

    def OnPaint(self, evt):
        w, h = self.GetSize()
        # BufferedPaintDC should reduce flickering
        dc = wx.BufferedPaintDC(self)
        background_brush = wx.Brush(self.GetBackgroundColour(), wx.SOLID)
        dc.SetBackground(background_brush)
        dc.Clear()
        # Draw slider
        track_height = 12
        dc.SetPen(wx.Pen(self.slider_outline_color, width=1, style=wx.PENSTYLE_SOLID))
        dc.SetBrush(wx.Brush(self.slider_background_color, style=wx.BRUSHSTYLE_SOLID))
        dc.DrawRectangle(self.border_width, h/2 - track_height/2, w - 2 * self.border_width, track_height)
        # Draw selected range
        if self.IsEnabled():
            dc.SetPen(wx.Pen(self.selected_range_outline_color, width=1, style=wx.PENSTYLE_SOLID))
            dc.SetBrush(wx.Brush(self.selected_range_color, style=wx.BRUSHSTYLE_SOLID))
        else:
            dc.SetPen(wx.Pen(self.slider_outline_color, width=1, style=wx.PENSTYLE_SOLID))
            dc.SetBrush(wx.Brush(self.slider_outline_color, style=wx.BRUSHSTYLE_SOLID))
        low_pos = self.thumbs['low'].GetPosition()[0]
        high_pos = self.thumbs['high'].GetPosition()[0]
        dc.DrawRectangle(low_pos, h / 2 - track_height / 4, high_pos - low_pos, track_height / 2)
        # Draw thumbs
        for thumb in self.thumbs.values():
            thumb.OnPaint(dc)
        evt.Skip()

    def OnEraseBackground(self, evt):
        # This should reduce flickering
        pass

    def GetValues(self):
        return self.thumbs['low'].value, self.thumbs['high'].value

    def SetValues(self, lowValue, highValue):
        if lowValue > highValue:
            lowValue, highValue = highValue, lowValue
        lowValue = max(lowValue, self.min_value)
        highValue = min(highValue, self.max_value)
        self.thumbs['low'].SetValue(lowValue)
        self.thumbs['high'].SetValue(highValue)
        self.Refresh()

    def GetMax(self):
        return self.max_value

    def GetMin(self):
        return self.min_value

    def SetMax(self, maxValue):
        if maxValue < self.min_value:
            maxValue = self.min_value
        _, old_high = self.GetValues()
        if old_high > maxValue:
            self.thumbs['high'].SetValue(maxValue)
        self.max_value = maxValue
        self.Refresh()

    def SetMin(self, minValue):
        if minValue > self.max_value:
            minValue = self.max_value
        old_low, _ = self.GetValues()
        if old_low < minValue:
            self.thumbs['low'].SetValue(minValue)
        self.min_value = minValue
        self.Refresh()


class TestFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'Range Slider Demo', size=(300, 100))
        panel = wx.Panel(self)
        b = 6
        vbox = wx.BoxSizer(orient=wx.VERTICAL)
        vbox.Add(wx.StaticText(parent=panel, label='Custom Range Slider:'), flag=wx.ALIGN_LEFT | wx.ALL, border=b)
        self.rangeslider = RangeSlider(parent=panel, lowValue=20, highValue=80, minValue=0, maxValue=100,
                                       size=(300, 26))
        self.rangeslider.Bind(wx.EVT_SLIDER, self.rangeslider_changed)
        vbox.Add(self.rangeslider, proportion=1, flag=wx.EXPAND | wx.ALL, border=b)
        self.rangeslider_static = wx.StaticText(panel)
        vbox.Add(self.rangeslider_static, flag=wx.ALIGN_LEFT | wx.ALL, border=b)
        vbox.Add(wx.StaticText(parent=panel, label='Regular Slider with wx.SL_SELRANGE style:'),
                 flag=wx.ALIGN_LEFT | wx.ALL, border=b)
        self.slider = wx.Slider(parent=panel, style=wx.SL_SELRANGE)
        self.slider.SetSelection(20, 40)
        self.slider.Bind(wx.EVT_SLIDER, self.slider_changed)
        vbox.Add(self.slider, proportion=1, flag=wx.EXPAND | wx.ALL, border=b)
        self.slider_static = wx.StaticText(panel)
        vbox.Add(self.slider_static, flag=wx.ALIGN_LEFT | wx.ALL, border=b)
        self.button_toggle = wx.Button(parent=panel, label='Disable')
        self.button_toggle.Bind(wx.EVT_BUTTON, self.toggle_slider_enable)
        vbox.Add(self.button_toggle, flag=wx.ALIGN_CENTER | wx.ALL, border=b)
        panel.SetSizerAndFit(vbox)
        box = wx.BoxSizer()
        box.Add(panel, proportion=1, flag=wx.EXPAND)
        self.SetSizerAndFit(box)

    def slider_changed(self, evt):
        obj = evt.GetEventObject()
        val = obj.GetValue()
        self.slider_static.SetLabel('Value: {}'.format(val))

    def rangeslider_changed(self, evt):
        obj = evt.GetEventObject()
        lv, hv = obj.GetValues()
        self.rangeslider_static.SetLabel('Low value: {:.0f}, High value: {:.0f}'.format(lv, hv))

    def toggle_slider_enable(self, evt):
        if self.button_toggle.GetLabel() == 'Disable':
            self.slider.Enable(False)
            self.rangeslider.Enable(False)
            self.button_toggle.SetLabel('Enable')
        else:
            self.slider.Enable(True)
            self.rangeslider.Enable(True)
            self.button_toggle.SetLabel('Disable')


def main():
    app = wx.App()
    TestFrame().Show()
    app.MainLoop()


if __name__ == "__main__":
    main()

关于python - 在 Linux 上使用范围创建 wxSlider,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29745635/

有关python - 在 Linux 上使用范围创建 wxSlider的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐