有两种可能的方法,它们都使用QStyle
来获取滑块的几何形状和subPage / addPage矩形(滑块外部及其按钮内的“空格”,如果可见)。
子类QScrollBar并覆盖paintEvent()
在这里,我们重写滚动条的paintEvent()
,调用基类实现(绘制滚动条小部件)并在其上绘制文本 。
为了获得要绘制的矩形,我们创建一个QStyleOptionSlider
,它是QStyleOption子类,用于任何基于滑块的小部件(包括滚动条); QStyleOption包含QStyle绘制图形元素所需的所有信息,其子类使QStyle可以找到如何绘制复杂元素(如滚动条)或控制鼠标事件的行为。
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,sideWidth,0)
painter.drawText(sliderRect,Qt.AlignCenter,self.sliderText)
# get the "subPage" rectangle and draw the text
subPageRect = self.style().subControlRect(QStyle.CC_ScrollBar,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,QStyle.SC_ScrollBarAddPage,self)
painter.drawText(addPageRect,Qt.AlignRight|Qt.AlignVCenter,self.postText)
这种方法非常有效,在大多数简单情况下可能很好,但是只要文本宽比滑块手柄的尺寸大,都会有问题,因为Qt决定了滑块的范围根据其总体大小以及最小值和最大值之间的范围。
尽管您可以调整绘制文本的矩形的大小(就像我在示例中所做的那样),但这远非完美:只要滑块文本太宽,它可能就会绘制 上的“ pre”和“ post”文本,如果滑块靠近边缘,则使整个滚动条非常难看,因为文本可能会覆盖箭头按钮:
注意:“未调整”文本矩形的结果将与上图中的第一个滚动条相同,并且文本“剪切”到滑块几何上。
使用代理样式
QProxyStyle是 QStyle 的后代,它通过提供一种简单的方法来仅覆盖现有样式的方法,从而使子类化更加容易。
我们最感兴趣的功能是drawComplexControl()
,这是Qt用来绘制诸如旋转框和滚动条之类的复杂控件的功能。通过仅实现此功能,只要您将自定义样式应用于标准QScrollBar,其行为将与上述paintEvent()
方法完全相同。
(代理)样式可以真正帮助您改变几乎所有小部件的整体外观和行为。
为了能够充分利用其大多数功能,我实现了另一个QScrollBar子类,该子类允许更多的自定义,同时覆盖其他重要的QProxyStyle函数。
class TextScrollBarStyle(QProxyStyle):
def drawComplexControl(self,control,painter,widget):
# call the base implementation which will draw anything Qt will ask
super().drawComplexControl(control,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,widget)
painter.drawText(sliderRect,widget.sliderText)
subPageRect = self.subControlRect(control,widget)
subPageRect.setRight(sliderRect.left() - 1)
painter.save()
painter.setClipRect(subPageRect)
painter.drawText(subPageRect.adjusted(margin,0),widget.preText)
painter.restore()
addPageRect = self.subControlRect(control,widget)
addPageRect.setLeft(sliderRect.right() + 1)
painter.save()
painter.setClipRect(addPageRect)
painter.drawText(addPageRect.adjusted(0,-margin,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,widget)
def subControlRect(self,subControl,widget):
rect = super().subControlRect(control,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,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,widget)
rect.setRight(sliderRect.left())
elif subControl == QStyle.SC_ScrollBarAddPage:
# same as above
sliderRect = self.subControlRect(
control,widget)
rect.setLeft(sliderRect.right())
return rect
def hitTestComplexControl(self,pos,widget):
if control == QStyle.CC_ScrollBar:
# check click events against the resized slider
sliderRect = self.subControlRect(control,widget)
if pos in sliderRect:
return QStyle.SC_ScrollBarSlider
return super().hitTestComplexControl(control,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
使用基本的paintEvent覆盖,将样式应用于标准QScrollBar和使用具有完全实现的子类的完整的“启用样式”滚动条之间有很多区别。如您所见,当前样式(或为自定义代理样式选择的baseStyle
)在外观上可能总是不太友好:
两种(三种)方法之间的变化以及您最终决定使用的方法取决于您的需求;如果您需要向滚动条添加其他功能(或为文本内容或其外观添加更多控件)并且文本不是很宽,则可能需要使用子类化;另一方面,QProxyStyle方法对于控制其他方面或元素也可能很有用。
请记住,如果未在QApplication构造函数之前设置QStyle,则应用的样式可能无法完美地与之配合使用:与QFont和QPalette相反,QStyle不会传播到它所应用于的QWidget的子代(必须通知新的代理样式有关父样式的更改,并采取相应的措施。
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_())
本文链接:https://www.f2er.com/3166250.html