引入
在上篇文章基于QML的飞行仪表中,从GitHub上搬下来几个基于QML的飞行仪表,但只是绘制出来,并不会动。今天通过C++对象访问的方式把他们驱动起来。
实际上,原项目也是这么做的,这里参考其C++类,并做了一些改动。
QML访问C++对象
在之前的文章中曾提到过QML与C++交互的方法,用到了将C++类名注册到QML,并在QML实体化然后使用的方式。今天我们换个方式实现,在C++中先定义类并实体化,然后使用QML访问C++对象。
C++类的定义其实没有什么区别,QML要想访问C++对象的方法,要使用Q_INVOKABLE、Q_PROPERTY宏等。
访问C++对象,需要先注册该对象
QQmlApplicationEngine::rootContext()->setContextProperty()
然后就可以在QML中直接访问C++对象了
C++类
在工程上右键添加类,类必须继承 QObject ,并包含QObject。直接上源码
#ifndef FLIGHTDATA_H
#define FLIGHTDATA_H
#include <QObject>
#include <QTimer>
class FlightData : public QObject
{
Q_OBJECT
public:
explicit FlightData(QObject *parent = nullptr);
double roll() const;
void setRoll(double newroll);
double pitch() const;
void setPitch(double newpitch);
double heading() const;
void setHeading(double newheading);
double slipSkid() const;
void setSlipSkid(double newslipskid);
double turnRate() const;
void setTurnRate(double newturnrate);
double airspeed() const;
void setAirspeed(double newairspeed);
double pressure() const;
void setPressure(double newpressure);
double altitude() const;
void setAltitude(double newaltitude);
double climbRate() const;
void setClimbRate(double newclimbrate);
Q_INVOKABLE void anim_Start();
Q_INVOKABLE void anim_Stop();
signals:
void rollChanged();
void pitchChanged();
void headingChanged();
void slipSkidChanged();
void turnRateChanged();
void airspeedChanged();
void pressureChanged();
void altitudeChanged();
void climbRateChanged();
private:
double m_roll;
double m_pitch;
double m_heading;
double m_slipskid;
double m_turnrate;
double m_airspeed;
double m_pressure;
double m_altitude;
double m_climbrate;
QTimer mTimer;
int frame;
void update();
Q_PROPERTY(double roll READ roll WRITE setRoll NOTIFY rollChanged);
Q_PROPERTY(double pitch READ pitch WRITE setPitch NOTIFY pitchChanged);
Q_PROPERTY(double heading READ heading WRITE setHeading NOTIFY headingChanged);
Q_PROPERTY(double slipSkid READ slipSkid WRITE setSlipSkid NOTIFY slipSkidChanged);
Q_PROPERTY(double turnRate READ turnRate WRITE setTurnRate NOTIFY turnRateChanged);
Q_PROPERTY(double airspeed READ airspeed WRITE setAirspeed NOTIFY airspeedChanged);
Q_PROPERTY(double pressure READ pressure WRITE setPressure NOTIFY pressureChanged);
Q_PROPERTY(double altitude READ altitude WRITE setAltitude NOTIFY altitudeChanged);
Q_PROPERTY(double climbRate READ climbRate WRITE setClimbRate NOTIFY climbRateChanged);
};
#endif // FLIGHTDATA_H
C++类的实现
#include "flightdata.h"
#include <cmath>
FlightData::FlightData(QObject *parent)
: QObject{parent}
{
m_roll = 0;
m_pitch = 0;
m_heading = 0;
m_slipskid = 0;
m_turnrate = 0;
m_airspeed = 0;
m_pressure = 0;
m_altitude = 0;
m_climbrate = 0;
frame = 0;
connect(&mTimer,&QTimer::timeout,this,&FlightData::update);
}
double FlightData::roll() const{
return m_roll;
}
void FlightData::setRoll(double newroll){
if (std::isnan(newroll) || qFuzzyCompare(m_roll, newroll))
return;
if (newroll < -180)
newroll = -180;
else if (newroll > 180)
newroll = 180;
m_roll = newroll;
emit rollChanged();
}
double FlightData::pitch() const{
return m_pitch;
}
void FlightData::setPitch(double newpitch){
if (std::isnan(newpitch) || qFuzzyCompare(m_pitch, newpitch))
return;
if (newpitch < -90)
newpitch = -90;
else if (newpitch > 90)
newpitch = 90;
m_pitch = newpitch;
emit pitchChanged();
}
double FlightData::heading() const{
return m_heading;
}
void FlightData::setHeading(double newheading){
if (std::isnan(newheading) || qFuzzyCompare(m_heading, newheading))
return;
while (newheading < 0.0)
newheading += 360.0;
while (newheading > 360.0)
newheading -= 360.0;
m_heading = newheading;
emit headingChanged();
}
double FlightData::slipSkid() const{
return m_slipskid;
}
void FlightData::setSlipSkid(double newslipskid){
if (std::isnan(newslipskid) || qFuzzyCompare(m_slipskid, newslipskid))
return;
if (newslipskid < -15)
newslipskid = -15;
else if (newslipskid > 15)
newslipskid = 15;
m_slipskid = newslipskid;
emit slipSkidChanged();
}
double FlightData::turnRate() const{
return m_turnrate;
}
void FlightData::setTurnRate(double newturnrate){
if (std::isnan(newturnrate) || qFuzzyCompare(m_turnrate, newturnrate))
return;
if (newturnrate < -6)
newturnrate = -6;
else if (newturnrate > 6)
newturnrate = 6;
m_turnrate = newturnrate;
emit turnRateChanged();
}
double FlightData::airspeed() const{
return m_airspeed;
}
void FlightData::setAirspeed(double newairspeed){
if (std::isnan(newairspeed) || qFuzzyCompare(m_airspeed, newairspeed))
return;
if (newairspeed < 0)
newairspeed = 0;
else if (newairspeed > 9999)
newairspeed = 9999;
m_airspeed = newairspeed;
emit airspeedChanged();
}
double FlightData::pressure() const{
return m_pressure;
}
void FlightData::setPressure(double newpressure){
if (std::isnan(newpressure) || qFuzzyCompare(m_pressure, newpressure))
return;
if (newpressure < 0)
newpressure = 0;
else if (newpressure > 2000)
newpressure = 2000;
m_pressure = newpressure;
emit pressureChanged();
}
double FlightData::altitude() const{
return m_altitude;
}
void FlightData::setAltitude(double newaltitude){
if (std::isnan(newaltitude) || qFuzzyCompare(m_altitude, newaltitude))
return;
if (newaltitude < 0)
newaltitude = 0;
else if (newaltitude > 99999)
newaltitude = 99999;
m_altitude = newaltitude;
emit altitudeChanged();
}
double FlightData::climbRate() const{
return m_climbrate;
}
void FlightData::setClimbRate(double newclimbrate){
if (std::isnan(newclimbrate) || qFuzzyCompare(m_altitude, newclimbrate))
return;
if (newclimbrate > 20)
newclimbrate = 20;
else if (newclimbrate < -20)
newclimbrate = -20;
m_climbrate = newclimbrate;
emit climbRateChanged();
}
void FlightData::anim_Start(){
mTimer.start(100);
frame = 0;
}
void FlightData::anim_Stop(){
if(mTimer.isActive())
mTimer.stop();
frame = 0;
}
void FlightData::update(){
double newroll = 90.0 * sin(M_PI*frame*0.01);
double newpitch = 60.0 * sin(0.5*M_PI*frame*0.01);
double newheading = 180.0 * sin(M_PI*frame*0.01) ;
double newslipskid = 15.0 * sin(2*M_PI*frame*0.01);
double newturnrate = 6.0 * sin(1*M_PI*frame*0.01 + M_PI/2.0);
double newairspeed = 120 * sin(M_PI*frame*0.01) + 120.0;
double newmpressure = 1.75 * sin(M_PI*frame*0.01) + 29.75;
double newaltitude = 15000 * sin(0.5*M_PI*frame*0.01) + 15000;
double newclimbrate = 20.0 * sin(2*M_PI*frame*0.01) ;
frame++;
setRoll(newroll);
setPitch(newpitch);
setHeading(newheading);
setTurnRate(newturnrate);
setSlipSkid(newslipskid);
setAirspeed(newairspeed);
setPressure(newmpressure);
setAltitude(newaltitude);
setClimbRate(newclimbrate);
}
使用了一个定时器,定时触发更新飞行数据。
QML文件
加入访问C++对象的部分,这里访问C++对象的几个函数。
import "sixbasic"
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0
Window {
minimumWidth: 800
minimumHeight: 700
title: "Basic Six"
color: "#ffffff"
visible: true
id:root
GridLayout{
columns: 3
id:meter
width: parent.width*0.9
anchors
{
fill:parent
margins: 16
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
id: it_asi
AirspeedIndicator {
radius: 0.5 * Math.min(parent.width, parent.height)
id:myasi
airspeed: pfd.airspeed
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
AttitudeIndicator{
radius: 0.5 * Math.min(parent.width,parent.height)
id: myai
roll: pfd.roll
pitch: pfd.pitch
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
id:it_alt
Altimeter{
radius: 0.5 * Math.min(parent.width, parent.height)
id: myalt
altitude: pfd.altitude
pressure: pfd.pressure
}
}
Item{
Layout.fillWidth: true
Layout.fillHeight: true
TurnCoordinator {
radius: 0.5 * Math.min(parent.width, parent.height)
id: mytc
turnRate: pfd.turnRate
slipSkid: pfd.slipSkid
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
HeadingIndicator {
radius: 0.5 * Math.min(parent.width, parent.height)
id: myheading
heading: pfd.heading
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
VerticalSpeedIndicator {
radius: 0.5 * Math.min(parent.width, parent.height)
id: myvsi
climbRate: pfd.climbRate
}
}
// Slider{
// id: airspeed_ctl
// anchors.left: myasi.it_asi
// width: myasi.width
// from: 0
// to: 240
// stepSize: 0.1
Connections{
target:myai
onValueChanged:{
myai.airspeed = airspeed_ctl.value
}
// onValueChanged: {
// myasi.airspeed = value
// }
// }
// Slider{
// id: roll_ctl
// anchors.left: myai.left
// width: myai.width
// from: -180
// to: 180
// stepSize: 1
onValueChanged: {
myai.roll = value
}
// }
// Slider{
// id: pitch_ctl
// anchors.left: myalt.left
// width: myalt.width
// from: -30
// to: 30
// stepSize: 0.1
onValueChanged: {
myai.pitch = value
}
// }
// Slider{
// id: altitude_ctl
// anchors.left: myasi.it_asi
// width: myasi.width
// from: 0
// to: 10000
// stepSize: 1
onValueChanged: {
myalt.altitude = value
}
// }
// Slider{
// id: pressure_ctl
// anchors.left: myai.left
// width: myai.width
// from: 28
// to: 31.6
// stepSize: 0.1
onValueChanged: {
myalt.pressure = value
}
// }
// Slider{
// id: heading_ctl
// anchors.left: myalt.left
// width: myalt.width
// from: 0
// to: 360
// stepSize: 0.1
// onValueChanged: {
// myheading.heading = value
// }
// }
// Slider{
// id: turnrate_ctl
// anchors.left: myasi.it_asi
// width: myasi.width
// from: -6
// to: 6
// stepSize: 0.1
// onValueChanged: {
// mytc.turnRate = value
// }
// }
// Slider{
// id: slipskid_ctl
// anchors.left: myai.left
// width: myai.width
// from: -15
// to: 15
// stepSize: 0.1
// onValueChanged: {
// mytc.slipSkid = value
// }
// }
// Slider{
// id: verticalspd_ctl
// anchors.left: myalt.left
// width: myalt.width
// from: -20
// to: 20
// stepSize: 0.1
onValueChanged: {
myvsi.climbRate = value
}
// }
Button{
id:btn
text: qsTr("Sim_Start")
font{
pixelSize: 16
family: "Times"
bold: true
}
background:Rectangle{
id: btnbg
color: "green"
}
Layout.fillWidth: false
Layout.fillHeight: false
Layout.columnSpan: 3
Layout.rowSpan: 1
Layout.column:0
Layout.row:2
Layout.alignment: Qt.AlignHCenter
onClicked: {
if(btn.text == "Sim_Start")
{
pfd.anim_Start()
btn.text = "Sim_Stop"
btnbg.color = "orange"
}
else if(btn.text == "Sim_Stop")
{
pfd.anim_Stop()
btn.text = "Sim_Start"
btnbg.color = "green"
}
}
}
// Button{
// width: parent.width*0.4
anchors.left: start.left + 500
// visible: false
// }
// Button{
// text: qsTr("Anim_Stop")
// width: root.width*0.3
// Layout.columnSpan: 3
// Layout.column:1
// Layout.row:3
// onClicked: {
// pfd.anim_Stop()
// }
// }
}
}
控件布局还不理想,还需要研究优化😟
Main函数
将C++对象注册到QML即可。
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "flightdata.h"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
FlightData *pfd = new FlightData();
engine.rootContext()->setContextProperty("pfd", pfd);
engine.load(url);
return app.exec();
}