是否可以在滚动条顶部添加文本? [英] Is it possible to add text on top of a scrollbar?

查看:60
本文介绍了是否可以在滚动条顶部添加文本?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在左端、右端和滑块上添加一些文字,如下图

我不明白如何在小部件顶部添加文本这里是 Qscrollbar 的最小例子(没有文本)

 from PyQt5.QtGui import *从 PyQt5.QtCore 导入 *从 PyQt5.QtWidgets 导入 *导入系统类查看器(QMainWindow):def __init__(self, parent=None):super(Viewer, self).__init__()self.parent = 父母self.centralWidget = QWidget()self.setCentralWidget(self.centralWidget)self.mainVBOX_param_scene = QVBoxLayout()self.paramPlotV = QVBoxLayout()self.horizo​​ntalSliders = QScrollBar(Qt.Horizo​​ntal)self.horizo​​ntalSliders.setMinimum(0)self.horizo​​ntalSliders.setMaximum(10)self.horizo​​ntalSliders.setPageStep(1)self.paramPlotV.addWidget(self.horizo​​ntalSliders)self.centralWidget.setLayout(self.paramPlotV)定义主():app = QApplication(sys.argv)app.setStyle('Windows')ex = 查看者(应用程序)ex.showMaximized()sys.exit(app.exec())如果 __name__ == '__main__':主要的()

解决方案

有两种可能的方法,都使用

注意:未调整"的结果文本矩形将与上图中的第一个滚动条相同,文本为剪切".到滑块几何体.

使用代理样式

两(三)种方法之间有何变化以及您最终决定使用什么取决于您的需求;如果您需要向滚动条添加其他功能(或对文本内容或其外观添加更多控制)并且文本不是很宽,您可能需要使用子类化;另一方面,QProxyStyle 方法也可能有助于控制其他方面或元素.
请记住,如果 QStyle 未在 QApplication 构造函数之前设置,则应用的样式可能无法完美使用:与 QFont 和 QPalette 不同,QStyle 不会传播到它应用到的 QWidget 的子级(意思是必须通知新的代理样式有关父样式更改并相应地进行操作).

class HLine(QFrame):def __init__(self):super().__init__()self.setFrameShape(self.HLine|self.Sunken)类示例(QWidget):def __init__(self):QWidget.__init__(self)布局 = QVBoxLayout(self)layout.addWidget(QLabel('Base subclass with paintEvent override, small text:'))shortPaintTextScrollBar = PaintTextScrollBar(Qt.Horizo​​ntal)layout.addWidget(shortPaintTextScrollBar)layout.addWidget(QLabel('同上,长文本(文本矩形调整为文本宽度):'))longPaintTextScrollBar = PaintTextScrollBar(Qt.Horizo​​ntal)longPaintTextScrollBar.sliderText = '我是一个很长的滑块'layout.addWidget(longPaintTextScrollBar)layout.addWidget(HLine())layout.addWidget(QLabel('Base QScrollBar with drawComplexControl override of proxystyle:'))shortBasicScrollBar = QScrollBar(Qt.Horizo​​ntal)layout.addWidget(shortBasicScrollBar)shortBasicScrollBar.sliderText = 'slider'shortBasicScrollBar.preText = '前文本'shortBasicScrollBar.postText = '发布文本'shortBasicScrollBar.setStyle(TextScrollBarStyle())layout.addWidget(QLabel('同上,长文本(基于滑块几何的文本矩形):'))longBasicScrollBar = QScrollBar(Qt.Horizo​​ntal)layout.addWidget(longBasicScrollBar)longBasicScrollBar.sliderText = '我是一个很长的滑块'longBasicScrollBar.preText = '前文本'longBasicScrollBar.postText = '发布文本'longBasicScrollBar.setStyle(TextScrollBarStyle())layout.addWidget(HLine())layout.addWidget(QLabel('具有完整代理样式实现的子类,所有可用样式:'))对于 QStyleFactory.keys() 中的 styleName:scrollBar = StyledTextScrollBar()layout.addWidget(scrollBar)scrollBar.setSliderText('带有{}样式的长滑块'.format(styleName))scrollBar.setStyle(TextScrollBarStyle(QStyleFactory.create(styleName)))scrollBar.valueChanged.connect(self.setScrollBarPreText)scrollBar.setPostText('发布文本')对于 self.findChildren(QScrollBar) 中的滚动条:scrollBar.setValue(7)def setScrollBarPreText(self, value):self.sender().setPreText(str(value))如果 __name__ == '__main__':app = QApplication(sys.argv)示例 = 示例()示例.show()sys.exit(app.exec_())

I would like to add some text to the left end side, the right end side and on the slider as in the figure below

I don't understand how I can add text on top of a widget here the minimal example of the Qscrollbar (without texts)

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


class Viewer(QMainWindow):
    def __init__(self, parent=None):
        super(Viewer, self).__init__()
        self.parent = parent 
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.mainVBOX_param_scene = QVBoxLayout()

        self.paramPlotV = QVBoxLayout()
        self.horizontalSliders = QScrollBar(Qt.Horizontal)
        self.horizontalSliders.setMinimum(0)
        self.horizontalSliders.setMaximum(10)
        self.horizontalSliders.setPageStep(1)

        self.paramPlotV.addWidget(self.horizontalSliders) 
        self.centralWidget.setLayout(self.paramPlotV)


def main():
    app = QApplication(sys.argv)
    app.setStyle('Windows')
    ex = Viewer(app)
    ex.showMaximized()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()

解决方案

There are two possible approaches, and both of them use QStyle to get the geometry of the slider and the subPage/addPage rectangles (the "spaces" outside the slider and within its buttons, if they are visible).

Subclass QScrollBar and override paintEvent()

Here we override the paintEvent() of the scroll bar, call the base class implementation (which paints the scroll bar widget) and draw the text over it.
To get the rectangle where we're going to draw, we create a QStyleOptionSlider, which is a QStyleOption sub class used for any slider based widget (including scroll bars); a QStyleOption contains all the information QStyle needs to draw graphical elements, and its subclasses allow QStyle to find out how to draw complex elements such as scroll bars or control the behavior against any mouse event.

class PaintTextScrollBar(QScrollBar):
    preText = 'pre text'
    postText = 'post text'
    sliderText = 'slider'
    def paintEvent(self, event):
        # call the base class paintEvent, which will draw the scrollbar
        super().paintEvent(event)

        # create a suitable styleoption and "init" it to this instance
        option = QStyleOptionSlider()
        self.initStyleOption(option)

        painter = QPainter(self)

        # get the slider rectangle
        sliderRect = self.style().subControlRect(QStyle.CC_ScrollBar, 
            option, QStyle.SC_ScrollBarSlider, self)
        # if the slider text is wider than the slider width, adjust its size;
        # note: it's always better to add some horizontal margin for text
        textWidth = self.fontMetrics().width(self.sliderText)
        if textWidth > sliderRect.width():
            sideWidth = (textWidth - sliderRect.width()) / 2
            sliderRect.adjust(-sideWidth, 0, sideWidth, 0)
        painter.drawText(sliderRect, Qt.AlignCenter, 
            self.sliderText)

        # get the "subPage" rectangle and draw the text
        subPageRect = self.style().subControlRect(QStyle.CC_ScrollBar, 
            option, QStyle.SC_ScrollBarSubPage, self)
        painter.drawText(subPageRect, Qt.AlignLeft|Qt.AlignVCenter, self.preText)

        # get the "addPage" rectangle and draw its text
        addPageRect = self.style().subControlRect(QStyle.CC_ScrollBar, 
            option, QStyle.SC_ScrollBarAddPage, self)
        painter.drawText(addPageRect, Qt.AlignRight|Qt.AlignVCenter, self.postText)

This approach is very effective and may be fine for most simple cases, but there will be problems whenever the text is wider than the size of the slider handle, since Qt decides the extent of the slider based on its overall size and the range between its minimum and maximum values.
While you can adjust the size of the rectangle you're drawing text (as I've done in the example), it will be far from perfect: whenever the slider text is too wide it might draw over the "pre" and "post" text, and make the whole scrollbar very ugly if the slider is near the edges, since the text might cover the arrow buttons:

Note: the result of a "non adjusted" text rectangle would be the same as the first scroll bar in the image above, with the text "clipped" to the slider geometry.

Use a proxy style

QProxyStyle is a QStyle descendant that makes subclassing easier by providing an easy way to override only methods of an existing style. The function we're most interested in is drawComplexControl(), which is what Qt uses to draw complex controls like spin boxes and scroll bars. By implementing this function only, the behavior will be exactly the same as the paintEvent() method explained above, as long as you apply the custom style to a standard QScrollBar.

What a (proxy) style could really help with is being able to change the overall appearance and behavior of almost any widget.
To be able to take the most of its features, I've implemented another QScrollBar subclass, allowing much more customization, while overriding other important QProxyStyle functions.

class TextScrollBarStyle(QProxyStyle):
    def drawComplexControl(self, control, option, painter, widget):
        # call the base implementation which will draw anything Qt will ask
        super().drawComplexControl(control, option, painter, widget)
        # check if control type and orientation match
        if control == QStyle.CC_ScrollBar and option.orientation == Qt.Horizontal:
            # the option is already provided by the widget's internal paintEvent;
            # from this point on, it's almost the same as explained above, but 
            # setting the pen might be required for some styles
            painter.setPen(widget.palette().color(QPalette.WindowText))
            margin = self.frameMargin(widget) + 1

            sliderRect = self.subControlRect(control, option, 
                QStyle.SC_ScrollBarSlider, widget)
            painter.drawText(sliderRect, Qt.AlignCenter, widget.sliderText)

            subPageRect = self.subControlRect(control, option, 
                QStyle.SC_ScrollBarSubPage, widget)
            subPageRect.setRight(sliderRect.left() - 1)
            painter.save()
            painter.setClipRect(subPageRect)
            painter.drawText(subPageRect.adjusted(margin, 0, 0, 0), 
                Qt.AlignLeft|Qt.AlignVCenter, widget.preText)
            painter.restore()

            addPageRect = self.subControlRect(control, option, 
                QStyle.SC_ScrollBarAddPage, widget)
            addPageRect.setLeft(sliderRect.right() + 1)
            painter.save()
            painter.setClipRect(addPageRect)
            painter.drawText(addPageRect.adjusted(0, 0, -margin, 0), 
                Qt.AlignRight|Qt.AlignVCenter, widget.postText)
            painter.restore()

    def frameMargin(self, widget):
        # a helper function to get the default frame margin which is usually added
        # to widgets and sub widgets that might look like a frame, which usually
        # includes the slider of a scrollbar
        option = QStyleOptionFrame()
        option.initFrom(widget)
        return self.pixelMetric(QStyle.PM_DefaultFrameWidth, option, widget)

    def subControlRect(self, control, option, subControl, widget):
        rect = super().subControlRect(control, option, subControl, widget)
        if (control == QStyle.CC_ScrollBar 
            and isinstance(widget, StyledTextScrollBar)
            and option.orientation == Qt.Horizontal):
                if subControl == QStyle.SC_ScrollBarSlider:
                    # get the *default* groove rectangle (the space in which the
                    # slider can move)
                    grooveRect = super().subControlRect(control, option, 
                        QStyle.SC_ScrollBarGroove, widget)
                    # ensure that the slider is wide enough for its text
                    width = max(rect.width(), 
                        widget.sliderWidth + self.frameMargin(widget))
                    # compute the position of the slider according to the
                    # scrollbar value and available space (the "groove")
                    pos = self.sliderPositionFromValue(widget.minimum(), 
                        widget.maximum(), widget.sliderPosition(), 
                        grooveRect.width() - width)
                    # return the new rectangle
                    return QRect(grooveRect.x() + pos, 
                        (grooveRect.height() - rect.height()) / 2, 
                        width, rect.height())
                elif subControl == QStyle.SC_ScrollBarSubPage:
                    # adjust the rectangle based on the slider
                    sliderRect = self.subControlRect(
                        control, option, QStyle.SC_ScrollBarSlider, widget)
                    rect.setRight(sliderRect.left())
                elif subControl == QStyle.SC_ScrollBarAddPage:
                    # same as above
                    sliderRect = self.subControlRect(
                        control, option, QStyle.SC_ScrollBarSlider, widget)
                    rect.setLeft(sliderRect.right())
        return rect

    def hitTestComplexControl(self, control, option, pos, widget):
        if control == QStyle.CC_ScrollBar:
            # check click events against the resized slider
            sliderRect = self.subControlRect(control, option, 
                QStyle.SC_ScrollBarSlider, widget)
            if pos in sliderRect:
                return QStyle.SC_ScrollBarSlider
        return super().hitTestComplexControl(control, option, pos, widget)


class StyledTextScrollBar(QScrollBar):
    def __init__(self, sliderText='', preText='', postText=''):
        super().__init__(Qt.Horizontal)
        self.setStyle(TextScrollBarStyle())
        self.preText = preText
        self.postText = postText
        self.sliderText = sliderText
        self.sliderTextMargin = 2
        self.sliderWidth = self.fontMetrics().width(sliderText) + self.sliderTextMargin + 2

    def setPreText(self, text):
        self.preText = text
        self.update()

    def setPostText(self, text):
        self.postText = text
        self.update()

    def setSliderText(self, text):
        self.sliderText = text
        self.sliderWidth = self.fontMetrics().width(text) + self.sliderTextMargin + 2

    def setSliderTextMargin(self, margin):
        self.sliderTextMargin = margin
        self.sliderWidth = self.fontMetrics().width(self.sliderText) + margin + 2

    def sizeHint(self):
        # give the scrollbar enough height for the font
        hint = super().sizeHint()
        if hint.height() < self.fontMetrics().height() + 4:
            hint.setHeight(self.fontMetrics().height() + 4)
        return hint

There's a lot of difference between using the basic paintEvent override, applying the style to a standard QScrollBar and using a full "style-enabled" scroll bar with a fully implemented subclass; as you can see it's always possible that the current style (or the baseStyle chosen for the custom proxy style) might not be very friendly in its appearance:

What changes between the two (three) approaches and what you will finally decide to use depends on your needs; if you need to add other features to the scroll bar (or add more control to text contents or their apparance) and the text is not very wide, you might want to go with subclassing; on the other hand, the QProxyStyle approach might be useful to control other aspects or elements too.
Remember that if the QStyle is not set before the QApplication constructor, it's possible that the applied style won't be perfect to work with: as opposed with QFont and QPalette, QStyle is not propagated to the children of the QWidget it's applied to (meaning that the new proxy style has to be notified about the parent style change and behave accordingly).

class HLine(QFrame):
    def __init__(self):
        super().__init__()
        self.setFrameShape(self.HLine|self.Sunken)


class Example(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        layout = QVBoxLayout(self)

        layout.addWidget(QLabel('Base subclass with paintEvent override, small text:'))
        shortPaintTextScrollBar = PaintTextScrollBar(Qt.Horizontal)
        layout.addWidget(shortPaintTextScrollBar)

        layout.addWidget(QLabel('Same as above, long text (text rect adjusted to text width):'))
        longPaintTextScrollBar = PaintTextScrollBar(Qt.Horizontal)
        longPaintTextScrollBar.sliderText = 'I am a very long slider'
        layout.addWidget(longPaintTextScrollBar)

        layout.addWidget(HLine())

        layout.addWidget(QLabel('Base QScrollBar with drawComplexControl override of proxystyle:'))
        shortBasicScrollBar = QScrollBar(Qt.Horizontal)
        layout.addWidget(shortBasicScrollBar)
        shortBasicScrollBar.sliderText = 'slider'
        shortBasicScrollBar.preText = 'pre text'
        shortBasicScrollBar.postText = 'post text'
        shortBasicScrollBar.setStyle(TextScrollBarStyle())

        layout.addWidget(QLabel('Same as above, long text (text rectangle based on slider geometry):'))
        longBasicScrollBar = QScrollBar(Qt.Horizontal)
        layout.addWidget(longBasicScrollBar)
        longBasicScrollBar.sliderText = 'I am a very long slider'
        longBasicScrollBar.preText = 'pre text'
        longBasicScrollBar.postText = 'post text'
        longBasicScrollBar.setStyle(TextScrollBarStyle())

        layout.addWidget(HLine())

        layout.addWidget(QLabel('Subclasses with full proxystyle implementation, all available styles:'))
        for styleName in QStyleFactory.keys():
            scrollBar = StyledTextScrollBar()
            layout.addWidget(scrollBar)
            scrollBar.setSliderText('Long slider with {} style'.format(styleName))
            scrollBar.setStyle(TextScrollBarStyle(QStyleFactory.create(styleName)))
            scrollBar.valueChanged.connect(self.setScrollBarPreText)
            scrollBar.setPostText('Post text')

        for scrollBar in self.findChildren(QScrollBar):
            scrollBar.setValue(7)

    def setScrollBarPreText(self, value):
        self.sender().setPreText(str(value))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    example = Example()
    example.show()
    sys.exit(app.exec_())

这篇关于是否可以在滚动条顶部添加文本?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆