QT c++和qml交互实例

文章目录

      • 一、demo效果图
      • 二、c++和qml交互的基本方式
        • 1、qml访问C++类对象
      • 三、关键代码
        • 1、工程结构图
        • 2、c++代码
          • MainWindow.cpp
          • MainQuickView.cpp
          • StudentInfoView.cpp
          • StudentInfoModel.cpp
        • 3、qml代码
          • main.qml
          • MainQuickTopRect.qml
          • MainQuickMiddleRect.qml
          • MainQuickMiddleTableRect.qml

一、demo效果图

该实例,主要是在已有的QWidget工程中,加入qml工程,方便qml项目的开发与维护,让qml开发者更好的上手qml。

(1)展示了c++和qml常见的交互方式。
(2)qwidget工程如何加载qml工程,如何加载自己实现的qml tool库。
(3)创建无边框qml界面,支持拖拽,窗口的缩小与展开,界面的分段实现。
(4)展示一个简单的堆栈窗口(SwipeView),相当于QStackedWidget,管理多个子窗口页面。
(5)实现一个简单的列表框(ListView),相当于QListWidget,定时请求网络数据,展示学生信息,将view和model进行分离,降低界面与数据的耦合。
(6)点击学号、年龄,实现了列表数据的排序。

QT c++和qml交互实例_第1张图片

二、c++和qml交互的基本方式

交互有很多种方式,有的比较繁杂,不易理解,此处只使用了最方便使用的方法,满足基本的交互。

1、qml访问C++类对象

需要先将C++类(MyWindow)注册到QQuickView中。和qml相关的C++类,最好都进行注册。

class MainQuickView : public QQuickView
{
    Q_OBJECT
public:
    MainQuickView(QQuickView *parent = nullptr);
    ~MainQuickView() override;
    void initialzeUI();
protected:
};

void MainQuickView ::initialzeUI()
{
    // 注册,为了qml中可以直接访问C++对象
    MyWindow *w = new MyWindow();
    this->rootContext()->setContextProperty("myWindow", w);
	...
}

这样C++的信号public槽函数Q_INVOKABLE 修饰的类成员函数

class MyWindow : public QWidget
{
      Q_OBJECT
  public:
      MyWindow();
      // Q_INVOKABLE可以将C++函数暴漏给qml引擎,注册到元对象系统
      Q_INVOKABLE void invokableMethod();
  signals:
      void dataChanged();
  public slots:
  	  void refresh();
};

就可以在qml中调用

// main.qml
Rectangle {
    id: root
    width: 1200
    height: 800
    onClicked: {
        myWindow.showNormal(); // showNormal是QWidget父类的槽函数
    }
}

三、关键代码

1、工程结构图

QT c++和qml交互实例_第2张图片

2、c++代码
MainWindow.cpp
MainWindow::MainWindow(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MainWindow)
    , m_pQuickVew(nullptr)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    if (!m_pQuickVew)
    {
    	// 加载qml包含两种方式,一种是QQuickView,一种是QQmlApplicationEngine
    	// 当前使用第一种方式,MainQuickView继承自QQuickView
        m_pQuickVew = new MainQuickView();
        m_pQuickVew->setObjectName("quickView");
    }

    m_pQuickVew->show();
}
MainQuickView.cpp
static const char* s_mainPath = "qrc:/qml/main.qml";

MainQuickView::MainQuickView(QQuickView *parent)
    : QQuickView(parent)
    , m_bMin(false)
{
    this->setFlags(Qt::Window | Qt::FramelessWindowHint);
    this->setTitle(QString::fromLocal8Bit("图书馆"));
    initialize();
    setMoveable();
    setTitleData();
}

MainQuickView::~MainQuickView()
{
}

void MainQuickView::initialize()
{
    // 初始化view 和 model,降低耦合,提高可维护性
    if (!m_pStudentInfoView)
        m_pStudentInfoView = new StudentInfoView();

    if (!m_pStudentInfoModel)
        m_pStudentInfoModel = m_pStudentInfoView->getStudentInfoMode();

    // ...其他功能

    initialzeUI();
}

void MainQuickView::initialzeUI()
{
    // 注册,qml中可以直接访问C++对象
    this->rootContext()->setContextProperty("mainQuickView", this);
	// qml可以通过"studentInfoView",去使用其实现的函数
    this->rootContext()->setContextProperty("studentInfoView", m_pStudentInfoView);
    this->rootContext()->setContextProperty("studentInfoModel", m_pStudentInfoModel);

    // qml根对象大小随窗口大小改变而改变
    this->setResizeMode(QQuickView::SizeRootObjectToView);
    // 导入自定义模块工具
    this->engine()->addImportPath(":/qmlTools");
    // 设置准备加载得qml文件,相当于c++的main函数入口
    this->setSource(QUrl(s_mainPath));
}

void MainQuickView::minMaxQmlWindow()
{
    m_bMin = !m_bMin;
    emit minQmlWindow(m_bMin);
}

void MainQuickView::onSendTopRectPos(QVariant pX, QVariant pY)
{
    this->setX(this->x() + pX.toInt());
    this->setY(this->y() + pY.toInt());
}

void MainQuickView::onCloseQuickView()
{
    close();
}

void MainQuickView::onShowMinimized()
{
    showMinimized();
}

void MainQuickView::setMoveable()
{
	// 当移动qml窗口时,将移动坐标告诉C++
    // 找到对象名叫topRect的qml,C++绑定qml的信号
    QQuickItem* topRect = this->rootObject()->findChild<QQuickItem*>("topRect");
    if (topRect) {
        connect(topRect, SIGNAL(sendTopRectPos(QVariant, QVariant)),
                this, SLOT(onSendTopRectPos(QVariant, QVariant)));
    }
}

void MainQuickView::setTitleData()
{
    // 找到对象名叫topRect的qml,C++向qml发送消息,触发qml中实现的setTitleText函数
    QQuickItem* topRect = this->rootObject()->findChild<QQuickItem*>("topRect");
    QMetaObject::invokeMethod(topRect, "setTitleText", Q_ARG(QVariant, QString::fromLocal8Bit("Qml窗口标题")));
}
StudentInfoView.cpp
StudentInfoView::StudentInfoView(QObject *parent)
    : QObject(parent)
    , m_sortType(StudentInfoSort::sortNone)
{
    if (!m_pStudentInfoModel)
        m_pStudentInfoModel = new StudentInfoModel(this);
	
	// 如果数据来自网络,就需要定时请求接口,并更新界面
    m_timer = new QTimer(this);
    m_timer->setInterval(10 * 1000);
    QObject::connect(m_timer, &QTimer::timeout, this, &StudentInfoView::onUpdateInfoData);

    onUpdateInfoData();
    m_timer->start();
}

StudentInfoModel *StudentInfoView::getStudentInfoMode()
{
    return m_pStudentInfoModel;
}

// qSort()比较函数不能定义为类成员函数(参数包含隐藏的this指针),会导致参数不符
// 可以定义为static类成员函数、static函数、普通函数
bool StudentInfoView::idAscend(const StudentInfoItem &stu1, const StudentInfoItem &stu2)
{
    return stu1.stuId < stu2.stuId; //学号升序
}

// 普通函数
bool idDescend(const StudentInfoItem &stu1, const StudentInfoItem &stu2)
{
    return stu1.stuId > stu2.stuId; //学号降序
}

bool ageAscend(const StudentInfoItem &stu1, const StudentInfoItem &stu2)
{
    return stu1.age < stu2.age; //年龄升序
}

void StudentInfoView::setSort(StudentInfoSort sortType)
{
    m_sortType = sortType;
    if (m_sortType == StudentInfoSort::sortIdAscend)
    {
        qSort(m_allDatas.begin(), m_allDatas.end(), idAscend);
    }
    else if (m_sortType == StudentInfoSort::sortIdDescend)
    {
        qSort(m_allDatas.begin(), m_allDatas.end(), idDescend);
    }
    else if (m_sortType == StudentInfoSort::sortAgeAscend)
    {
    	// 比较函数也可以写为lambda表达式
        qSort(m_allDatas.begin(), m_allDatas.end(),
              [](const StudentInfoItem& stu1, const StudentInfoItem& stu2)
        {
            return stu1.age < stu2.age;
        });
    }
}

void StudentInfoView::updateSort(int sortType)
{
	// qml调用,执行哪一种排序方式,并刷新数据
    setSort((StudentInfoSort)sortType);

    if (m_pStudentInfoModel)
        m_pStudentInfoModel->clear();

    for (auto studentInfo : m_allDatas) {
        if (m_pStudentInfoModel) {
            m_pStudentInfoModel->AddModel(studentInfo);
        }
    }
}

void StudentInfoView::onUpdateInfoData()
{
    // 每隔十秒请求网络接口,根据返回的数据刷新model,该实例模拟网络数据
    m_allDatas.clear();
    if (m_pStudentInfoModel)
        m_pStudentInfoModel->clear();

    QVector<StudentInfoItem> studentInfos;

    StudentInfoItem item1;
    item1.stuId = 9704;
    item1.stuName = QString::fromLocal8Bit("百里");
    item1.sex = 1;
    item1.age = 14;

    StudentInfoItem item2;
    item2.stuId = 9207;
    item2.stuName = QString::fromLocal8Bit("黄忠");
    item2.sex = 1;
    item2.age = 26;

    StudentInfoItem item3;
    item3.stuId = 9206;
    item3.stuName = QString::fromLocal8Bit("鲁班");
    item3.sex = 1;
    item3.age = 17;

    StudentInfoItem item4;
    item4.stuId = 9787;
    item4.stuName = QString::fromLocal8Bit("女娲");
    item4.sex = 0;
    item4.age = 33;

    studentInfos << item1 << item2 << item3 << item4;

    m_allDatas = studentInfos;
    setSort(m_sortType); //每一次网络数据刷新后,执行保存的排序方式

    for (auto studentInfo : m_allDatas) {
        if (m_pStudentInfoModel) {
            m_pStudentInfoModel->AddModel(studentInfo);
        }
    }
}
StudentInfoModel.cpp
#include "StudentInfoModel.h"

StudentInfoModel::StudentInfoModel(QObject *parent)
    : QAbstractListModel(parent)
{
    roleNames();
}

StudentInfoModel::~StudentInfoModel()
{

}

void StudentInfoModel::clear()
{
    if (m_allDatas.size() <= 0)
        return;

    beginRemoveRows(QModelIndex(), 0, m_allDatas.size() - 1);
    m_allDatas.clear();
    endRemoveRows();
}

void StudentInfoModel::mremove(int index)
{
    beginRemoveRows(QModelIndex(), index, index);
    m_allDatas.erase(m_allDatas.begin() + index);
    endRemoveRows();
}

void StudentInfoModel::update(int index, const StudentInfoItem &infoModel)
{
    if (index < 0 || m_allDatas.size() < index)
        return;

    StudentInfoItem &srcModel = m_allDatas[index];
    srcModel = infoModel;
}

void StudentInfoModel::AddModel(const StudentInfoItem &md)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    m_allDatas.push_back(md);
    endInsertRows();
}

QVariant StudentInfoModel::data(const QModelIndex &index, int role) const
{
    if (index.row() < 0 || index.row() >= m_allDatas.size()) {
        return QVariant();
    }

    const StudentInfoItem &itemInfo = m_allDatas.at(index.row());

    StudentInfoRoles infoRole = static_cast<StudentInfoRoles>(role);

    switch (infoRole) {
    case StudentInfoRoles::stuIdRole :
        return itemInfo.stuId;
        break;

    case StudentInfoRoles::stuNameRole :
        return itemInfo.stuName;
        break;

    case StudentInfoRoles::stuSexRole :
        return itemInfo.sex;
        break;

    case StudentInfoRoles::stuAgeRole :
        return itemInfo.age;
        break;

    default:
        return QVariant();
        break;
    }
}

int StudentInfoModel::rowCount(const QModelIndex &parent) const
{
    return m_allDatas.size();
}

QHash<int, QByteArray> StudentInfoModel::roleNames() const
{
    // 映射C++端的枚举与QML端的字符串
    QHash<int, QByteArray> data;

    data[int(StudentInfoRoles::stuIdRole)]   = "stuId";
    data[int(StudentInfoRoles::stuNameRole)] = "stuName";
    data[int(StudentInfoRoles::stuSexRole)]  = "sex";
    data[int(StudentInfoRoles::stuAgeRole)]  = "age";
    return data;
}
3、qml代码
main.qml
import QtQuick 2.0
import QtQuick.Layouts 1.0

Item {
    id: root
    width: 1200
    height: 800

    Rectangle {
        anchors.fill: parent
        color: "red"
        ColumnLayout {
            spacing: 0
            MainQuickTopRect {
                id: topRect
                objectName: "topRect"
                width: root.width
                height: 50
            }

            MainQuickMiddleRect {
                id: middleRect
                objectName: "middleRect"
                width: root.width
                height: root.height - topRect.height
            }
        }
    }
}
MainQuickTopRect.qml
import QtQuick 2.12
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.12
import "../qmlTools/ButtonTools"

Rectangle {
    id: root
    color: "#181D33"

    signal sendTopRectPos(var x, var y)

    function setTitleText(text) {
        titleText.text = text
    }

    function onMinQmlWindow(bMin){
        if (bMin)
            narrowBtn.clicked() // narrowBtn是最小化按钮,当然也可以直接mainQuickView.onShowMinimized()
        else
            mainQuickView.showNormal()
    }

    Component.onCompleted: {
        mainQuickView.minQmlWindow.connect(onMinQmlWindow)
    }

    RowLayout {
        id: rowLayout
        anchors.fill: root
        anchors.leftMargin: 16
        anchors.rightMargin: 16
        Text {
            id: titleText
            anchors.fill: root
            anchors.left: rowLayout.left
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
            color: "white"
            font.pixelSize: 18
            font.family: "Microsoft YaHei UI"
            font.bold: true
            text: ""

            // 设置文本框背景颜色
            Rectangle {
                id: titleTextBack
                anchors.fill: titleText
                color: "#000000"
                z: -1
            }
        }

        // 最小化
        ImageBtn {
            id: narrowBtn
            width: 24
            height: 24
            anchors.right: closeBtn.left

            normalUrl: "qrc:/resource/minNormal.png"
            hoveredUrl: "qrc:/resource/minHover.png"
            pressedUrl: "qrc:/resource/minHover.png"

            onClicked: {
                mainQuickView.onShowMinimized();
                console.log("min");
            }
        }

        // 关闭
        ImageBtn {
            id: closeBtn
            width: 24
            height: 24
            anchors.right: rowLayout.right

            normalUrl: "qrc:/resource/closeNormal.png"
            hoveredUrl: "qrc:/resource/closeHover.png"
            pressedUrl: "qrc:/resource/closeHover.png"

            onClicked: {
                mainQuickView.onCloseQuickView();
            }
        }
    }

    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton
        propagateComposedEvents: true  //传递鼠标事件
        property point clickPos: "0, 0"

        onPressed: {
            clickPos = Qt.point(mouse.x, mouse.y)
            console.log("onPressed " + clickPos);
        }

        onPositionChanged: {
            var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y)
            sendTopRectPos(delta.x, delta.y)
        }
    }
}
MainQuickMiddleRect.qml
import QtQuick 2.0
import QtQuick 2.12
import QtQuick.Controls 1.2
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.2
import "../qmlTools/ButtonTools"

Rectangle {
    id: root
    color: "#2E3449"
    property int oneCount: 50
    property int twoCount: 50
    property int threeCount: 50
    readonly property int pageWidth  : 400

    Rectangle {
        id: titleSwitchBtn
        width: parent.width / 2
        anchors.top: parent.top
        anchors.topMargin: 30
        anchors.left: parent.left
        anchors.leftMargin: width - width / 2
        height: 46
        color: "#2C5CA5"

        RowLayout {
            spacing: 20
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 0
            anchors.left: parent.left
            anchors.leftMargin: 40

            DownLineBtn { // qmlTool中自定义实现的下划线按钮
                id: cover_one_btn
                btn_width: 100
                btn_string: qsTr("一班") + oneCount + qsTr("人")
                onMyClicked: {
                    coverBtnStat(cover_one_btn, true)
                    coverBtnStat(cover_two_btn, false)
                    coverBtnStat(cover_three_btn, false)
                    swipeView.currentIndex = 0
                }
            }

            DownLineBtn {
                id: cover_two_btn
                btn_width: 100
                btn_string: qsTr("二班") + twoCount + qsTr("人")
                onMyClicked: {
                    coverBtnStat(cover_one_btn, false)
                    coverBtnStat(cover_two_btn, true)
                    coverBtnStat(cover_three_btn, false)
                    swipeView.currentIndex = 1
                }
            }

            DownLineBtn {
                id: cover_three_btn
                btn_width: 100
                btn_string: qsTr("三班") + threeCount + qsTr("人")
                onMyClicked: {
                    coverBtnStat(cover_one_btn, false)
                    coverBtnStat(cover_two_btn, false)
                    coverBtnStat(cover_three_btn, true)
                    swipeView.currentIndex = 2
                }
            }
        }
    }

    Rectangle {
        id: view
        anchors.top: titleSwitchBtn.bottom
        anchors.topMargin: 4
        anchors.left: titleSwitchBtn.left
        width: titleSwitchBtn.width
        height: pageWidth
        color: "white"
        radius: 4

        SwipeView {
            id: swipeView
            objectName: "outSideSwipView"
            anchors.fill: parent
            currentIndex: 0
            clip: true  //隐藏未选中的界面
            interactive: false  //鼠标能否滑动

            Item {  //St: 0,第一页
                id: onePage
                Rectangle {
                    id: oneView
                    anchors.fill: parent
                    color: "#D7B9A1"
                }
            }

            Item {  //St: 1,第二页
                id: twoPage
                MainQuickMiddleTableRect {
                    id: tableView
                    color: "green"
                    width: titleSwitchBtn.width
                    height: pageWidth
                }
            }

            Item { //St: 2,第三页
                id: defaultPage

                Rectangle {
                    anchors.fill: parent
                    color: "#E6EC12"
                    radius: 4
                    Text {
                        text: qsTr("没有数据")
                        font.pixelSize:16
                        color: "red"
                        font.family: "Microsoft YaHei"
                        verticalAlignment: Text.AlignVCenter
                        horizontalAlignment: Text.AlignHCenter
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.horizontalCenter: parent.horizontalCenter
                    }
                }
            }

        }
    }

    Component.onCompleted: {
    	// 初始化按钮状态
        coverBtnStat(cover_one_btn, true)
        coverBtnStat(cover_two_btn, false)
        coverBtnStat(cover_three_btn, false)
    }

    /*  btn: object
     *  st : modify state
    */
    function coverBtnStat(btn, st) {
        btn.bold_st = st
        btn.show_line = st
    }
}
MainQuickMiddleTableRect.qml
import QtQuick 2.0
import "../qmlTools/ButtonTools"
import QtQuick.Controls 1.4
import QtQuick.Controls 2.5

Rectangle {
    id: win
    anchors.fill: parent
    color: "green"

    property var current_numbers      : 0
    property var stu_id_sort_state    : 1
    property var stu_id_sort_open     : false

    // 绘制表头
    Rectangle {
        id: table_Head
        height: 48
        anchors.top: titleSwitchBtn.bottom

        Rectangle {
            id: table_Text
            height: 47  // 比整个表头高度小1,为了容纳下划线

            TextShow {
                id: name_text
                text: qsTr("姓名")
                anchors.left: table_Text.left
                anchors.leftMargin: 68
                anchors.verticalCenter: table_Text.verticalCenter
            }

            TextShow {
                id: id_text
                // 学号未点击时,显示"↕",表示不排序,点击后再判断时升序还是降序
                text: qsTr("学号") + (!stu_id_sort_open ?
                                     qsTr("↕") :
                                     (stu_id_sort_state ? qsTr("↓") : qsTr("↑")))
                anchors.left: table_Text.left
                anchors.leftMargin: 150
                anchors.verticalCenter: table_Text.verticalCenter

                MouseArea {
                    anchors.fill: parent
                    propagateComposedEvents: true
                    onPressed: {
                        console.log("student id clicked")
                        studentInfoView.updateSort(stu_id_sort_state ? 1 : 2) // 通知C++修改排序方式
                        stu_id_sort_state = !stu_id_sort_state
                        stu_id_sort_open = true
                    }
                }
            }

            TextShow {
                id: sex_text
                text: qsTr("性别")
                anchors.left: table_Text.left
                anchors.leftMargin: 220
                anchors.verticalCenter: table_Text.verticalCenter
            }

            TextShow {
                id: age_text
                text: qsTr("年龄")
                anchors.left: table_Text.left
                anchors.leftMargin: 290
                anchors.verticalCenter: table_Text.verticalCenter

                MouseArea {
                    anchors.fill: parent
                    propagateComposedEvents: true
                    onPressed: {
                        console.log("student age clicked")
                        studentInfoView.updateSort(3)
                        stu_id_sort_open = false
                    }
                }
            }

            LongLine {
                anchors.top: table_Text.bottom
            }
        }
    }

    //listView
    Rectangle {
        width: parent.width
        height: parent.height - table_Head.height
        color: "green"
        anchors.top: table_Head.bottom

        ListView {
            id: list_view
            anchors.rightMargin: 10
            anchors.bottomMargin: 50
            anchors.leftMargin: 0
            anchors.topMargin: 0
            anchors.fill: parent
            maximumFlickVelocity: 800
            clip: true
            delegate: studentInfoDelegate
            model: studentInfoModel
            boundsBehavior: Flickable.StopAtBounds
            highlightMoveDuration: 0

            ScrollBar.vertical: ScrollBar {
                id: scrollbar
                //visible: (current_numbers > 8)
                visible: true
                anchors.right: list_view.right
                width: 8
                active: true
                background: Item {
                    Rectangle {
                        anchors.right: parent.right
                        height: parent.height
                        color: "yellow"
                        radius: 4
                    }
                }
                contentItem: Rectangle {
                    radius: 4
                    color: "red"
                }
            }
        }

        //studentInfoDelegate 渲染每一个item
        Component {
            id: studentInfoDelegate
            Rectangle {
                id: list_item
                width: parent.width
                height: 46
                color: "green"

                Rectangle {
                    width: parent.width
                    height: parent.height
                    color: "green"
                    anchors.verticalCenter: parent.verticalCenter

                    Rectangle {
                        id: rect_item
                        color: "#333333"
                        height: 45

                        TextShow {
                            id: stu_name
                            text: model.stuName
                            anchors.verticalCenter: rect_item.verticalCenter
                            anchors.left: rect_item.left
                            anchors.leftMargin: 68
                        }

                        TextShow {
                            id: id_type
                            text: model.stuId
                            anchors.verticalCenter: rect_item.verticalCenter
                            anchors.left: rect_item.left
                            anchors.leftMargin: 150
                        }

                        TextShow {
                            id: sex_type
                            text: (model.sex == 1) ? qsTr("男") : qsTr("女")
                            anchors.verticalCenter: rect_item.verticalCenter
                            anchors.left: rect_item.left
                            anchors.leftMargin: 220
                        }

                        TextShow {
                            id: age_type
                            text: model.age + qsTr(" 岁")
                            anchors.verticalCenter: rect_item.verticalCenter
                            anchors.left: rect_item.left
                            anchors.leftMargin: 290
                        }
                    }

                    LongLine {
                        anchors.top: rect_item.bottom
                    }
                }
            }
        }
    }

    Component.onCompleted: {
    	// qml加载完成后,可以获取C++中数据的个数,用来标识是否需要展示滚动条
        current_numbers = 4
    }
}

你可能感兴趣的:(qt,c++,qml)