Qt

Qt QML开发cpp的一些心得

Qt、C++

Posted by MetaNetworks on August 22, 2020
本页面总访问量

Qt 5.15.0 demo

Qt QML与C++类的交互方式

方式1:向QML注册C++类,并在.qml文件中使用

1
2
3
// add image provider
QQmlContext* context = engine.rootContext();
context->setContextProperty("deviceQrImgLoader",&qrImageLoader);

之后可以使用Connections进行信号的处理

1
2
3
4
5
6
Connections{
    target: deviceQrImgLoader
    on...:{
        console.log("on... triggered")
    }
}

若信号为initCompleted,则qml中为onInitCompleted

方式2:向QQmlContext上下文中注册类

1
2
qmlRegisterType<FileService>("cn.MetaNetworks.fileservice",1,0,"FileService");
qmlRegisterType<DeviceQrImageLoader>("cn.MetaNetworks.deviceqrimgloader",1,0,"DeviceQrImgLoader");

之后可以在QML中定义使用,通过id进行定义。

1
2
3
4
FileService {
    id: service
    ...
}

方式3:直接向QQmlContext中注册QObject

1
context->setContextObject(QObject* object)

Qt中使用libqrencode生成、展示二维码

1. 生成二维码

  • 保存在代码第一行的image
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
QImage image(300,300, QImage::Format_RGB32);
    image.fill(QColor("black"));

    QPainter painter(&image);
    if(!painter.isActive()){
    }else{
        _data = QJsonDocument(device_arr.at(index).toObject()).toJson(QJsonDocument::Compact);
        QRcode *qrCode = QRcode_encodeString(this->_data.toUtf8().data(), 1, QR_ECLEVEL_H, QR_MODE_8, true);
        qDebug()<<_data;
        if(!qrCode){
            QColor error("white");
            painter.setBrush(error);
            painter.setPen(Qt::NoPen);
            painter.drawRect(0, 0, image.width(), image.height());
            painter.end();
        }else{
            QColor colorForPoint("black");
            QColor colorForBackgroud("white");
            //先画背景
            painter.setBrush(colorForBackgroud);
            painter.setPen(Qt::NoPen);
            painter.drawRect(0, 0, image.width(), image.height());
            //再画二维码图案
            painter.setBrush(colorForPoint);
            const double &&s = ( qrCode->width > 0 ) ? ( qrCode->width ) : ( 1 );
            const double &&aspect = image.width() / image.height();
            const double &&scale = ( ( aspect > 1.0 ) ? image.height() : image.width() ) / s;
            for ( int y = 0; y < s; ++y )
            {
                const int &&yy = static_cast< int >( y * s );
                for( int x = 0; x < s; ++x )
                {
                    const int &&xx = yy + x;
                    const unsigned char &b = qrCode->data[xx];
                    if( b & 0x01 )
                    {
                        const double rx1 = x * scale, ry1 = y * scale;
                        QRectF r( rx1, ry1, scale, scale );
                        painter.drawRects( &r,1 );
                    }
                }
            }
            QRcode_free( qrCode );
            painter.end();
        }
    }

2. 将二维码图片传入QML界面

此处使用ImageProvider实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
class ImageProvider : public QQuickImageProvider
{
public:
    ImageProvider(): QQuickImageProvider(QQuickImageProvider::Pixmap){

    }

    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);

    QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);

    QImage img;
};
1
2
3
4
5
6
7
8
9
10
11
#include "imageprovider.h"

QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
    return this->img;
}

QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
    return QPixmap::fromImage(this->img);
}
  • 在QmlApplicationEngine中注册
1
engine.addImageProvider("DeviceQRImg", qrImageLoader.provider);

注册后,可以使用"image://DeviceQRImg"访问到图片,若在上述实现方法中用到了id,则将id追加在DeviceQRImg之后即可

  • QML代码
    • 如果图片需要切换,则需要关闭QML Image的默认cache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
                Image{
                    Layout.alignment: Qt.AlignCenter
                    id: img
                    width: 250
                    height: 250
                    source: "image://DeviceQRImg"
                    cache: false //关闭缓存
                    Connections{
                        target: deviceQrImgLoader
                        onRefreshImg:{
                            console.log("qml refresh");
                            img.source = "";
                            img.source = "image://DeviceQRImg";
                            var info = deviceQrImgLoader.getNetworkDevicesInfo()[deviceQrImgLoader.index];
                            current_device.text =  "当前:"+info["card"];
                             network_card_ip.text = info["ip"];
                             network_card_mac.text = info["mac"];
                             network_card_name.text = info["hostname"];
                            network_card_type.text = info["type"];
                        }

                    }

                }

Qt中针对不同平台的代码实现

  • macOS:Q_OS_MACOS
  • Linux:Q_OS_LINUX
  • Windows:Q_OS_WINDOWS

可以针对不同的系统,实现函数

1
2
3
4
5
#ifdef Q_OS_MACOS
void class::function(){
    
}
#endif

Qt中QSSLSocket的使用

此处记录:创建一个自己的类,继承QTcpServer

流程:incomingConnection -> newConnection -> nextPendingConnection -> readyRead

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class FileService : public QTcpServer
{
    Q_OBJECT
    Q_PROPERTY(bool status READ getStatus WRITE changeStatus NOTIFY statusChanged);
public:
    FileService(QObject *parent=0);
    // 端口监听
    Q_INVOKABLE int startListen();
    Q_INVOKABLE int stopListen();
    void changeStatus(bool running){this->status = running;};
    bool getStatus(){return this->status;};

public slots:
    void init();

private slots:
    void sslErrors(const QList<QSslError> &errors);
    void link();
    void rx();
    void disconnected();

protected:
    void incomingConnection(qintptr socketDescriptor); //重载函数

signals:
    void initComplete();
    void statusChanged(bool running);

private:
    // 初始化SSL
    int initSSL();
    QSslKey _key;
    QSslCertificate _cert;
    bool status;
    QProcess _process;
};

#endif // FILESERVICE_H
  • 初始化SSL证书
1
2
3
4
5
6
7
8
9
10
// 初始化文件HTTP
QFile keyFile(":/cert/red_local.key");
keyFile.open(QIODevice::ReadOnly);
_key = QSslKey(keyFile.readAll(), QSsl::Rsa);
keyFile.close();

QFile certFile(":/cert/red_local.pem");
certFile.open(QIODevice::ReadOnly);
_cert = QSslCertificate(certFile.readAll());
certFile.close();
  • 开启监听

    • 有新用户连接时,会先调用incomingConnection(这个函数不重载的话为普通TCP连接,自己实现可以加入SSL功能等)

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        
        void FileService::incomingConnection(qintptr socketDescriptor)
        {
            qDebug("incoming");
            QSslSocket *sslSocket = new QSslSocket(this);
              
            connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrors(QList<QSslError>)));
            sslSocket->setSocketDescriptor(socketDescriptor);
            sslSocket->setPrivateKey(_key);
            sslSocket->setLocalCertificate(_cert);
            sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone);
            sslSocket->startServerEncryption();
              
            addPendingConnection(sslSocket);
        }
        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int FileService::startListen()
{
    if (!listen(QHostAddress("0.0.0.0"), 2090)) {
            qCritical() << "2090端口被占用,请检查其他程序是否占用端口";
            QMessageBox box;
            box.setStandardButtons(QMessageBox::Ok);
            box.setText("2090端口被占用,请检查其他程序是否占用端口");
            box.setDefaultButton(QMessageBox::Ok);
            box.exec();
            exit(0);
    }

    connect(this,&FileService::newConnection,this,&FileService::link);
    status = true;
    emit statusChanged(status);
    return OK;
}
  • 实现link函数
    • 此处绑定读取数据、断开连接二信号,分别对应我们实现的rx、disconnected函数(相当于callback)
1
2
3
4
5
6
7
8
9
void FileService::link()
{
    qDebug()<<"recv 1 client";
    QTcpSocket *clientSocket;

    clientSocket = nextPendingConnection();
    connect(clientSocket, &QTcpSocket::readyRead, this, &FileService::rx);
    connect(clientSocket, &QTcpSocket::disconnected, this, &FileService::disconnected);
}
  • 可以在rx中使用sender()获取QTcpSocket*指针进行数据交互

    • 1
      2
      
      QTcpSocket* clientSocket = qobject_cast<QTcpSocket*>(sender());
          QByteArray recvData = clientSocket->readAll();
      
  • 可以在disconnected中做一些用户断开的操作

    • 1
      2
      3
      
      qDebug("Client Disconnected");
      QTcpSocket* clientSocket = qobject_cast<QTcpSocket*>(sender());
      clientSocket->deleteLater();
      

QML中Row、RowLayout、Column、ColumnLayout的区别

Row和Column是QtQuick 库提供的,RowLayout以及ColumnLayout是QtQuick.Layouts提供的。 顾名思义,含有Layout的表示一种布局方法,而Row和Column表示一种排列方法。

  • Row、Column都直接为QML类型

    • Column is a type that positions its child items along a single column. It can be used as a convenient way to vertically position a series of items without using anchors.

  • RowLayout、ColumnLayout都继承自Item,Item为QML类型

    • 子item可以使用Layout.xxx方法进行居中等显示

…这个后面再整理吧