关于定制Qt中QSlidre外观,网上多是使用setStyleSheet函数设置sytlesheet进行修改。对于固定外观的定制倒是十分的方便,但对于需要在程序运行时改变外观,并且外观图片需要在内存中进行处理时,使用setStyleSheet函数就不适合了。
本文介绍使用QStyle类的子类进行外观的定制。要使用QStyle进行组件外观的改变,首先需要从QCommonStyle、QWindowsStyle、QMotifStyle、QPlastiqueStyle等类进行继承,生成新类,而这些类都是继承于QStyle类,并重载draw*函数。在本例中,MySliderStyle类继承于QPlastiqueStyle类。
MySliderStyle.h内容如下:
#ifndef MYSLIDERSTYLE_H
#define MYSLIDERSTYLE_H
#include <QPlastiqueStyle>
class QPainter;
class QWidget;
class MySliderStyle : public QPlastiqueStyle
{
public:
MySliderStyle(){}
void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *w) const; // 画出Slider中groove、handle和填充条外观
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); // 取消设置
};
#endif // MYSLIDERSTYLE_H
MySliderStyle.cpp内容如下:
#include "mysliderstyle.h"
#include "myslider.h"
#include <QPainter>
#include <QWidget>
#include <QStyleOptionComplex>
#include <QDebug>
int MySliderStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const{
if(hint == QStyle::SH_Slider_AbsoluteSetButtons)
return (Qt::LeftButton);
return QPlastiqueStyle::styleHint(hint, option, widget, returnData);
}
int MySliderStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const{
// 对Slidr上的handle而言返回的是 横向 宽度;纵向 高度;
if(const MySlider *ms = qobject_cast<const MySlider *>(widget)){
if(ms->orientation() == Qt::Vertical && ms->getHandle(0) != NULL){
return ms->getHandle(0)->height();
}else if(ms->orientation() == Qt::Horizontal && ms->getHandle(0) != NULL){
return ms->getHandle(0)->width();
}else if(ms->getHandle(0) == NULL){
return 1;
}
}
return QPlastiqueStyle::pixelMetric(metric, option, widget);
}
void MySliderStyle::polish(QWidget *widget){
widget->setAttribute(Qt::WA_Hover, true);
}
void MySliderStyle::unpolish(QWidget *widget){
widget->setAttribute(Qt::WA_Hover, false);
}
void MySliderStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *w) const{
if(cc == CC_Slider){
QRect groove = subControlRect(CC_Slider, opt, SC_SliderGroove, w);
QRect handle = subControlRect(CC_Slider, opt, SC_SliderHandle, w);
// qDebug() << handle.topLeft() << handle.size();
p->save();
if(const MySlider *ms = qobject_cast<const MySlider *>(w)){
if(const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)){
if ((opt->subControls & SC_SliderGroove) && groove.isValid() && handle.isValid()) { // 背景
QPixmap *t_groove = ms->getGroove();
p->drawPixmap(QRect(groove.x(), groove.y(),
groove.width(), groove.height()), *(t_groove));
QPixmap *fill = ms->getFill();
if(ms->orientation() == Qt::Vertical){
p->drawPixmap(ms->getFillPoint(), handle.y(),fill->copy(0, handle.y(), fill->width(), groove.height() - handle.y()));
}else if(ms->orientation() == Qt::Horizontal){
p->drawPixmap(0,ms->getFillPoint(), fill->copy(0, 0, handle.x() == 0 ? 1 : handle.x(), fill->height()));
}
}
if(opt->subControls & SC_SliderHandle){
QPixmap *p0 = NULL;
QPixmap *p1 = NULL;
QPixmap *p2 = NULL;
QPixmap * p3 = NULL;
bool isNew = false;
if(ms->getHandle(0) == NULL){
QPixmap *fill = ms->getFill();
if(ms->orientation() == Qt::Vertical)
p0 = new QPixmap(fill->copy(0, handle.y(), fill->width(), 1));
else
p0 = new QPixmap(fill->copy(handle.x(), 0, 1, fill->height()));
p1 = p0;
p2 = p0;
p3 = p0;
isNew = true;
}else{
p0 = ms->getHandle(0);
p1 = ms->getHandle(1);
p2 = ms->getHandle(2);
p3 = ms->getHandle(3);
}
if((slider->state & QStyle::State_MouseOver) && (slider->state & State_Sunken)){ // handle处理 点击
if(ms->orientation() == Qt::Vertical){
p->drawPixmap(ms->getHandlePoint(),handle.y(), *p2);
}else if(ms->orientation() == Qt::Horizontal){
p->drawPixmap(handle.x(),ms->getHandlePoint(), *p2);
}
}else if(slider->state & QStyle::State_MouseOver){ // handle处理 鼠标进入
if(ms->orientation() == Qt::Vertical)
p->drawPixmap(ms->getHandlePoint(),handle.y(), *p1);
else
p->drawPixmap(handle.x(),ms->getHandlePoint(), *p1);
}else if(!(slider->state & QStyle::State_Enabled)){ // handle处理 失效
if(ms->orientation() == Qt::Vertical)
p->drawPixmap(ms->getHandlePoint(),handle.y(), *p3);
else
p->drawPixmap(handle.x(),ms->getHandlePoint(), *p3);
}else if(slider->state & QStyle::State_Enabled){ // handle处理 有效
if(ms->orientation() == Qt::Vertical)
p->drawPixmap(ms->getHandlePoint(),handle.y(), *p0);
else
p->drawPixmap(handle.x(),ms->getHandlePoint(), *p0);
}
if(isNew){
delete p0;
p0 = NULL;
p1 = NULL;
p2 = NULL;
p3 = NULL;
}
}
}
}
p->restore();
}
// QPlastiqueStyle::drawComplexControl(cc, opt, p, w);
}
drawComplexControl函数用于实现绘制QSlider中groove、handle滑块部分外观。首先判断当前组件是否为滑动条(CC_Slider为QT中定义的复杂组件滑动条的标识),然后获取groove在父组件中的区域信息和handle滑块在滑动条的区域信息,subControlRect函数为QStyle类的成员函数,在Qt文档中说返回的是屏幕坐标(screen coordinates),但据我观察返回的是相对父组件的坐标(groove返回是QSlider在父组件中的坐标位置,handle滑块返回是相对QSlider的坐标位置)。下面进行外观绘制,方法是使用QPainter类的drawPixmap函数里绘制。
groove部分绘制。由于在QStyle中只定义了SC_SliderGroove和SC_SliderHandle子控件,而没有sub-page和add-page相关的子控件定义。所以填充条将在这个地方绘制,依据handle滑块的位置绘制进度条。
handle滑块部分绘制。滑块的绘制分为四个部分,滑块为有效状态、鼠标进入、点击和滑块为无效状态时分别使用不同图片进行绘制。在进行判断时,从条件复杂的开始,不然简单条件始终满足,将不会绘制复杂条件。如将鼠标点击部分放到鼠标移入的后面,则点击部分语句将不会被执行,因为鼠标移入部分的判断条件始终满足。在绘制部分的4个QPixmap指针对象为滑块各种状态下的图片。p0为有效状态(滑块可用,但没有鼠标移入和点击),p1为鼠标移入,p2为鼠标点击,p3为无效状态(滑块不可用)。在这个地方进行处理,当滑块为不为固定形状时和为固定形状时两种情况。当不为固定形状时假定从填充条相应位置去取一个像素图形。
在程序用到的MySlider类为自定义的QSlider的子类。因为所有的图片都是在处理完后以QPixmap方式存储在内存,所以创建子类保存图片信息,并且按图片大小进行了位置调整,将填充条和滑块进行剧中处理。
MySlider.h内容如下:
#ifndef MYSLIDER_H
#define MYSLIDER_H
#include <QSlider>
#include <QTemporaryFile>
#include "mysliderstyle.h"
#include "TTSkin/sliderinfo.h"
class QMouseEvent;
class QPaintEvent;
class MySlider : public QSlider
{
Q_OBJECT
public:
explicit MySlider(QWidget *parent = 0);
~MySlider();
int initSlider(SliderInfo &si); // 设置滑动条信息,包括坐标位置,大小,需要绘制的各种图片
QPixmap *getGroove() const; // 返回背景图片指针
QPixmap *getFill() const; // 返回填充条图片指针
QPixmap *getHandle(int i) const; // 返回滑块图片指针,0 有效, 1 鼠标移入, 2 点击, 3 无效
int getFillPoint() const; // 返回填充条绘制的调整坐标,横向时为 y轴坐标,垂直时为 x轴坐标
int getHandlePoint() const; // 返回滑块绘制的调整坐标,横向时为 y轴坐标,垂直时为 x轴坐标
SliderInfo *getSliderInfo(); // 返回当前组件的所有信息,此信息为从配置文件中读取并设置
protected:
void mouseMoveEvent(QMouseEvent *ev);
private:
QPixmap *groove; // 背景图片
QPixmap *fill; // 填充条图片,大小与背景图片一致,横向时长与背景图片相同,垂直时高与背景图片相同
QPixmap *handle_p0; // 滑块有效时图片
QPixmap *handle_p1; // 鼠标移入时图片
QPixmap *handle_p2; // 鼠标点击时图片
QPixmap *handle_p3; // 滑块无效时图片
int fill_p; // 填充条绘制时调整的坐标,横向时为 y轴坐标,垂直时为 x轴坐标
int handle_p; // 滑块绘制时调整的坐标,横向时为 y轴坐标,垂直时为 x轴坐标
static MySliderStyle *mss; // 样式对象,使用静态对象,是为了由多个滑动条对象复用
SliderInfo *si; // 当前组件的所有信息,此信息为从配置文件中读取并设置
signals:
public slots:
};
#endif // MYSLIDER_H
MySlider.cpp内容如下:
#include "myslider.h"
#include "../TTSkin/ttwin.h"
#include <QDebug>
#include <QMouseEvent>
#include <QPainter>
#include <QStyleOptionSlider>
#include <QPaintEvent>
#include <QDir>
MySlider::MySlider(QWidget *parent) :
QSlider(parent)
{
this->setRange(0, 100); // 设置滑动条取值范围
this->setMouseTracking(true); // 设置鼠标跟踪
this->setCursor(Qt::PointingHandCursor); // 设置手开指针
si = NULL;
this->setStyle(mss); // 设置样式
}
MySlider::~MySlider(){
if(mss != NULL)
delete mss;
mss = NULL;
}
void MySlider::mouseMoveEvent(QMouseEvent *ev){
QSlider::mouseMoveEvent(ev);
ev->accept();
}
MySliderStyle *MySlider::mss = new MySliderStyle();
QPixmap *MySlider::getGroove() const{
return this->groove;
}
QPixmap *MySlider::getFill() const{
return this->fill;
}
QPixmap *MySlider::getHandle(int i) const{
switch(i){
case 0:
return this->handle_p0;
case 1:
return this->handle_p1;
case 2:
return this->handle_p2;
case 3:
return this->handle_p3;
default:
return 0;
}
}
int MySlider::getFillPoint() const{
return fill_p;
}
int MySlider::getHandlePoint() const{
return handle_p;
}
SliderInfo *MySlider::getSliderInfo(){
return this->si;
}
int MySlider::initSlider(SliderInfo &si){
this->si = &si;
this->setOrientation(si.getVertical() ? Qt::Vertical : Qt::Horizontal);
this->setGeometry(*(si.getRect()));
this->groove = si.getGroove();
this->fill = si.getFill();
this->handle_p0 = si.getP0();
this->handle_p1 = si.getP1();
this->handle_p2 = si.getP2();
this->handle_p3 = si.getP3();
if(si.getVertical()){ // 进行位置调整
handle_p = (si.getRect()->width() - (handle_p0 == NULL ? si.getRect()->width() : handle_p0->width())) / 2;
fill_p = (si.getRect()->width() - this->fill->width()) / 2;
}else{
handle_p = (si.getRect()->height() - (handle_p0 == NULL ? si.getRect()->height() : handle_p0->height())) / 2;
fill_p = (si.getRect()->height() - this->fill->height()) / 2;
}
return 0;
}
如果不需要对图片进行处理,使用固定的图片,就可以不用继承QSlider类,在MySliderStyle中直接使用QPixmap对象载入图片。
效果图如下: