文章目录
一、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)点击学号、年龄,实现了列表数据的排序。
二、c++和qml交互的基本方式
交互有很多种方式,有的比较繁杂,不易理解,此处只使用了最方便使用的方法,满足基本的交互。
1、qml访问C++类对象
需要先将C++类(MyWindow)注册到QQuickView中。
和qml相关的C++类,最好都进行注册。
classMainQuickView:publicQQuickView{
Q_OBJECT
public:MainQuickView(QQuickView *parent =nullptr);~MainQuickView()override;voidinitialzeUI();protected:};voidMainQuickView::initialzeUI(){// 注册,为了qml中可以直接访问C++对象
MyWindow *w =newMyWindow();this->rootContext()->setContextProperty("myWindow", w);...}
这样C++的
信号
,
public槽函数
,
Q_INVOKABLE 修饰的类成员函数
classMyWindow:publicQWidget{
Q_OBJECT
public:MyWindow();// Q_INVOKABLE可以将C++函数暴漏给qml引擎,注册到元对象系统
Q_INVOKABLE voidinvokableMethod();
signals:voiddataChanged();public slots:voidrefresh();};
就可以在qml中调用
// main.qml
Rectangle {
id: root
width:1200
height:800
onClicked:{
myWindow.showNormal();// showNormal是QWidget父类的槽函数}}
三、关键代码
1、工程结构图
2、c++代码
MainWindow.cpp
MainWindow::MainWindow(QWidget *parent):QWidget(parent),ui(new Ui::MainWindow),m_pQuickVew(nullptr){
ui->setupUi(this);}MainWindow::~MainWindow(){delete ui;}voidMainWindow::on_pushButton_clicked(){if(!m_pQuickVew){// 加载qml包含两种方式,一种是QQuickView,一种是QQmlApplicationEngine// 当前使用第一种方式,MainQuickView继承自QQuickView
m_pQuickVew =newMainQuickView();
m_pQuickVew->setObjectName("quickView");}
m_pQuickVew->show();}
MainQuickView.cpp
staticconstchar* 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(){}voidMainQuickView::initialize(){// 初始化view 和 model,降低耦合,提高可维护性if(!m_pStudentInfoView)
m_pStudentInfoView =newStudentInfoView();if(!m_pStudentInfoModel)
m_pStudentInfoModel = m_pStudentInfoView->getStudentInfoMode();// ...其他功能initialzeUI();}voidMainQuickView::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));}voidMainQuickView::minMaxQmlWindow(){
m_bMin =!m_bMin;
emit minQmlWindow(m_bMin);}voidMainQuickView::onSendTopRectPos(QVariant pX, QVariant pY){this->setX(this->x()+ pX.toInt());this->setY(this->y()+ pY.toInt());}voidMainQuickView::onCloseQuickView(){close();}voidMainQuickView::onShowMinimized(){showMinimized();}voidMainQuickView::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)));}}voidMainQuickView::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 =newStudentInfoModel(this);// 如果数据来自网络,就需要定时请求接口,并更新界面
m_timer =newQTimer(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函数、普通函数boolStudentInfoView::idAscend(const StudentInfoItem &stu1,const StudentInfoItem &stu2){return stu1.stuId < stu2.stuId;//学号升序}// 普通函数boolidDescend(const StudentInfoItem &stu1,const StudentInfoItem &stu2){return stu1.stuId > stu2.stuId;//学号降序}boolageAscend(const StudentInfoItem &stu1,const StudentInfoItem &stu2){return stu1.age < stu2.age;//年龄升序}voidStudentInfoView::setSort(StudentInfoSort sortType){
m_sortType = sortType;if(m_sortType == StudentInfoSort::sortIdAscend){qSort(m_allDatas.begin(), m_allDatas.end(), idAscend);}elseif(m_sortType == StudentInfoSort::sortIdDescend){qSort(m_allDatas.begin(), m_allDatas.end(), idDescend);}elseif(m_sortType == StudentInfoSort::sortAgeAscend){// 比较函数也可以写为lambda表达式qSort(m_allDatas.begin(), m_allDatas.end(),[](const StudentInfoItem& stu1,const StudentInfoItem& stu2){return stu1.age < stu2.age;});}}voidStudentInfoView::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);}}}voidStudentInfoView::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(){}voidStudentInfoModel::clear(){if(m_allDatas.size()<=0)return;beginRemoveRows(QModelIndex(),0, m_allDatas.size()-1);
m_allDatas.clear();endRemoveRows();}voidStudentInfoModel::mremove(int index){beginRemoveRows(QModelIndex(), index, index);
m_allDatas.erase(m_allDatas.begin()+ index);endRemoveRows();}voidStudentInfoModel::update(int index,const StudentInfoItem &infoModel){if(index <0|| m_allDatas.size()< index)return;
StudentInfoItem &srcModel = m_allDatas[index];
srcModel = infoModel;}voidStudentInfoModel::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()){returnQVariant();}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:returnQVariant();break;}}intStudentInfoModel::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
importQtQuick2.0importQtQuick.Layouts1.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
importQtQuick2.12importQtQuick.Layouts1.0importQtQuick.Controls2.12import"../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
importQtQuick2.0importQtQuick2.12importQtQuick.Controls1.2importQtQuick.Controls2.5importQtQuick.Layouts1.1importQtQuick.Controls.Styles1.2import"../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
importQtQuick2.0import"../qmlTools/ButtonTools"importQtQuick.Controls1.4importQtQuick.Controls2.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}}
版权归原作者 daboluo520 所有, 如有侵权,请联系我们删除。