1 简介

全局变量在软件配置,样式切换,UI布局,属性同步等用处很大。

2 全局变量应用场景

  • 资源共享、重用
    整个应用程序相关的设置,比如说程序的版本、风格(theme)、字体资源等,这些数据适合放入一个全局变量,从而可以在整个程序的任何地方反复使用。
  • 数据传递
    全局变量在代码里都能访问到,相当于一块共享的内存空间,所以可以在不同的地方传递数据。但如果是多线程环境下的话,需要考虑好互斥,读写锁等。
  • 事件驱动
    我们可以将该变量申明为QObject子类,然后定义好信号与槽,然后在程序需要的地方连接这些信号或者调用槽函数,这样我们的程序就通过这个全局变量连接起来了。这就相当于有了一个核心驱动,比如只要一个信号发出来,程序中所有连接的槽函数就都会被调用,而无需关心是否漏掉了一个。

3 QML中定义全局变量

QML是需要QML引擎(即QQmlEngine)来解释执行的,所以QML中的全局变量本质是QML执行上下文(QQmlContext)的属性。定义QML全局变量也就是把我们的对象设置为QML执行上下文的属性。
单独定义 和 批量定义 (C++形式、QML形式)

3.1 单独定义

  • 定义一个QObject的子类,设计好它的信号、槽还有属性;
  • 在main函数里构建对象;
  • 在QQmlEngine构建之后还未加载任何QML文件之前,将该对象设置为执行上下文的属性,并取一个合理的名字:
engine->rootContext()->setContextProperty("$hub", cppBackend);

这样$hub就成为了QML中的全局变量,你可以直接使用它内部的各种元数据(信号、槽、属性、枚举类型等等)。

这里我们约定,用$作为全局变量的开头字母,这个在JavaScript和QML中是合法的,便于我们区分普通局部变量和全局变量。

3.2 C++形式批量定义

如果我们的程序比较复杂,把功能都放在一个全局变量里不合适,我们可以将它们拆开来,用不同的C++类来实现,然后定义一个总的C++类,将这些功能类作为这个总类的属性,主要步骤是:

1根据功能定义不同类,例如:
程序设置类:

class Settings : public QObject{
    Q_OBJECT
public:
    Q_PROPERTY(QString appName MEMBER m_appName)
private:
    QString m_appName = "MyApp";
}

和网络类:

class Networks : public QObject{
    Q_OBJECT
}

2 定义一个总的类:

class GlobalObject : public QObject{
    Q_OBJECT
public:
    Q_PROPERTY(Settings* settings MEMBER m_settings)
    Q_PROPERTY(Networks* networks MEMBER m_networks)
private:
    Settings* m_settings;
    Networks* m_networks;
}

3 在main函数中创建总类的对象:

auto globalObject = new GlobalObject();

4 在QQmlEngine构建之后还未加载任何QML文件之前,将该对象设置为执行上下文对象:
engine->rootContext()->setContextObject(globalObject);
QML中约定,contextObject的所有属性都自动变为contextProperty,就像他们用第一种方法单独定义一样。所以如果需要的功能比较多,建议使用批量定义的方法,更方便快捷。

需要注意,如果一个程序中同时使用了这两种方法定义全局变量而且有变量重名了,那么以单独定义的优先。

3.3 QML形式批量定义

可以用QML文件来代替上面的C++总类。

在定义好Settings和Networks之后,接下去的步骤改为:

1 将这些C++类注册到QML中:

qmlRegisterType<Settings>("MyCppBackend", 1, 0, "Settings");
qmlRegisterType<Networks>("MyCppBackend", 1, 0, "Networks");

2 然后新建一个QML文件:

//globalobject.qml
import QtQuick 2.7
import MyCppBackend 1.0

QtObject {
    id: root
    property var $settings: Settings{}
    property var $networks: Networks{}
}

3 将该QML文件作为QML引擎加载的第一个文件:

engine->load(QUrl("qrc:///globalobject.qml")); 

QML引擎约定,加载的第一个QML文件就是contextObject,所以和C++定义类似,它的属性也就成了contextProperty。

和C++批量定义相比,QML批量定义有如下优势:

变量名前面可以加$,从而方便区分全局变量和局部变量,这个在C++定义属性的时候是不允许的;

如果某个全局变量(一般是QML对象)构造很慢,可以通过QML中的Loader来很方便异步构造,从而加速程序启动:

property var loader: Loader{
    asynchronous: true
    source: "qrc:/UI/Main.qml"
    onLoaded: {
        // 当加载完毕会进入这里
    }
}

QML全局变量的中枢作用
最后我们结合批量定义中的例子来看下QML中全局变量起的数据、消息中枢作用。这个主要是利用了QML的属性绑定特性(Property Binding)。

假如说我们有两个QML文件:

//View1
import QtQuick 2.7
Item{
    id: root
    Text{
        text: $settings.appName
    }
}

和:

//View2
import QtQuick 2.7
import QtQuick.Controls 2.2
Rectangle{
    id: root
    TextField{
        text: $settings.appName
    }
    Button{
        text: "Click!"
        onClick:{
            $settings.appName = "New Name!";
        }
    }
}

View1和View2中的text都和 s e t t i n g s 中 的 a p p N a m e 这 个 属 性 做 了 绑 定 。 当 我 点 击 V i e w 2 中 的 按 钮 , settings中的appName这个属性做了绑定。当我点击View2中的按钮, settingsappNameView2settings.appName被修改,所有绑定的属性也就会自动更新,不会遗忘。由于$settings是全局变量,这种用法可以深入到任意复杂、任意层级的界面中,非常方便。

参考

1、QML中使用全局变量
2、Flux

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐