【问题标题】:Is it possible to expand the drawable area around the QSlider是否可以扩展 QSlider 周围的可绘制区域
【发布时间】:2019-06-12 04:30:57
【问题描述】:

我的目标是使用 PySide2 模块在 Python 3 中使用 tickmarks 和刻度标签自定义 QSlider .为此,我在派生类中编辑了 QSlider 类的默认paintEvent。然而,事实证明可打印区域是有限的,并且我放置的顶部/底部标签被裁剪(见截图)。我用来生成这些滑块的代码如下:

import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *

slider_x = 150
slider_y = 450
slider_step = [0.01, 0.1, 1, 10, 100]  # in microns


class MySlider(QSlider):
    def __init__(self, type, parent=None):
        super(MySlider, self).__init__(parent)
        self.Type = type

    def paintEvent(self, event):
        super(MySlider, self).paintEvent(event)
        qp = QPainter(self)
        pen = QPen()
        pen.setWidth(2)
        pen.setColor(Qt.red)

        qp.setPen(pen)
        font = QFont('Times', 10)
        qp.setFont(font)
        self.setContentsMargins(50, 50, 50, 50)
        # size = self.size()
        # print(size)
        # print("margins", self.getContentsMargins())
        # print(self.getContentsMargins())
        # print(self.contentsRect())
        contents = self.contentsRect()
        self.setFixedSize(QSize(slider_x, slider_y))
        max_slider = self.maximum()
        y_inc = 0
        for i in range(max_slider):
            qp.drawText(contents.x() - font.pointSize(), y_inc + font.pointSize() / 2, '{0:2}'.format(slider_step[i]))
            qp.drawLine(contents.x() + font.pointSize(), y_inc, contents.x() + contents.width(), y_inc)
            y_inc += slider_y/4


class Window(QWidget):
    """ Inherits from QWidget """
    def __init__(self):
        super().__init__()
        self.title = 'Control Stages'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 100
        self.AxesMapping = [0, 1, 2, 3]
        self.initUI()

    def initUI(self):
        """ Initializes the GUI either using the grid layout or the absolute position layout"""
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        Comp4 = self.createSlider("step_size")
        Comp5 = self.createSlider("speed")
        windowLayout = QGridLayout()
        windowLayout.setContentsMargins(50, 50, 50, 50)
        HGroupBox = QGroupBox()
        layout = QGridLayout()
        layout.addWidget(Comp4, 0, 0)
        layout.addWidget(Comp5, 0, 1)
        HGroupBox.setLayout(layout)
        HGroupBox.setFixedSize(QSize(740, 480))
        windowLayout.addWidget(HGroupBox, 0, 0)
        self.setLayout(windowLayout)
        self.show()

    def createSlider(self, variant):
        Slider = MySlider(Qt.Vertical)
        Slider.Type = variant
        Slider.setMaximum(5)
        Slider.setMinimum(1)
        Slider.setSingleStep(1)
        Slider.setTickInterval(1)
        Slider.valueChanged.connect(lambda: self.sliderChanged(Slider))
        return Slider

    @staticmethod
    def sliderChanged(Slider):
        print("Slider value changed to ", Slider.value(), "slider type is ", Slider.Type)
        if Slider.Type == "step_size":
            print("this is a step size slider")
        elif Slider.Type == "speed":
            print("this is a speed slider")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Window()
    sys.exit(app.exec_())

是否可以扩展 QSlider 周围的可绘制区域,如果可以,如何实现此效果?您可以在屏幕截图中看到,第一个和最后一个刻度线旁边的红色标签没有正确显示并且被裁剪(即在第一个刻度线标签中,标签 0.01 缺少 1 和 0 的顶部)。

编辑:在尝试了建议的解决方案后,顶部标签的一部分仍然被剪掉。下面的第二个版本在带有 PySide2 5.12.0 和 Python 3.6.6 的 Windows 10 64 位上仍然相似。

EDIT2 我有一个双启动系统,所以我在 Ubuntu 16.04.3 LTS 上使用 Python 3.5.2 / PySide 5.12.0 进行了尝试,它开箱即用。这是那里的屏幕截图,但不幸的是它必须在 Windows 上运行。

【问题讨论】:

  • 您可以更好地解释您的问题,您的代码无法执行,因此我无法将您获得的内容与您展示的图像进行比较以了解您想要的内容。
  • @eyllanesc 你说得对,我提供了 MWE 并进一步澄清了我的问题。如您所见,屏幕截图中的第一个和最后一个标签可能由于延伸到不可绘制的区域而无法正确显示。
  • @eyllanesc 您对如何使用 QSlider 实现自定义刻度线有什么建议吗?它们周围的可绘制区域似乎被剪裁了。setContentsMargins 方法不会更改该区域。
  • @Vesnog QSlider 继承了 QAbstractSlider - 你可以集成 SliderToMaximum 以查看顶部吗?只是一个想法.. (pyqt.sourceforge.net/Docs/PyQt4/…)
  • @Vesnog 我今晚对此的最后一句话 - doc.qt.io/qtforpython/PySide2/QtWidgets/… 不得不发帖让我清醒......zzzz

标签: python python-3.x pyside2 qslider


【解决方案1】:

在折腾了更多之后,我终于找到了解决方案。它包括样式表,我之前尝试过。但是,当时我无法正确实施它。 II 保持小部件尺寸不变,但减少了凹槽长度以适应可打印区域内的标签。完整代码如下:

import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *

slider_x = 150
slider_y = 450
slider_step = [0.01, 0.1, 1, 10, 100]  # in microns
groove_y = 400
handle_height = 10


class MySlider(QSlider):
    def __init__(self, type, parent=None):
        # super(MySlider, self).__init__(parent)
        super().__init__()
        self.parent = parent
        self.Type = type
        # self.setFixedHeight(115)
        self.setStyleSheet("""QSlider::groove:vertical {
    border: 1px solid black;
    height: """ + str(groove_y) + """ px;
    width: 10px;
    border-radius: 2px;
    }

    QSlider::handle:vertical {
        background: red;
        border: 1px solid red;
        height: """ + str(handle_height) + """ px;
        margin: 2px 0;
        border-radius: 1px;
    }

    QSlider::add-page:vertical {
        background: blue;
    }
    QSlider::sub-page:vertical {
        background: red;
""")

    def paintEvent(self, event):
        super(MySlider, self).paintEvent(event)
        qp = QPainter(self)
        pen = QPen()
        pen.setWidth(2)
        pen.setColor(Qt.black)

        qp.setPen(pen)
        font = QFont('Times', 10)
        qp.setFont(font)
        self.setContentsMargins(50, 50, 50, 50)
        self.setFixedSize(QSize(slider_x, slider_y))
        contents = self.contentsRect()
        max = self.maximum()
        min = self.minimum()

        y_inc = slider_y - (slider_y - groove_y) / 2
        for i in range(len(slider_step)):
            qp.drawText(contents.x() - 2 * font.pointSize(), y_inc + font.pointSize() / 2, '{0:3}'.format(slider_step[i]))
            qp.drawLine(contents.x() + 2 * font.pointSize(), y_inc, contents.x() + contents.width(), y_inc)
            y_inc -= groove_y / (max - min)



class Window(QWidget):
    """ Inherits from QWidget """
    def __init__(self):
        super().__init__()
        self.title = 'Control Stages'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 100
        self.AxesMapping = [0, 1, 2, 3]
        self.initUI()

    def initUI(self):
        """ Initializes the GUI either using the grid layout or the absolute position layout"""
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        Comp4 = self.createSlider("step_size")
        Comp5 = self.createSlider("speed")
        windowLayout = QGridLayout()
        windowLayout.setContentsMargins(50, 50, 50, 50)
        HGroupBox = QGroupBox()
        layout = QGridLayout()
        layout.addWidget(Comp4, 0, 0)
        layout.addWidget(Comp5, 0, 1)
        HGroupBox.setLayout(layout)
        HGroupBox.setFixedSize(QSize(740, 480))
        windowLayout.addWidget(HGroupBox, 0, 0)
        self.setLayout(windowLayout)
        self.show()

    def createSlider(self, variant):
        Slider = MySlider(Qt.Vertical)
        Slider.Type = variant
        Slider.setMaximum(len(slider_step))
        Slider.setMinimum(1)
        Slider.setSingleStep(1)
        Slider.setTickInterval(1)
        Slider.valueChanged.connect(lambda: self.sliderChanged(Slider))
        return Slider

    @staticmethod
    def sliderChanged(Slider):
        print("Slider value changed to ", Slider.value(), "slider type is ", Slider.Type)
        if Slider.Type == "step_size":
            print("this is a step size slider")
        elif Slider.Type == "speed":
            print("this is a speed slider")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Window()
    sys.exit(app.exec_())

【讨论】:

    【解决方案2】:

    我的建议是调查由 QSlider 继承的 QAbstractSlider。它包含一个名为triggerAction(SliderToMaximum 值为6)的方法,当action is triggered 时,可以使用该方法set the Slider Position

    触发动作的语法是

     QAbstractSlider.triggerAction (self, SliderAction action)
    

    SliderAction 的取值如下:

    QAbstractSlider.SliderNoAction 0 QAbstractSlider.SliderSingleStepAdd 1

    QAbstractSlider.SliderSingleStepSub 2

    QAbstractSlider.SliderPageStepAdd 3

    QAbstractSlider.SliderPageStepSub 4

    QAbstractSlider.SliderToMinimum 5

    QAbstractSlider.SliderToMaximum 6

    QAbstractSlider.SliderMove 7

    (source)

    使用PySide2,您可以使用

    调用这些方法
    PySide2.QtWidgets.QAbstractSlider.maximum()  //returns the value of the maximum
    

    PySide2.QtWidgets.QAbstractSlider.setMaximum(arg) // pass your own value
    

    【讨论】:

    • 感谢您的回答,据我所知PySide2.QtWidgets.QAbstractSlider.setMaximum(arg) 将可能的最大值设置为整数,而PySide2.QtWidgets.QAbstractSlider.maximum() 方法返回此值。那么标签的定位会如何受此影响呢?对不起,我有点困惑,不明白你在评论中的陈述The slide rmax value is 6, the minimum is 5, that's why it's cut off probably. I put my suggestions in an answer below.
    • @Vesnog 查看我在答案中输入的 QAbstractSlider 滑块操作值。 Slider ToMinimum 是 5,Slider ToMaximum 是 6,你说你打印的值是 5...这就是我以为你的意思,它一直处于最小值。
    • 您可以使用 setMaximum 设置最大值,否则它不会被调用,并且不需要参数。
    • 事实并非如此,请原谅我的误解。 print("The max is ", Comp4.maximum(), "The min is :", Comp4.minimum()) 的结果是 The max is 5 The min is : 1
    【解决方案3】:

    如果我做对了,您希望刻度在顶部和底部可见,因为它们似乎由于边距而被“切割”。 我认为这是假设小部件边距正确设置为零或类似的结果,您可以尝试更改数字并查看此效果。 (我尝试移动边距,但没有成功)

    现在,在old post 之后,我注意到实现类似的目标似乎很棘手,您可以根据基于minimummaximumQSlider 的公式来确定您的报价。

    由于底部的勾号将位于主窗口小部件下方,您只需添加一个特殊条件使其可见。

    不想粘贴整个代码,但您只需要在循环之前声明opthandle。计算里面的y 位置,并用它代替你的y_inc

    def paintEvent(self, event):                                                                                                                                                            
        super(MySlider, self).paintEvent(event)                                                     
        qp = QPainter(self)                                                                         
        pen = QPen()                                                                                
        pen.setWidth(2)                                                                             
        pen.setColor(Qt.red)                                                                        
    
        qp.setPen(pen)                                                                              
        font = QFont('Times', 10)                                                                   
        qp.setFont(font)                                                                            
        self.setContentsMargins(50, 50, 50, 50)                                                     
        # size = self.size()                                                                        
        # print(size)                                                                               
        # print("margins", self.getContentsMargins())                                               
        # print(self.getContentsMargins())                                                          
        # print(self.contentsRect())                                                                
        contents = self.contentsRect()                                                              
        self.setFixedSize(QSize(slider_x, slider_y))                                                
    
        # New code                                                                                  
        max_slider = self.maximum()                                                                 
        min_slider = self.minimum()                                                                 
        len_slider = max_slider - min_slider                                                        
        height = self.height()                                                                      
    
        opt = QStyleOptionSlider()                                                                  
        handle = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self)   
        handle_height = handle.height() # Add +10 on windows (workarount)                                                            
        height_diff = height - handle_height                                                        
        point_size = font.pointSize()                                                               
    
        for i in range(max_slider):                                                                 
            y = round(((1 - i / len_slider) * height_diff + (handle_height / 2.0))) - 1             
    
            # Trick for the tick at the bottom                                                      
            if i == 0:                                                                              
                y = self.height() - handle_height/2.0 # To have the tick in the middle              
                #y = height_diff # to shift it a bit                                                
    
            qp.drawText(contents.x() - point_size, y + point_size / 2, '{0:2}'.format(slider_step[len_slider - i]))
            qp.drawLine(contents.x() + point_size, y, contents.x() + contents.width(), y)  
    

    编辑: Windows 上似乎有大约 10 个单位的上边距被忽略了,这是 Qt 中的一个错误,恕我直言。一种解决方法是替换:

    handle_height = handle.height()
    

    handle_height = handle.height() + 10
    

    paintEvent 方法内。

    【讨论】:

    • 谢谢,但顶部标签的一部分仍然被剪掉,并且顺序更改为底部的 0.01。
    • 哎呀,没注意到。只需更改 for 循环上的迭代方向,例如将 -5 更改为 -8 即可避免底部刻度。
    • 标签仍然在顶部被剪掉。
    • 我更新了paintEvent方法,并附上了一个新的截图,希望对你有帮助。
    • 我直接从您的最新帖子中复制了paintEvent 方法,但最终值仍然被裁剪。可能与 Python 版本或 PySide2 有关。
    猜你喜欢
    • 2011-05-06
    • 2013-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-21
    相关资源
    最近更新 更多