参考文档:https://doc.qt.io/qt-6/qtqml-tutorials-extending-qml-example.html#chapter-2-connecting-to-c-methods-and-signals
回顾
上一章,介绍了如何从C++中导出新类型给QML使用!实现步骤:
- 定义C++类型,此类要求继承自QObject类,并使用Q_OBJECT宏,用Q_PROPERTY声明QML属性
- 设置qmake : CONFIG += qmltypes QML_IMPORT_NAME = Charts ...
- 在QML中导入与使用 Charts
import Charts 1.0
...
PieChart {
id: aPieChart
anchors.centerIn: parent
width: 100; height: 100
name: "A simple pie chart"
color: "red"
}
...
因为第2步中要设置qmake,所以中途去做了个支线任务--使用qmake: https://blog.51cto.com/u_12072082/5616418
连接到C++的函数与信号
实现目标
- 调用函数
clearChart()
删除图形,发送chartCleared
信号 - 在qml中可以访问
clearChart
函数,并接收到 chartCleared
信号
import Charts 1.0
import QtQuick 2.0
Item {
width: 300; height: 200
PieChart {
id: aPieChart
anchors.centerIn: parent
width: 100; height: 100
color: "red"
onChartCleared: console.log("The chart has been cleared")
}
MouseArea {
anchors.fill: parent
onClicked: aPieChart.clearChart()
}
Text {
anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
text: "Click anywhere to clear the chart"
}
}
实现
在C++类中添加clearChart函数和 chartCleared
信号
class PieChart : public QQuickPaintedItem
{
...
public:
...
Q_INVOKABLE void clearChart();
signals:
void chartCleared();
...
};
Q_INVOKABLE的使用使得cleararchart()方法可用于Qt元对象系统,反过来,也可用于QML。
另外你也可以声明其为槽函数,因为槽函数在QML中也可以被调用。
实现clearChart函数:
void PieChart::clearChart()
{
setColor(QColor(Qt::transparent));
update();
emit chartCleared();
}
信号函数是不需要实现的...
现在可以在QML代码调用clearChart函数,处理chartCleared
信号了
添加属性绑定
import Charts 1.0
import QtQuick 2.0
Item {
width: 300; height: 200
Row {
anchors.centerIn: parent
spacing: 20
PieChart {
id: chartA
width: 100; height: 100
color: "red"
}
PieChart {
id: chartB
width: 100; height: 100
color: chartA.color
}
}
MouseArea {
anchors.fill: parent
onClicked: { chartA.color = "blue" }
}
Text {
anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
text: "Click anywhere to change the chart color"
}
}
第26行,点击之后,希望可以把颜色改成蓝色...
现在的属性是这样的:
class PieChart : public QQuickPaintedItem
{
//![0]
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QColor color READ color WRITE setColor)
QML_ELEMENT
...
}
要改成这样:
class PieChart : public QQuickPaintedItem
{
...
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
...
signals:
void colorChanged();
...
};
- color 属性添加了 NOTIFY 选项
- 声明信号 colorChanged
void PieChart::setColor(const QColor &color)
{
if (color != m_color) {
m_color = color;
update(); // repaint with the new color
emit colorChanged();
}
}
检查color != m_color之后再发出信号这很重要,这确保了信号不会不必要地发出,也防止了在其他类型响应值变化时发生循环。
绑定对QML来说非常重要,所以如果属性有写操作,NOTIFY 必须加上。
使用自定义的属性类型
可以导出一个int类型:
// C++
class PieChart : public QQuickPaintedItem
{
Q_PROPERTY(int chartId READ chartId WRITE setChartId NOTIFY chartIdChanged)
...
public:
void setChartId(int chartId);
int chartId() const;
...
signals:
void chartIdChanged();
};
// QML
PieChart {
...
chartId: 100
}
除了int外,还可导出处理类型的属性,如QColor,QSize...都支持自动转换, Data Type Conversion Between QML and C++
使用自定义类型 PieSlice:
import Charts 1.0
import QtQuick 2.0
Item {
width: 300; height: 200
PieChart {
id: chart
anchors.centerIn: parent
width: 100; height: 100
pieSlice: PieSlice {
anchors.fill: parent
color: "red"
}
}
Component.onCompleted: console.log("The pie is colored " + chart.pieSlice.color)
}
如果是自定类型,不支持默认的QML的默认转换所以我们需要把这个类型注册到QML引擎上。
PieSlice是一个C++中定义的类型,和PieChart一样,PieSlice继承自QQuickPaintedItem并且用Q_PROPERTY声明属性
class PieSlice : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor)
QML_ELEMENT
public:
PieSlice(QQuickItem *parent = nullptr);
QColor color() const;
void setColor(const QColor &color);
void paint(QPainter *painter) override;
private:
QColor m_color;
};
// 重载函数的实现
void PieSlice::paint(QPainter *painter)
{
QPen pen(m_color, 2);
painter->setPen(pen);
painter->setRenderHints(QPainter::Antialiasing, true);
painter->drawPie(boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16);
}
PieChart中把pPieSlice作为PieChart的属性暴露给QML
class PieSlice;
//![0]
class PieChart : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(PieSlice* pieSlice READ pieSlice WRITE setPieSlice)
//![0]
Q_PROPERTY(QString name READ name WRITE setName)
Q_MOC_INCLUDE("pieslice.h")
QML_ELEMENT
//![1]
public:
//![1]
PieChart(QQuickItem *parent = nullptr);
QString name() const;
void setName(const QString &name);
//![2]
PieSlice *pieSlice() const;
void setPieSlice(PieSlice *pieSlice);
//![2]
private:
QString m_name;
PieSlice *m_pieSlice;
//![3]
};
//![3]
//![0]
void PieChart::setPieSlice(PieSlice *pieSlice)
{
m_pieSlice = pieSlice;
pieSlice->setParentItem(this);
}
//![0]
- color 属性被替换成了PieSlice* pieSlice
- paint函数没了
- 第38行pieSlice->setParentItem(this);很重要。
qmake设置
CONFIG += qmltypes
QML_IMPORT_NAME = Charts
QML_IMPORT_MAJOR_VERSION = 1
和上一小节一样,没有变化...
在同一个工程中,qmake导出qml类型的配置只需要写一次,同一个工程中导出的类型都在同一个命名空间下。
使用列表属性
import Charts 1.0
import QtQuick 2.0
Item {
width: 300; height: 200
PieChart {
anchors.centerIn: parent
width: 100; height: 100
slices: [
PieSlice {
anchors.fill: parent
color: "red"
fromAngle: 0; angleSpan: 110
},
PieSlice {
anchors.fill: parent
color: "black"
fromAngle: 110; angleSpan: 50
},
PieSlice {
anchors.fill: parent
color: "blue"
fromAngle: 160; angleSpan: 100
}
]
}
}
class PieChart : public QQuickItem
{
Q_OBJECT
// 只读的列表,虽然是只读的,但可以执行添等操作
Q_PROPERTY(QQmlListProperty<PieSlice> slices READ slices)
...
public:
...
QQmlListProperty<PieSlice> slices();
private:
static void append_slice(QQmlListProperty<PieSlice> *list, PieSlice *slice);
QString m_name;
QList<PieSlice *> m_slices;
};
QQmlListProperty<PieSlice> PieChart::slices()
{
return QQmlListProperty<PieSlice>(this, nullptr, &PieChart::append_slice, nullptr,
nullptr, nullptr, nullptr, nullptr);
}
void PieChart::append_slice(QQmlListProperty<PieSlice> *list, PieSlice *slice)
{
PieChart *chart = qobject_cast<PieChart *>(list->object);
if (chart) {
slice->setParentItem(chart);
chart->m_slices.append(slice);
}
}
QQmlListProperty 虽然是只读的,但是它可以修改(添加元素等)。
关于QQmlListProperty
上例中还有很多地方没说清楚,可以参考另外一个例子:https://doc.qt.io/qt-6/qtqml-referenceexamples-properties-example.html
构造函数的第一个参数赋值给了object,第二个参数给了data,它们都是公有属性可以直接访问(这个文档上没有或都是我没找到,是从源码中得到的信息)。
构造时要提供5个操作函数:
- append
- count
- at
- clear
- replace
- removeLast
这些操作函数可以传null,几个重载都是根据特定情况给某个操作函数赋值为null,比如:
QQmlListProperty(QObject *o, void *d, CountFunction c, AtFunction a)
: object(o), data(d), count(c), at(a)
{}
这个列表它就是只读的...
还有一个特殊的构造函数
QQmlListProperty(QObject *o, QList<T *> *list)
: object(o), data(list), append(qlist_append), count(qlist_count), at(qlist_at),
clear(qlist_clear), replace(qlist_replace), removeLast(qlist_removeLast)
{}
内部提供了默认的操作函数集
插件化
PieChart必须插件化之后才能给不同的项目使用。
又要去做支线任务了....
- 创建插化的步骤: https://doc.qt.io/qt-6/qtqml-modules-cppplugins.html
- 如何创建Qt插件: https://doc.qt.io/qt-6/plugins-howto.html