概述
#ifndef SLIDERSTYLE_H
#define SLIDERSTYLE_H
#include <QProxyStyle>
#include <QStyleOptionComplex>
class SliderStyle : public QProxyStyle
{
public:
SliderStyle();
~SliderStyle();
void drawComplexControl(ComplexControl which,const QStyleOptionComplex *option,QPainter *painter,const QWidget *widget = nullptr) const override;
int styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const; // 让handle滑块直接移到鼠标点击处
int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const; // 修改handle滑块大小
void polish(QWidget *widget); // 设置滑动条响应鼠标移入移出事件
void unpolish(QWidget *widget); // 取消设置
void drawLine(Qt::Orientation orentation, QRect rect, QRect handleRect, QPainter *painter) const; // 画滑块
void drawHandle(QRect handleRect, QPainter *painter, bool handleActivate) const; // 画滑块
void drawTicks(const QStyleOptionSlider *slider, QRect handleRect, int len, QPainter *painter) const; // 画刻度
void getCoverColor(QLinearGradient &lg) const;
private:
const int lineWidth = 6; // 滑动条宽度
const qreal ratio = 1.5; // 滑块与刻度的比例
};
#endif // SLIDERSTYLE_H
#include "sliderstyle.h"
#include <QDebug>
#include <QPainter>
#include <QtMath>
SliderStyle::SliderStyle()
{
}
SliderStyle::~SliderStyle()
{
}
int SliderStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const
{
if(hint == QStyle::SH_Slider_AbsoluteSetButtons) {
return (Qt::LeftButton);
}
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
int SliderStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
{
return QProxyStyle::pixelMetric(metric, option, widget);
}
void SliderStyle::polish(QWidget *widget)
{
widget->setAttribute(Qt::WA_Hover, true);
}
void SliderStyle::unpolish(QWidget *widget)
{
widget->setAttribute(Qt::WA_Hover, false);
}
void SliderStyle::drawComplexControl(ComplexControl which, const QStyleOptionComplex *option,
QPainter *painter, const QWidget *widget) const
{
if (which == CC_Slider) {
if (const QStyleOptionSlider *styleOptionSlider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
QRect sliderRect = subControlRect(CC_Slider, styleOptionSlider, SC_SliderGroove, widget);
QRect handleRect = subControlRect(CC_Slider, styleOptionSlider, SC_SliderHandle, widget);
drawLine(styleOptionSlider->orientation, sliderRect, handleRect, painter);
drawHandle(handleRect, painter, styleOptionSlider->activeSubControls & SC_SliderHandle);
if (styleOptionSlider->tickPosition != QSlider::NoTicks) {
int len = proxy()->pixelMetric(PM_SliderLength, styleOptionSlider, widget);
drawTicks(styleOptionSlider, handleRect, len, painter);
}
}
} else {
return QProxyStyle::drawComplexControl(which, option, painter,widget);
}
}
void SliderStyle::drawLine(Qt::Orientation orentation, QRect rect, QRect handleRect, QPainter *painter) const
{
QRect baseRect;
QRect coverRect;
QLinearGradient lg;
QPoint handlePos = handleRect.center();
painter->setPen(Qt::NoPen);
painter->setRenderHint(QPainter::Antialiasing); // 反锯齿;
if (orentation == Qt::Horizontal) {
lg.setStart(0, 0);
lg.setFinalStop(1, 0);
// 由于滑块比刻度大,因此两端会有冗余,绘制时减掉冗余部分
int l = (handleRect.width() - qRound(handleRect.width()/ratio)) / 2 + 1;
baseRect = QRect(handlePos.x(),
rect.center().y() - lineWidth / 2,
rect.right() - handlePos.x() - l,
lineWidth);
coverRect = QRect(rect.left() + l,
rect.center().y() - lineWidth / 2,
handlePos.x() - rect.left(),
lineWidth);
} else {
lg.setStart(0, 1);
lg.setFinalStop(0, 0);
int l = (handleRect.height() - qRound(handleRect.height()/ratio)) / 2 + 1;
baseRect = QRect(rect.center().x() - lineWidth / 2,
rect.top() + l, //垂直方向top坐标为0
lineWidth,
handlePos.y());
coverRect = QRect(rect.center().x() - lineWidth / 2,
handlePos.y(),
lineWidth,
rect.bottom() - handlePos.y() - l);
}
painter->setBrush(QColor("#CFCFCF"));
painter->drawRoundedRect(baseRect, 4, 4);
getCoverColor(lg);
painter->setBrush(lg);
painter->drawRoundedRect(coverRect, 4, 4);
}
void SliderStyle::drawHandle(QRect handleRect, QPainter *painter, bool handleActivate) const
{
painter->setBrush(QColor("#4876FF"));
painter->drawRoundedRect(handleRect, 4, 4);
if (handleActivate) { // 按下时,滑块叠加灰色
QColor slightlyOpaqueBlack(0, 0, 0, 63);
painter->setBrush(slightlyOpaqueBlack);
painter->drawRoundedRect(handleRect, 4, 4);
}
}
void SliderStyle::drawTicks(const QStyleOptionSlider *slider, QRect handleRect, int len, QPainter *painter) const
{
int v = slider->minimum;
int interval = slider->tickInterval;
int w = qRound(handleRect.width() / ratio);
int h = qRound(handleRect.height() / ratio);
if (interval <= 0) {
interval = 1;
}
bool horizontal = slider->orientation == Qt::Horizontal;
while (v <= slider->maximum + 1) {
if (v == slider->maximum + 1 && interval == 1) {
break;
}
const int v_ = qMin(v, slider->maximum);
int pos = sliderPositionFromValue(slider->minimum, slider->maximum,
v_,
(horizontal ? slider->rect.width() : slider->rect.height()) - len,
slider->upsideDown) + len / 2;
if (horizontal) {
int x = pos - w/2;
int y = handleRect.center().y() - h/2;
QRect rect(x, y, w, h);
if (!handleRect.contains(rect)) {
if (x > handleRect.right()) {
painter->setBrush(QColor("#CFCFCF"));
} else {
painter->setBrush(QColor("#4876FF"));
}
painter->drawRoundedRect(rect, 4, 4);
}
} else {
int x = handleRect.center().x() - w/2;
int y = pos - h/2;
QRect rect(x, y, w, h);
if (!handleRect.contains(rect)) {
if (y < handleRect.top()) {
painter->setBrush(QColor("#CFCFCF"));
} else {
painter->setBrush(QColor("#4876FF"));
}
painter->drawRoundedRect(rect, 4, 4);
}
}
// in the case where maximum is max int
int nextInterval = v + interval;
if (nextInterval < v) {
break;
}
v = nextInterval;
}
}
void SliderStyle::getCoverColor(QLinearGradient &lg) const
{
lg.setCoordinateMode(QGradient::StretchToDeviceMode); // 设备边界模式
QVector<QColor> colorVector;
for ( int i = 0; i < 360; i+= 360/6 ) {
colorVector.push_front(QColor::fromHsv(i, 255, 255));
}
colorVector.push_front(Qt::red);
for(int i = 0; i < colorVector.size(); i++) {
lg.setColorAt(double(i) / (colorVector.size() - 1), colorVector[i]); // 设置梯度颜色, 参数一处于0~1之间
}
}
通过自定义style,可以实现自定义滑动条、滑块、刻度(节点)的显示效果,可以使滑块移动至鼠标左键点击处。效果图如下:
如果只是想让滑块跟随鼠标左键点击移动,还可以通过事件进行处理。qt的基础控件支持的是鼠标中键移动滑块,我们只需要转换一下事件类型即可:
void USlider::mousePressEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) {
QMouseEvent a(e->type(),e->localPos(),Qt::MiddleButton,
QFlags<Qt::MouseButton>(Qt::MidButton|Qt::MiddleButton),e->modifiers());
QSlider::mousePressEvent(&a); //左键事件转换为中键事件,实现滑动条点击移动
return;
} else {
QSlider::mousePressEvent(e);
return;
}
}
如果我们还想要在刻度下面显示文字,应该如何处理?
代码如下:
MySlider::MySlider(QStringList list, QWidget *parent) :
QSlider(parent)
{
// 设置高度,否则字体无位置可显示
this->setMinimumHeight(50);
this->setMaximumHeight(100);
this->textList = list;
}
void MySlider::paintEvent(QPaintEvent *e)
{
QSlider::paintEvent(e);
QPainter painter(this);
auto rect = this->rect();
int numTicks = textList.size();
QFont font = this->font();
painter.setFont(font);
int total = 0;
QFontMetrics fontMetrics = QFontMetrics(painter.font());
for (int i = 0; i < numTicks; i++) {
QRect fontRect = fontMetrics.boundingRect(textList.at(i));
total += fontRect.width();
}
if (this->orientation() == Qt::Horizontal) {
float tickY = rect.height() / 2.0 + 25;
int len = this->style()->proxy()->pixelMetric(QStyle::PM_SliderLength);
for (int i = 0; i < numTicks; i++) {
int pos = this->style()->sliderPositionFromValue(this->minimum(), this->maximum(),
i,
rect.width() - len,
false) + len / 2;
float tickX = pos;
float fontWidth = fontMetrics.boundingRect(textList.at(i)).width();
// 刻度在文字中间位置
tickX = tickX - fontWidth / 2;
if (i == numTicks - 1) {
if (tickX + fontWidth / 2 > this->width()) {
tickX = this->width() - fontWidth / 2;
}
} else if (i == 0){
if (tickX < 0) {
tickX = 0;
}
}
painter.drawText(QPointF(tickX, tickY), textList.at(i));
}
}
painter.end();
}
效果如下:
再进一步,如果我们想要在滑动条上方显示tooltip悬浮显示效果,显示当前刻度的值,应该怎么做呢,因为在实际运用当中,可能无法给每个刻度都绘制文字效果,比如在以上的例子中,如果文字内容不是000,111....而是000000,1111111.....这时由于刻度内容较长,如果每个刻度都绘制文字,会导致文字内容叫错在一起,这时一般只在第一个刻度和最后一个刻度下绘制文字,其余地方通过tooltip显示文字内容。
这里展示tooltip的实现:
bool MySlider::eventFilter(QObject *w, QEvent *e)
{
if (e->type() == QEvent::ToolTip) {
QHelpEvent *helpEvent = static_cast<QHelpEvent*>(e);
QPoint pos = helpEvent->pos();
if (nodePointList.isEmpty()) {
initNodePoint();
}
for (int i = 0; i < nodePointList.size(); ++i) {
QPoint point = nodePointList.at(i);
int radius = 7; // 节点的宽度,省去计算过程(20/1.5)/2
int x = point.x() - 10; // 坐标的计算可以结合字体的宽度以及具体使用场景,这里只做demo使用
int y = this->height()/2 - 60;
if (this->value() == i + 1) { //当前选中时,宽度即为滑块的宽度20
radius = 10;
}
if (isContains(pos, point, radius)) {
QToolTip::showText(mapToGlobal(QPoint(x, y)), textList.at(i), this);
break;
} else {
if (i == nodePointList.size() - 1) {
QToolTip::hideText();
}
}
}
}
return QSlider::eventFilter(w, e);
}
void MySlider::initNodePoint()
{
nodePointList.clear();
int len = this->style()->proxy()->pixelMetric(QStyle::PM_SliderLength);
for (int i = 0; i <= this->maximum(); ++i) {
int posX = this->style()->sliderPositionFromValue(this->minimum(), this->maximum(),
i,
this->rect().width() - len,
false) + len / 2;
QPoint nodePoint(posX, this->height()/2);
nodePointList.append(nodePoint); // 所有刻度的中心位置
}
}
// 判断鼠标是否在节点上
bool MySlider::isContains(QPoint p1, QPoint p2, int radius)
{
return (p2.x() - radius <= p1.x()
&& p2.x() + radius >= p1.x()
&& p2.y() - radius <= p1.y()
&& p2.y() + radius >= p1.y());
}
// 移动鼠标修改滑块位置时,也显示tooltip
void MySlider::mouseMoveEvent(QMouseEvent *e)
{
QSlider::mouseMoveEvent(e);
if (e->buttons() & Qt::LeftButton) {
if (nodePointList.isEmpty()) {
initNodePoint();
}
int i = this->value();
int x = nodePointList.at(i).x() - 10; // 坐标的计算可以结合字体的宽度以及具体使用场景,这里只做demo使用
int y = this->height()/2 - 60;
QToolTip::showText(mapToGlobal(QPoint(x, y)), textList.at(i), this);
}
return;
}
// 左键松开后如果鼠标在节点上则继续显示tooltip
void MySlider::mouseReleaseEvent(QMouseEvent *e)
{
QSlider::mouseReleaseEvent(e);
if (e->button() == Qt::LeftButton) {
QPoint mousePos = e->pos();
for (int i = 0; i < nodePointList.size(); ++i) {
QPoint point = nodePointList.at(i);
int radius = 7; // 节点的宽度,省去计算过程(20/1.5)/2
int x = point.x() - 10; // 坐标的计算可以结合字体的宽度以及具体使用场景,这里只做demo使用
int y = this->height()/2 - 60;
if (this->value() == i + 1) { //当前选中时,宽度即为滑块的宽度20
radius = 10;
}
if (isContains(mousePos, point, radius)) {
QToolTip::showText(mapToGlobal(QPoint(x, y)), textList.at(i), this);
break;
}
}
}
return;
}
实际效果如下:
这里只是介绍实现的原理,如果要用到实际的工程项目中,颜色、刻度样式,代码封装、坐标计算等都需要根据实际场景进行优化调整。如果觉得tooltip样式不够好看,也可以自定义或者使用QLabel进行文字提示显示,显示的方案是一样的。
好了,以上的自定义qslider基本包括了所有日常项目中有可能遇到的需求:滑块跟随鼠标点击、自定义滑块、刻度、滑动条样式、文字绘制、tooltip显示等。
后面又想到一个可能的需求,如果要使鼠标移动到滑块上时,使滑块颜色加深,类似与点击效果,应该怎么做呢?
首先安装事件过滤器:
void SliderStyle::polish(QWidget *widget)
{
widget->setAttribute(Qt::WA_Hover, true);
widget->installEventFilter(this);
}
bool SliderStyle::eventFilter(QObject *obj, QEvent *e)
{
if (qobject_cast<QSlider *>(obj)) {
QWidget *w = qobject_cast<QWidget *>(obj);
w->update();
}
return false;
}
安装事件过滤器的目的是使鼠标进入时触发update进一步触发drawComplexControl。
(不应该自动触发 吗?也许要设置某些参数,暂时使用该方案。)
然后修改drawHandle函数,增加一个判断是否为hover的参数:
实现hover叠加:
void SliderStyle::drawHandle(QRect handleRect, QPainter *painter, bool handleActivate, bool hover) const
{
painter->setBrush(QColor("#4876FF"));
painter->drawRoundedRect(handleRect, 4, 4);
if (handleActivate) { // 按下时,滑块叠加灰色
QColor slightlyOpaqueBlack(0, 0, 0, 63);
painter->setBrush(slightlyOpaqueBlack);
painter->drawRoundedRect(handleRect, 4, 4);
} else if (hover) {// hover时,滑块叠加灰色
QColor slightlyOpaqueBlack(0, 0, 0, 23);
painter->setBrush(slightlyOpaqueBlack);
painter->drawRoundedRect(handleRect, 4, 4);
}
}
常态:
hover态:
选中态:
最后
以上就是热情哈密瓜为你收集整理的绘制自定义QSlider的全部内容,希望文章能够帮你解决绘制自定义QSlider所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复