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

    却道,此心安处是吾乡
  • 相关阅读:
    2019年江苏大学885编程大题
    2018年江苏大学885编程题
    python-类和对象
    unity游戏框架学习-登录模块
    unity游戏框架学习-AssetBundle
    记 Firebase Crashlytics 接入遇到的坑
    c# 枚举Enum
    unity性能优化-UGUI
    unity性能优化-GPU
    unity性能优化-CPU
  • 原文地址:https://www.cnblogs.com/lucifer25/p/7761488.html
Copyright © 2011-2022 走看看