Chapter5 創建自定義窗口部件


Chapter5 創建自定義窗口部件

Qt本身已經包含了絕大部分,我們開發過程中所需要的窗體部件,比如說QPushButton,QLineEdit等等,我們也都接觸過,用起來很方便.但是有的時候,我們由於應用上的需要,而內置的窗口部件又不能滿足我們的功能,於是就需要我們自己動手,寫一個自定義的窗口部件出來.

非常幸運,Qt中一種叫做元對象(meta object)的機制救了我們.目前我理解的也並不深刻,簡單說下自己的理解:元對象,其實就是利用了C++里抽象,封裝,繼承以及多態這種面向對象的設計思路,通過繼承,重新為子類實現功能,常規功能保持不變,因此我們可以得到自定義的子類.

這也就談及了Qt自定義窗口部件的兩種方法:

1.對一個已經存在的Qt窗口部件進行子類化
2.直接對QWidget進行子類化

上面兩種方法可以看出:第一種比較簡單,但是實現的功能有限,要圍繞其繼承的父類對象展開,第二種方法較為麻煩,但是自由度更高.

1.自定義Qt窗口部件

hexspinbox.h

#ifndef HEXSPINBOX_H
#define HEXSPINBOX_H

#include <QSpinBox>

class QRegExpValidator;
class HexSpinBox : public QSpinBox{
Q_OBJECT
public:
explicit HexSpinBox(QWidget *parent = 0);
protected:
QValidator::State validate(QString &text, int &pos) const ;
virtual int valueFromText(const QString &text) const;
virtual QString textFromValue(int value) const;
private:
QRegExpValidator *validator;
};

#endif

代碼並不復雜,在原有QSpinBox的基礎上增加了一個正則表達式檢驗器,並且重新實現三個虛函數.validate()函數返回一個QValidator::State,驗證用戶輸入的合法性,valueFromText()會在用戶輸入的時候觸發,textFromValue()會在用戶通過微調框調整值的時候觸發.

注意:在繼承QSpinxBox的時候,必須加上explicit關鍵字,禁止隱式轉換

hexspinbox.cpp

#include <QtWidgets>

#include "hexspinbox.h"

HexSpinBox::HexSpinBox(QWidget *parent) : QSpinBox(parent){
setRange(0, 255);
validator = new QRegExpValidator(QRegExp("[0-9A-Fa-f]{1,8}"), this);
}

QValidator::State HexSpinBox::validate(QString &text, int &pos) const{
return validator->validate(text, pos);
}

QString HexSpinBox::textFromValue(int value) const{
return QString::number(value, 16).toUpper();
}

int HexSpinBox::valueFromText(const QString &text) const{
bool ok;
return text.toInt(&ok, 16);
}

先設定一個范圍,再增加一個validator限制用戶輸入,這樣用戶輸入的就一定是16進制的合法值.進制的轉換就比較簡單了.

這時如果我們需要在窗口中增加這個窗體空間,直接降頭文件導入,即可使用該類,很方便.

注意:除非是想實現原本窗體控件不具備的功能,才需要子類化,如果只是想更改外觀,那大可不必如此復雜,通過修改樣式表即可實現,這個后面會接觸到.

2.子類化QWidget

通常情況,我們通過組合不同的窗體控件,可以實現很復雜的功能,但是有的時候我們會發現,無法組織出一個這樣的窗體出來,我們就需要子類化QWidget,重新實現繪制事件,以及鼠標事件等.

這一次,跟着書中所講,從0開始,實現一個圖標編輯器(Icon Editor).

iconeditor.h

#ifndef ICONEDOTOR_H
#define ICONEDOTOR_H

#include <QColor>
#include <QImage>
#include <QWidget>

class IconEditor : public QWidget{
Q_OBJECT
Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor)
Q_PROPERTY(QImage iconImage READ iconImage WRITE setIconImage)
Q_PROPERTY(int zoomFactor READ zoomFactor WRITE setZoomFactor)

public:
explicit IconEditor(QWidget *parent = 0);

void setPenColor(const QColor &newColor);
QColor penColor() const{
return curColor;
}
void setZoomFactor(int newZoom);
int zoomFactor() const{
return zoom;
}
void setIconImage(const QImage &newImage);
QImage iconImage() const{
return image;
}
QSize sizeHint() const;

protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);

private:
void setImagePixel(const QPoint &pos, bool obaque);
QRect pixelRect(int i, int j) const;

QColor curColor;
QImage image;
int zoom;
};

#endif

明顯的復雜了很多.

先定義三個Q_PROPERTY宏,聲明一個數據,再聲明讀寫屬性,有些像Java里的setter和getter.

核心的屬性有三個:curColor用來描述畫筆的顏色,image用來表示圖像,zoom表示放大級別,共有函數即為這三個屬性的讀寫函數,保護屬性里重新實現QWidget的繪制事件,還有鼠標的點擊和移動事件.

iconeditor.cpp

#include <QtWidgets>
#include <QRegion>
#include "iconeditor.h"

IconEditor::IconEditor(QWidget *parent) : QWidget(parent){
setAttribute(Qt::WA_StaticContents);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
curColor = Qt::black;
zoom = 8;

image = QImage(16, 16, QImage::Format_ARGB32);
image.fill(qRgba(0, 0, 0, 0));
}

QSize IconEditor::sizeHint() const{
QSize size = zoom * image.size();
if(zoom >= 3){
size += QSize(1, 1);
}
return size;
}

void IconEditor::setPenColor(const QColor &newColor){
curColor = newColor;
}

void IconEditor::setIconImage(const QImage &newImage){
if(newImage != image){
image = newImage.convertToFormat(QImage::Format_ARGB32);
update();
updateGeometry();
}
}

void IconEditor::setZoomFactor(int newZoom){
if(newZoom < 1){
newZoom = 1;
}
if(newZoom != zoom){
zoom = newZoom;
update();
updateGeometry();
}
}

void IconEditor::paintEvent(QPaintEvent *event){
QPainter painter(this);

if(zoom >= 3){
painter.setPen(palette().foreground().color());
for(int i = 0; i <= image.width(); ++i){
painter.drawLine(zoom * i, 0, zoom * i, zoom * image.height());
}
for(int j = 0; j <= image.height(); ++j){
painter.drawLine(0, zoom * j, zoom * image.width(), zoom * j);
}
}

for(int i = 0; i < image.width(); ++i){
for(int j = 0; j < image.height(); ++j){
QRect rect = pixelRect(i, j);
if(event->region().intersects(rect)){
QColor color = QColor::fromRgba(image.pixel(i, j));
if(color.alpha() < 255){
painter.fillRect(rect, Qt::white);
}
painter.fillRect(rect, color);
}
}
}
}

QRect IconEditor::pixelRect(int i, int j) const{
if(zoom >= 3){
return QRect(zoom * i + 1, zoom * j + 1, zoom - 1, zoom - 1);
}else{
return QRect(zoom *i, zoom *j, zoom, zoom);
}
}

void IconEditor::mousePressEvent(QMouseEvent *event){
if(event->button() == Qt::LeftButton){
setImagePixel(event->pos(), true);
}else if(event->button() == Qt::RightButton){
setImagePixel(event->pos(), false);
}
}

void IconEditor::mouseMoveEvent(QMouseEvent *event){
if(event->buttons() & Qt::LeftButton){
setImagePixel(event->pos(), true);
}else if(event->buttons() & Qt::RightButton){
setImagePixel(event->pos(), false);
}
}

void IconEditor::setImagePixel(const QPoint &pos, bool obaque){
int i = pos.x() / zoom;
int j = pos.y() / zoom;

if(image.rect().contains(i, j)){
if(obaque){
image.setPixel(i, j, penColor().rgba());
}else{
image.setPixel(i, j, qRgba(0, 0, 0, 0));
}
update(pixelRect(i, j));
}

}

不拘泥於具體的代碼,主要看三個部分:

sizeHint()函數,可以返回一個窗口部件的理想大小.

update()重新繪制窗口事件,這時會調用paintEvent()函數.

paintEvent()函數是整個源文件的核心,負責窗口的繪制,也是最復雜的部分,是通過QPainter對象實現的.

3.在Qt設計師中集成自定義窗口部件

方法主要分為兩種:改進法和插件法,其中,改進法簡單直接,更容易利用,也便於分發.
比如HexSpinBox,現在設計師窗口中拖入一個QSpinBox,右擊,選擇'Promote to Custom Widget',將HexSpinBox的類名填入,即可.

插件法過於復雜,而且Qt4和5之間的方式不兼容,按下不表.

總結:這一節我們自定義了自己的窗體控件,自由度還是挺高的,可以根據自己的需求,選擇性的實現原本並不具備的功能,而且Qt的元對象系統為我們提供了很大的便利,我們只需重寫部分函數,其余的保持不動,即可,完全沒有必要將所有的功能都全部實現,沒有這個必要.


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com