在qt里面有默认的滚动区域可以方便使用,QScrollArea,这个自带了水平和垂直滚动条。
但是这些默认样式一般不是我们想要的,例如很多时候我们只需滚动块,而不需要其他的控件。
例如效果:
?
现在介绍自定义滚动条。
1.关闭默认滚动条显示
在使用QScrollArea时,垂直和水平滚动条全部不展示:
area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2.派生QScrollBar
class MyScrollBar :public QScrollBar
{
Q_OBJECT
public:
MyScrollBar(QWidget* parent = nullptr);
~MyScrollBar();
QSize sizeHint()const override;
void setArea(QAbstractScrollArea * area);
protected:
void paintEvent(QPaintEvent *ev) override;
private slots:
void onSetRange(int min, int max);
private:
QAbstractScrollArea* m_area = nullptr;
MyScrollStyle* m_style = nullptr;
};
我们自定义的QScrollBar需要绑定原有的滚动条信号,这里我们只处理垂直滚动条:
void MyScrollBar::setArea(QAbstractScrollArea * area)
{
area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
auto bar = area->verticalScrollBar();
QObject::connect(
bar, SIGNAL(rangeChanged(int, int)), this, SLOT(onSetRange(int, int)));
QObject::connect(
bar, SIGNAL(valueChanged(int)), this, SLOT(setValue(int))
);
QObject::connect(
this, SIGNAL(valueChanged(int)), bar, SLOT(setValue(int))
);
setVisible(false);
m_area = area;
//设置成自定义的样式
m_style = new MyScrollStyle();
setStyle(m_style);
}
绑定了原有滚动条的滚动范围变化、值变化,更新到自定义滚动条;自定义的滚动条的值变化后,也需要更改原滚动条的值。注意:第一步设置的只是不展示滚动条,其实他还是在的。
在滚动范围变化后,将滚动条设置位置并展示:
void MyScrollBar::onSetRange(int min, int max)
{
if (max > 0)
{
setVisible(true);
setGeometry(m_area->rect().width() - width() - 2, 2, width(), height());
}
setRange(min, max);
}
3.自定义QStyle
翻看qt文档,可以看到滚动条有几个子控件:
QStyle::SC_ScrollBarAddLine QStyle::SC_ScrollBarSubLine QStyle::SC_ScrollBarAddPage QStyle::SC_ScrollBarSubPage QStyle::SC_ScrollBarFirst QStyle::SC_ScrollBarLast QStyle::SC_ScrollBarSlider QStyle::SC_ScrollBarGroove 这些含义可以在qt文档里面找到,例如SC_ScrollBarAddLine就是滚动条的“下一行”按钮:
?我们要做的是只剩下里面的灰色滚动控件。
所以我们继承QStyle后,override这些控件的大小就行:
class MyScrollStyle :public QCommonStyle
{
public:
MyScrollStyle();
QRect subControlRect(ComplexControl cc,const QStyleOptionComplex* opt,
SubControl sc, const QWidget* widget /*= nullptr*/) const override;
};
MyScrollStyle::MyScrollStyle()
{
}
QRect MyScrollStyle::subControlRect(ComplexControl cc,
const QStyleOptionComplex* opt, SubControl sc, const QWidget* widget /*= nullptr*/) const
{
if (cc == QStyle::CC_ScrollBar)
{
QRect ret;
if (const QStyleOptionSlider* scrollbar = qstyleoption_cast<const QStyleOptionSlider*>(opt)) {
const QRect scrollBarRect = scrollbar->rect;
int sbextent = 0;
int maxlen = ((scrollbar->orientation == Qt::Horizontal) ?
scrollBarRect.width() : scrollBarRect.height());
int sliderlen;
// calculate slider length
if (scrollbar->maximum != scrollbar->minimum) {
uint range = scrollbar->maximum - scrollbar->minimum;
int contentlen = 0;
sliderlen = 2 * maxlen / 3;
if (widget)
{
//这里给定的最大高度,按照自己的喜好处理
//我的Scrollbar设置的parent为:QAbstractScrollArea
QAbstractScrollArea* area = qobject_cast<QAbstractScrollArea*>(widget->parentWidget());
QWidget * w = area->viewport();
contentlen = w->height();
int nPageCount = contentlen / maxlen;
if (nPageCount > 0)
{
sliderlen = sliderlen / nPageCount;
}
}
int slidermin = proxy()->pixelMetric(PM_ScrollBarSliderMin, scrollbar, widget);
if (sliderlen < slidermin || range > INT_MAX / 2)
sliderlen = slidermin;
if (sliderlen > maxlen)
sliderlen = maxlen;
}
else {
sliderlen = maxlen;
}
int sliderstart = sbextent + sliderPositionFromValue(scrollbar->minimum,
scrollbar->maximum,
scrollbar->sliderPosition,
maxlen - sliderlen,
scrollbar->upsideDown);
switch (sc) {
case SC_ScrollBarSubLine: // top/left button
case SC_ScrollBarAddLine: // bottom/right button
case SC_ScrollBarSubPage: // between top/left button and slider
case SC_ScrollBarAddPage: // between bottom/right button and slider
break;
case SC_ScrollBarGroove:
if (scrollbar->orientation == Qt::Horizontal)
ret.setRect(sbextent, 0, scrollBarRect.width() - sbextent * 2,
scrollBarRect.height());
else
ret.setRect(0, sbextent, scrollBarRect.width(),
scrollBarRect.height() - sbextent * 2);
break;
case SC_ScrollBarSlider:
if (scrollbar->orientation == Qt::Horizontal)
ret.setRect(sliderstart, 0, sliderlen, scrollBarRect.height());
else
ret.setRect(0, sliderstart, scrollBarRect.width(), sliderlen);
break;
default:
break;
}
return ret;
}
}
return QCommonStyle::subControlRect(cc, opt, sc, widget);
}
只处理SC_ScrollBarGroove(可滚动区域)和SC_ScrollBarSlider(滚动滑块),其余的控件大小全返回空Rect。
4. 复写滚动条的paintevent
在滚动条的paintevent里面,我们取出控件进行绘制:
void MyScrollBar::paintEvent(QPaintEvent *ev)
{
QRect rc = this->rect();
QPainter painter(this);
painter.fillRect(rc, QColor("#FFFFFF")); //绘制滚动条背景
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect sliderRc = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider,this); //获取滑块控件
QPainterPath p;
p.addRoundedRect(sliderRc, 5, 5);
painter.fillPath(p, QColor(0,0,0,0x33));
}
|