QML中使用全局变量

全局变量,顾名思义就是在程序中到处都能使用的变量。这在一定程度上违背了“模块化设计”这个思想。在笔者刚接触编程的时候老师就说过全局变量有害,就跟goto一样;但在实际工程中它其实很有用,使用得当的话反而能让整个软件结构更清晰、紧凑。本文结合实际经验向大家介绍在QML程序中如何有效使用全局变量。

全局变量的作用

首先要说明的是,我们这里说的全局变量不是整数、浮点数这样的简单变量,而是复杂的类对象。那什么时候会用到全局变量呢?在笔者的实践中,一般下面几种情况会用到全局变量:

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

这几个特点加起来其实也就是Facebook所推崇的Flux设计模块,核心思想简单讲就是把程序的层级关系变得简单,单一驱动。如果你被综错复杂的模块化设计弄糊涂了,那可以试试Flux这种清晰明了的设计思路。

和C++中的使用全局变量相比,QML中使用起来更加方便,因为QML中有属性绑定,尤其是上面第三点,确实可以让我们的程序“智能化”很多。同时也要说明的是,这里说的“全局”并不是真的全局,而是指QML执行环境中的全局;C++中的全局变量不在本文讨论范围之内。

QML中定义全局变量

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

有两种方式:单独定义,或者批量定义,其中批量定义又可分为C++形式和QML形式。我们把这些方法都介绍下。

单独定义

该方法主要步骤是:

  1. 定义一个QObject的子类,设计好它的信号、槽还有属性;

  2. main函数里构建对象;

  3. QQmlEngine构建之后还未加载任何QML文件之前,将该对象设置为执行上下文的属性,并取一个合理的名字:

    engine->rootContext()->setContextProperty("$hub", cppBackend);
    

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

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

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,就像他们用第一种方法单独定义一样。所以如果需要的功能比较多,建议使用批量定义的方法,更方便快捷。

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

QML形式批量定义

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

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

  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!";
        }
    }
}

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • QML 性能上的注意事项和建议 赵者也[https://www.jianshu.com/u/7b2ff27d6fd...
    赵者也阅读 16,031评论 1 11
  • 1.OC里用到集合类是什么? 基本类型为:NSArray,NSSet以及NSDictionary 可变类型为:NS...
    轻皱眉头浅忧思阅读 1,374评论 0 3
  • 现在是晚上7点18分。孩子正在写作业,可是她太累了。趴在桌子上就睡着了。看见孩子睡得这么香甜,很心疼,我很...
    91b6f8355762阅读 225评论 1 1