找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1871

积分

0

好友

259

主题
发表于 2025-12-31 09:53:35 | 查看: 22| 回复: 0

控件能够被拖曳和拉伸是基本前提,接下来的核心任务便是如何动态加载被选中控件的属性。Qt的属性机制异常强大,堪称“强大到爆”。如果你在编写自定义控件时,使用 Q_PROPERTY 宏仔细描述过属性,那么这些属性将能在整个Qt生态的各种应用场景中被轻松识别和加载,无论是Widget的属性设计还是QML的属性绑定,都极为方便。

观察Qt Designer的属性编辑器,其外观与表格控件颇为相似。若要自己从头实现这样一个组件,代价巨大。事实上,Qt官方在 qt-solutions 项目中提供了一个开源组件——qtpropertybrowser。网络上有很多关于它的使用教程和演示,非常详尽。笔者也对其部分代码进行了修改,以实现父类属性过滤和中文属性名称映射等功能。

功能概览

  1. 自动加载插件文件中的所有控件并生成列表,默认自带控件超过120个。
  2. 拖曳控件到画布即可自动生成对应实例,所见即所得。
  3. 右侧提供中文属性栏,修改属性可立即应用到选中的控件上,直观简洁,易于上手。
  4. 独创高效的属性栏文字翻译映射机制,方便扩展支持其他语言。
  5. 自动提取并显示控件的所有属性,包括枚举值下拉框等。
  6. 支持手动选择并导入外部插件文件。
  7. 可将当前画布的所有控件配置导出为XML文件。
  8. 可通过选择XML文件打开并自动加载已保存的控件布局。
  9. 支持通过滑动条、勾选模拟数据复选框、文本框输入三种方式生成测试数据并应用到所有控件。
  10. 控件支持八个方向拉伸调整大小,自适应任意分辨率,支持键盘方向键微调位置。
  11. 支持通过串口采集、网络采集、数据库采集三种方式设置数据。
  12. 代码精简,注释详尽,可作为组态软件的雏形,便于拓展更多功能。
  13. 纯Qt编写,支持任意Qt版本、任意编译器及任意操作系统。

界面效果

自定义控件属性设计器界面
图:属性设计器主界面,展示多种仪表盘控件及其属性设置面板。

核心代码解析

以下代码展示了如何通过Qt的元对象系统(Meta-Object System)动态加载和显示对象的属性,这是实现属性编辑器的关键。如果你对 C++ 的反射和元编程机制感兴趣,可以在云栈社区的C/C++板块找到更多深度讨论

void QtObjectController::setObject(QObject *object)
{
    // 如果设置的控件已是当前控件,则直接返回
    if (d_ptr->m_object == object) {
        return;
    }

    if (d_ptr->m_object) {
        d_ptr->saveExpandedState();
        QListIterator<QtProperty *> it(d_ptr->m_topLevelProperties);
        while (it.hasNext()) {
            d_ptr->m_browser->removeProperty(it.next());
        }
        d_ptr->m_topLevelProperties.clear();
    }

    d_ptr->m_object = object;

    if (!d_ptr->m_object) {
        return;
    }

    // 加载父类的属性
    d_ptr->addClassPropertiesParent(d_ptr->m_object->metaObject()->superClass());
    // 加载当前控件的属性
    d_ptr->addClassProperties(d_ptr->m_object->metaObject());
    // 恢复节点展开状态
    d_ptr->restoreExpandedState();
    // 折叠所有节点
    d_ptr->collapseAll();
}

void QtObjectControllerPrivate::addClassProperties(const QMetaObject *metaObject)
{
    if (!metaObject) {
        return;
    }

    QtProperty *classProperty = m_classToProperty.value(metaObject);
    if (!classProperty) {
        QString className = QLatin1String(metaObject->className());
        classProperty = m_manager->addProperty(QtVariantPropertyManager::groupTypeId(), className);
        m_classToProperty[metaObject] = classProperty;
        m_propertyToClass[classProperty] = metaObject;

        for (int idx = metaObject->propertyOffset(); idx < metaObject->propertyCount(); idx++) {
            QMetaProperty metaProperty = metaObject->property(idx);
            int type = metaProperty.userType();
            QtVariantProperty *subProperty = 0;

            // 将英文属性名映射为中文
            QString propertyName = metaProperty.name();
            propertyName = QtPropertyName::maps.value(propertyName, propertyName);

            if (!metaProperty.isReadable()) {
                subProperty = m_readOnlyManager->addProperty(QVariant::String, propertyName);
                subProperty->setValue(QLatin1String("< Non Readable >"));
            } else if (metaProperty.isEnumType()) {
                if (metaProperty.isFlagType()) {
                    subProperty = m_manager->addProperty(QtVariantPropertyManager::flagTypeId(), propertyName);
                    QMetaEnum metaEnum = metaProperty.enumerator();
                    QMap<int, bool> valueMap;
                    QStringList flagNames;
                    for (int i = 0; i < metaEnum.keyCount(); i++) {
                        int value = metaEnum.value(i);
                        if (!valueMap.contains(value) && isPowerOf2(value)) {
                            valueMap[value] = true;
                            flagNames.append(QLatin1String(metaEnum.key(i)));
                        }
                        subProperty->setAttribute(QLatin1String("flagNames"), flagNames);
                        subProperty->setValue(flagToInt(metaEnum, metaProperty.read(m_object).toInt()));
                    }
                } else {
                    subProperty = m_manager->addProperty(QtVariantPropertyManager::enumTypeId(), propertyName);
                    QMetaEnum metaEnum = metaProperty.enumerator();
                    QMap<int, bool> valueMap; // 避免显示具有相同值的多个枚举项
                    QStringList enumNames;
                    for (int i = 0; i < metaEnum.keyCount(); i++) {
                        int value = metaEnum.value(i);
                        if (!valueMap.contains(value)) {
                            valueMap[value] = true;
                            // 将枚举键名也强制转为中文
                            QString enumName = metaEnum.key(i);
                            enumName = QtPropertyName::maps.value(enumName, enumName);
                            enumNames.append(enumName);
                        }
                    }
                    subProperty->setAttribute(QLatin1String("enumNames"), enumNames);
                    subProperty->setValue(enumToInt(metaEnum, metaProperty.read(m_object).toInt()));
                }
            } else if (m_manager->isPropertyTypeSupported(type)) {
                if (!metaProperty.isWritable()) {
                    subProperty = m_readOnlyManager->addProperty(type, propertyName + QLatin1String(" (Non Writable)"));
                }
                if (!metaProperty.isDesignable()) {
                    subProperty = m_readOnlyManager->addProperty(type, propertyName + QLatin1String(" (Non Designable)"));
                } else {
                    subProperty = m_manager->addProperty(type, propertyName);
                }
                subProperty->setValue(metaProperty.read(m_object));
            } else {
                subProperty = m_readOnlyManager->addProperty(QVariant::String, propertyName);
                subProperty->setValue(QLatin1String("< Unknown Type >"));
                subProperty->setEnabled(false);
            }
            classProperty->addSubProperty(subProperty);
            m_propertyToIndex[subProperty] = idx;
            m_classToIndexToProperty[metaObject][idx] = subProperty;
        }
    } else {
        updateClassProperties(metaObject, false);
    }

    m_topLevelProperties.append(classProperty);
    m_browser->addProperty(classProperty);
}

void QtObjectControllerPrivate::addClassPropertiesParent(const QMetaObject *metaObject)
{
    if (!metaObject) {
        return;
    }

    // 存储需要过滤的父类属性名(例如某些通用属性可能不需要显示)
    QStringList keyName;
    keyName << "geometry";

    QtProperty *classProperty = m_classToProperty.value(metaObject);
    if (!classProperty) {
        QString className = QLatin1String(metaObject->className());
        classProperty = m_manager->addProperty(QtVariantPropertyManager::groupTypeId(), className);
        m_classToProperty[metaObject] = classProperty;
        m_propertyToClass[classProperty] = metaObject;

        for (int idx = metaObject->propertyOffset(); idx < metaObject->propertyCount(); idx++) {
            QMetaProperty metaProperty = metaObject->property(idx);
            int type = metaProperty.userType();
            QtVariantProperty *subProperty = 0;

            // 如果当前属性不在允许显示的列表中,则跳过
            QString propertyName = metaProperty.name();
            if (!keyName.contains(propertyName)) {
                continue;
            }

            propertyName = QtPropertyName::maps.value(propertyName, propertyName);

            // ... (属性创建逻辑与addClassProperties类似,此处省略以保持简洁)
            classProperty->addSubProperty(subProperty);
            m_propertyToIndex[subProperty] = idx;
            m_classToIndexToProperty[metaObject][idx] = subProperty;
        }
    } else {
        updateClassProperties(metaObject, false);
    }

    m_topLevelProperties.append(classProperty);
    m_browser->addProperty(classProperty);
}

控件库介绍

本设计器背后是一个丰富的自定义控件库,为可视化开发提供了强大支持。

  1. 种类丰富:包含超过150个精美控件,涵盖各种仪表盘、进度条、曲线图、标尺、温度计、导航栏等,数量远超Qwt等传统库。
  2. 独立低耦合:每个控件类独立封装,仅包含一个头文件和一个实现文件,不依赖其他控件代码。你可以轻松地将单个控件以源码形式集成到项目中,极大减少了代码量。
  3. 跨平台兼容:全部采用纯Qt(QWidget+QPainter)编写,支持Qt 4.6至Qt 5.12的所有版本,兼容MSVC、GCC、MinGW等编译器,可在Windows、Linux、macOS及嵌入式Linux上稳定运行,无乱码问题。
  4. 易于集成与学习:控件可直接集成到Qt Creator中,像使用内置控件一样拖拽使用。每个控件都有独立的演示程序(DEMO)和一个综合所有控件的集成DEMO。源代码包含详细中文注释,遵循统一规范,是学习 Qt自定义控件开发的优秀资料,欢迎到云栈社区交流相关经验
  5. 设计器支持:集成了本文所述的自定义控件属性设计器,支持拖拽设计和XML格式的导入导出,实现所见即所得。
  6. 灵活部署:所有控件最终可编译生成一个动态库文件(DLL或SO),方便在Qt Creator中直接使用。同时提供ActiveX控件版本,可直接在IE浏览器中运行。
  7. 持续更新:控件库会不定期增加新控件并完善现有功能,SDK持续更新。



上一篇:主流流媒体服务器对比:RTSP/RTMP推流与HLS/WebRTC拉流方案选型指南
下一篇:求职者薪资谈判要价18K过分吗?HR招聘策略引争议
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:32 , Processed in 0.253569 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表