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的元对象系统为我们提供了很大的便利,我们只需重写部分函数,其余的保持不动,即可,完全没有必要将所有的功能都全部实现,没有这个必要.

却道,此心安处是吾乡
原文地址:https://www.cnblogs.com/lucifer25/p/7761488.html