需求

qt 中 propertyQ_PROPERTY 如何使用?

解决

qml

在 qml 中使用 property 来定义一个对象的属性。具体语法如下:

[default] [required] [readonly] property <propertyType> <propertyName>

特性

  • 类似于成员变量,不同的是可以初始化,并且没有public、private、 protected等限制。
  • 可以使用 onXXXChnaged 作为这个属性的信号处理函数.
    property string someText
    onSomeTextChanged: console.log("The someText will be: " + someText)
    
  • properName以一个小写字母开头,只能包括字母、数字和下划线。 propertyType可以是QML基本类型,enumeration以int来代替,也可以是QML对象类型,神奇的var类型是泛型的,支持任何类型的属性值.
    Item {
        property int theNumber
        property string theString
        property url theUrl
    
        property Item someItem
        property Rectangle someRectangle
    
        property var someNumber: 1.5
        property var someString: "abc"
        property var someBool: true
        property var someList: [1, 2, "three", "four"]
        property var someObject: Rectangle { width: 100; height: 100; color: "red" }
    }
    
  • 属性值可以被初始化,也可以使用JavaScript表达式来赋值,通过这两种方式赋值时,可以是一个静态值,也可以是一个与其它属性绑定的值。
    Rectangle {
        id: rootRect
    
        property color theColor: "green"
        property color previousColor: rootRect.color
        property color nextColor
    
        width: 100; height: 100
        color: "red"
    
        Component.onCompleted: {
        rootRect.nextColor = rootRect.color
        console.log(theColor, previousColor, nextColor, rootRect.color)
        }
    }
    
    QtObject {
        id: root
    
        property QtObject viewer: MainView{}
    
    }
    

分类

  • 自定义属性
    property <propertyType> <propertyName> [ : <value> ]
    
  • 列表属性, children属性就是一个列表属性,包含在一对方括号中,里面的元素必须是QML对象类型而不能是QML基本类型,并以逗号分隔。列表内的元素可通过数组下标[index]访问,元素个数由length属性提供。若列表内只有一个元素,方括号可省略。
    Item {
        children: [
        Text { text: "textOne" },
        Text { text: "textTwo" },
        Text { text: "textThree" }
        ]
    
        Component.onCompleted: {
        for(var i = 0; i < children.length; i++)
    	console.log("text of lable", i, ":", children[i].text)
        }
    }
    
  • 自定义列表属性
    Item {
        property list<Rectangle> siblingRects
        property list<Rectangle> childRects: [
        Rectangle { color: "red" },
        Rectangle { color: "green" },
        Rectangle { color: "blue"}
        ]
    
        Component.onCompleted: {
        for(var i = 0; i < childRects.length; i++)
    	console.log("color of lable", i, ":", childRects[i].color)
        }
    }
    
  • 分组属性, 在某些情况下,属性可以是由若干个子属性构成的一个逻辑组,我们可以用“.”符号或者分组符号对其进行赋值。
    Text {
        //dot notation
        font.pixelSize: 12
        font.bold: true
    }
    
    Text {
        //group notation
        font { pixelSize: 12; bold: true }
    }
    
  • 属性别名, 属性别名引用其它的属性,语法如下:
    [default] property alias <name>: <alias reference>
    
  • 默认属性, 任何基于Item的类型都有一个data列表属性,这个属性就是该类型的默认属性,保存了所有的孩子对象,其中可视的孩子对象又保存在了 children列表属性中,不可视的孩子对象保存在了resources列表属性中,在添加子对象时children、resources属性可写可不写,都会自动添加到对应的属性列表中。
  • 只读属性, 只读属性必须初始化,且不能修改,也不能是default属性和alias 属性.
    readonly property <propertyType> <propertyName> : <initialValue>
    
  • 附加属性, QML语言还包括了一些附加属性和信号处理器,由相应的附加类型提供,使用语法如下:
    <AttachingType>.<propertyName>
    <AttachingType>.on<SignalName>
    

cpp

QObject 及其子类中用 Q_PROPERTY 宏就可以使用属性系统的代码如下所示:

Q_PROPERTY(QCursor cursor  READ cursor  WRITE setCursor  RESET unsetCursor)

上面这一行代码就声明了一个 cursor 属性,指明了它是 QCursor 类型的,而且指明了需要用自己的 cursor() 函数来读取这个属性值,指明了用自己的 setCursor() 函数来修改属性值,还指明了用自己的 unsetCursor() 函数进行默认值设置。一行语句就把一个属性声明好了,代码还算是很简洁的。

常用的关键字

  • READ, WRITE, 后面连接具体的对于属性的读写函数
  • MEMBER, 申明属性是类的成员变量
  • RESET,
  • NOTIFY, 属性更新时发出的信号

读写

  • read, write 使用时,属性可以不是类的成员变量。只需要有相应的函数即可。

    class Widget : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(bool focus READ hasFocus WRITE setFocus NOTIFY focusChanged)
    public:
        Widget(QObject *parent = 0);
        ~Widget();
    
        bool hasFocus() const
        {
        return m_focus;
        }
    
        void setFocus(bool on)
        {
        m_focus = on;
        }
    signals:
        void focusChanged();
    
    private:
        bool m_focus;
    }
    
    Widget *w = new Widget;
    w->setFocus(true);
    
  • member 使用时,必须是类的成员变量,可以不需要有相应的函数。

    Class Widget : public QObject
    {
        Q_PROPERTY(bool focus MEMBER m_focus)
        Q_OBJECT
    private:
        bool m_focus;
    }
    
    Widget *w = new Widget;
    w->property("focus");
    w->setProperty("focus", true);
    

关联信号

除了 NOTIFY 之外还有 RESET、REVISION、DESIGNABLE、SCRIPTABLE、STORED、 USER、CONSTANT、FINAL。这些关键字的含义及用法参考官方文档 The Property System。

Class Widget : public QObject
{
    Q_PROPERTY(bool focus MEMBER m_focus NOTIFY focusChanged)
    Q_OBJECT
public:
    bool hasFocus() const;
    void setFocus(bool on);
signals:
    void focusChanged();
private:
    bool m_focus;
}

获取属性名称和值

MEMBER 是需要知道类中有这个属性值的,那么如果不知道类中有什么属性值时,可以通过 Qt 元对象系统中的 QMetaObject, QMetaProperties 来获取属性和值。


Widget *w = new Widget;

const QMetaObject *metaobject = w->metaObject();
int count = metaobject->propertyCount();

for (int i = 0; i < count; ++i) {
    QMetaProperty metaproperty = metaobject->property(i);
    const char *name = metaproperty.name();
    QVariant value = w->property(name);
    ...
}

运行中临时添加属性

  • QObject::setProperty() 函数也可以用于在运行期添加新的属性。如果对象中已经存在该属性,则新添加的属性值会更改原有值并返回true;如果对象中不存在该属性,那么会自动添加到QObject中,注意了,此时返回值仍有可能是 false。所以根据返回值是不能确定属性值是否被设置成功。
  • 在已知类中存在某属性的情况下,可以根据返回值判断是否设置成功。如果添加新的属性值,就不能用返回值判断是否设置成功。
  • 因为是在程序运行过程中新增的属性,所以这个属性可以理解为是“临时的”。它们只会加到 QObject 实例中,不会加到最为核心的 QMetaObject 实例中。就好比一个公司已经发展起来了,后来新入职的员工就不是核心人员。
  • 那么要想删除这个“临时的”属性,只需要掉用 QObject::setProperty() 函数将空的 QVariant 值传进去即可。

自定义属性类型

自定义的属性类型,需要用 Q_DECLARE_METATYPE 宏进行注册,这样就可以存储在QVariant中了。

给类添加额外的属性信息

除了正规常用的属性外,我们还可以用 Q_CLASSINFO 宏给类添加额外的属性信息,语法就是“键值-对”形式。例如: Q_CLASSINFO("Version", "3.0.0"), 那么在程序运行的过程中,随时都可以调用 QMetaObject::classInfo() 函数来获取这些额外属性信息。

参考