關于定制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對象載入圖檔。
效果圖如下: