找回密码
 立即注册
收起左侧

全网原创/纯QWidget编写可视化大屏电子看板系统/一直被模...

0
回复
128
查看
[复制链接]
累计签到:7 天
连续签到:1 天
来源: 2025-5-6 10:40:22 显示全部楼层 |阅读模式

## 一、前言说明
可视化的大屏系统基本上是网页BS的天下,为何还要用可执行文件CS的方式写一个呢,主要还是从性能方面考虑,尤其是现在考虑到国产芯片和系统的兴起,目前上面的配置,不足以带动强悍的谷歌浏览器,在运行一些可视化效果方面,还是非常吃力的,比如在同样的硬件上,对比开16路视频监控画面,BS方式拉垮的掉渣,而CS的方式性能稳得一逼,这就是目前为何CS还有一定市场的重要原因,在一些高性能场景,还得是CS架构牛逼。

自从写完这个可视化大屏电子看板系统后,发过一系列的文章,介绍当中的技术细节,后面陆陆续续就在网上看到不少人跟着这些思路和细节,也用Qt写自己的大屏系统,总体来说还是模仿的有模有样,但是整体的完整性和体验效果还是差了一大截,大部分都停留在练手阶段,其实很多人做项目,往往只开了个头,然后就烂尾了,写代码做项目都是需要持之以恒的耐心,不断持续的迭代优化,才能出好用的作品。

## 二、效果图


## 三、相关代码
```cpp
#include "frmmain.h"
#include "ui_frmmain.h"
#include "qthelper.h"
#include "appinit.h"
#include "appstyle.h"
#include "customtitlebar.h"

#include "frmmodule1.h"
#include "frmmodule2.h"
#include "frmmodule3.h"
#include "frmmodule4.h"
#include "frmmodule5.h"
#include "frmmodule6.h"
#include "frmmodule7.h"
#include "frmmodule8.h"
#include "frmmodule9.h"
#include "frmmodulecenter.h"
#include "frmmodulevideo.h"

frmMain::frmMain(QWidget *parent) : QMainWindow(parent), ui(new Ui::frmMain)
{
    ui->setupUi(this);
    this->initForm();
    this->initWidget();
    this->addWidget();
}

frmMain::~frmMain()
{
    delete ui;
}

void frmMain::closeEvent(QCloseEvent *)
{
    closeAll();
}

void frmMain::showEvent(QShowEvent *)
{
    //首次显示的时候加载布局
    static bool isLoad = false;
    if (!isLoad) {
        isLoad = true;
        //先设置下默认尺寸
        this->initSize();
        this->changeLayout(AppConfig:ayoutName);
    }
}

void frmMain::initForm()
{
    //找到当前用户对应的布局和风格
    int index = UserHelper::UserInfo_UserName.indexOf(UserHelper::CurrentUserName);
    if (index >= 0) {
        AppConfig:ayoutName = UserHelper::UserInfo_Permission6.at(index);
        QString themeName = UserHelper::UserInfo_Permission7.at(index);
        if (AppConfig::ThemeName != themeName) {
            AppConfig::ThemeName = themeName;
            AppStyle::initTheme();
            AppStyle::initStyle();
        }
    }

    //设置无边框窗体
    //QtHelper::setFramelessForm(this);
    this->setProperty("canMove", false);

    //全屏和QWebEngineView控件一起会产生右键菜单无法弹出的bug(需要上移一个像素)
    if (AppConfig::FullScreen) {
        QRect rect = QtHelper::getScreenRect(false);
        rect.setY(-1);
        this->setGeometry(rect);
    } else {
        QRect rect = QtHelper::getScreenRect(true);
        this->setGeometry(rect);
    }
}

void frmMain::initSize()
{
    //数量不符合不用处理
    if (dockWidths.count() == 0 || dockHeights.count() == 0) {
        return;
    }
    if (dockWidths.count() != dockHeights.count()) {
        return;
    }
    if (dockWidths.count() != dockWidgets.count()) {
        return;
    }

    //根据初始的宽高设置停靠窗体位置
#if (QT_VERSION >= QT_VERSION_CHECK(5,6,0))
    this->resizeDocks(dockWidgets, dockWidths, Qt::Horizontal);
    this->resizeDocks(dockWidgets, dockHeights, Qt::Vertical);
#endif
}

void frmMain::initWidget()
{
    //实例化停靠窗体
    frmModule1 *module1 = new frmModule1;
    frmModule2 *module2 = new frmModule2;
    frmModule3 *module3 = new frmModule3;
    frmModule4 *module4 = new frmModule4;
    frmModule5 *module5 = new frmModule5;
    frmModule6 *module6 = new frmModule6;
    frmModule7 *module7 = new frmModule7;
    frmModule8 *module8 = new frmModule8;
    frmModule9 *module9 = new frmModule9;
    frmModuleCenter *moduleCenter = new frmModuleCenter;
    frmModuleVideo *moduleVideo = new frmModuleVideo;

    //设置视频窗体媒体地址
    moduleVideo->setMediaUrl(AppConfig::MediaUrl);
    connect(AppEvent::Instance(), SIGNAL(changeMediaUrl(QString)), moduleVideo, SLOT(setMediaUrl(QString)));

    //启动采集
    module1->start(AppConfig::IntervalModule1);
    module2->start(AppConfig::IntervalModule2);
    module3->start(AppConfig::IntervalModule3);
    module4->start(AppConfig::IntervalModule4);
    module5->start(AppConfig::IntervalModule5);
    module6->start(AppConfig::IntervalModule6);
    module7->start(AppConfig::IntervalModule7);
    module8->start(AppConfig::IntervalModule8);

    //清空列表
    this->dockWidgets.clear();
    this->dockWidths.clear();
    this->dockHeights.clear();

    //停靠窗体默认尺寸 绝大部分模块都按照这个尺寸来
    int width = 600;
    int height = 400;

    //实例化停靠窗体 可以自行更改标题
    newWidget(module1, "年度产量汇总", width, height);
    newWidget(module2, "当月计划达成率", 400, 500);
    newWidget(module3, "设备监控", 400, 500);
    newWidget(module4, "模具进度", width, height);
    newWidget(module5, "负荷分布", width, height);
    newWidget(module6, "送检一次合格率", width, height);
    newWidget(module7, "品质管理", width, height);
    newWidget(module8, "物料管理", width, height);
    newWidget(module9, "备用模块", width, height);
    newWidget(moduleVideo, "视频监控", width, height);

    //关联信号槽用于布局切换+保存布局+退出系统
    connect(moduleCenter, SIGNAL(changeLayout(QString)), this, SLOT(changeLayout(QString)));
    connect(moduleCenter, SIGNAL(saveLayout(QString, int)), this, SLOT(saveLayout(QString, int)));
    connect(moduleCenter, SIGNAL(closeAll()), this, SLOT(closeAll()));

    //设置中心窗体
    this->setCentralWidget(moduleCenter);

    //设置允许各种嵌套比如上下排列左右排列非常灵活
    //此设置会和下面的 setDockOptions 中的参数覆盖所以要注意顺序
    //this->setDockNestingEnabled(true);

    //设置停靠参数,不允许重叠,只允许拖动和嵌套
    this->setDockOptions(AnimatedDocks | AllowNestedDocks);

    //将底部左侧作为左侧区域,底部右侧作为右侧区域,否则底部区域会填充拉伸
    if (AppConfig::CutLeftBottom) {
        this->setCorner(Qt::BottomLeftCorner, Qt:eftDockWidgetArea);
    }
    if (AppConfig::CutRightBottom) {
        this->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
    }
}

QDockWidget *frmMain::newWidget(QWidget *widget, const QString &title, int width, int height)
{
    //设置拉伸策略
    widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);

    //实例化停靠窗体
    QString objName = widget->objectName();
    QDockWidget *dockWidget = new QDockWidget;
    dockWidget->setObjectName("dockWidget_" + objName);
    dockWidget->setWindowTitle(title);
    dockWidget->setWidget(widget);

    //还可以设置透明度/在悬浮的时候会起作用
    //dockWidget->setWindowOpacity(0.5);
    //取消悬停模块右键菜单
    //dockWidget->setContextMenuPolicy(Qt:reventContextMenu);
    //取消分隔条右键菜单
    //this->setContextMenuPolicy(Qt::NoContextMenu);

    //自定义停靠窗体标题栏
    CustomTitleBar *titleBar = new CustomTitleBar;
    titleBar->setObjectName("titleBar_" + objName);
    titleBar->setFull(true);
    titleBar->setTitle(title);
    dockWidget->setTitleBarWidget(titleBar);

    //如果设置了不可移动则只允许关闭
    if (!AppConfig::MoveEnable) {
        dockWidget->setFeatures(QDockWidget:ockWidgetClosable);
    }

    //设置允许的停靠区域
    //dockWidget->setAllowedAreas(Qt::AllDockWidgetAreas);
    dockWidget->setAllowedAreas(Qt:eftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea);
    //将窗体指针+宽度+高度加入到队列
    dockWidgets << dockWidget;
    dockWidths << width;
    dockHeights << height;
    return dockWidget;
}

void frmMain::addWidget()
{
    //下面的添加顺序和位置都是首次起作用,在没有调用 restoreState 方法的时候
    //调用 restoreState 方法后按照配置文件的存储状态进行布局

    //添加左侧窗体
    addWidget(0, 0);
    addWidget(7, 0);
    addWidget(3, 0);

    //添加右侧窗体
    addWidget(6, 1);
    addWidget(4, 1);
    addWidget(5, 1);

    //添加底部窗体
    addWidget(2, 3);
    addWidget(1, 3);

    //添加其他模块
    //如果实例化过的停靠窗体没有加入到布局会提示
    //QMainWidget::resizeDocks: one QDockWidget is not part of the layout
    addWidget(8, 1);
    addWidget(9, 1);

    //设置模块悬浮
    dockWidgets.at(8)->resize(400, 300);
    dockWidgets.at(8)->move(600, 280);
    dockWidgets.at(8)->setFloating(true);
    dockWidgets.at(9)->resize(400, 300);
    dockWidgets.at(9)->move(600, 280);
    dockWidgets.at(9)->setFloating(true);

    //还可以自行添加自己设计的模块界面

#if 0
    //如下可以再添加一个视频窗体,同一个类多个窗体需要 setObjectName 才能让自动保存布局区分
    frmModuleVideo *moduleVideo2 = new frmModuleVideo;
    moduleVideo2->setObjectName("moduleVideo2");
    moduleVideo2->setUrl("f:/mp4/1.mp4");
    QDockWidget *dockWidget = newWidget(moduleVideo2, "视频监控2");
    addWidget(dockWidget, 1);
#endif

    //如果是首次生成布局则需要全部可见
    QString file = QString("%1/%2.ini").arg(AppConfig:ayoutPath).arg(AppConfig:ayoutName);
    if (!QFile(file).exists()) {
        foreach (QDockWidget *dockWidget, dockWidgets) {
            dockWidget->show();
        }
    }
}

void frmMain::addWidget(int index, int position)
{
    //指定窗体索引添加到布局指定位置
    if (index < dockWidgets.count()) {
        addWidget(dockWidgets.at(index), position);
    }
}

void frmMain::addWidget(QDockWidget *widget, int position)
{
    //设置停靠位置
    Qt:ockWidgetArea area = Qt::AllDockWidgetAreas;
    if (position == 0) {
        area = Qt:eftDockWidgetArea;
    } else if (position == 1) {
        area = Qt::RightDockWidgetArea;
    } else if (position == 2) {
        area = Qt::TopDockWidgetArea;
    } else if (position == 3) {
        area = Qt::BottomDockWidgetArea;
    }

    this->addDockWidget(area, widget);
}

void frmMain::changeLayout(const QString &layoutName)
{
    AppConfig:ayoutName = layoutName;
    AppConfig::writeConfig();
    this->initLayout(AppConfig:ayoutName);
}

void frmMain::initLayout(const QString &layoutName)
{
    //调用Qt自己的函数 restoreState 来加载布局
    QString file = QString("%1/%2.ini").arg(AppConfig:ayoutPath).arg(layoutName);
    QByteArray data = AppConfig::readLayout(file);
    this->restoreState(data);
}

//type: 0-新建布局 1-恢复布局 2-保存布局 3-布局另存
void frmMain::saveLayout(const QString &layoutName, int type)
{
    //如果为空则表示是恢复布局
    if (type == 0) {
        AppConfig::LayoutName = layoutName;
        this->changeLayout(AppConfig::LayoutName);
        return;
    } else if (type == 1) {
        this->changeLayout(AppConfig::LayoutName);
        return;
    }

    //调用Qt自己的函数 saveState 来保存布局
    QString file = QString("%1/%2.ini").arg(AppConfig::LayoutPath).arg(layoutName);
    QByteArray data = this->saveState();
    AppConfig::writeLayout(file, data);
    AppConfig::LayoutName = layoutName;
    AppConfig::writeConfig();
}

void frmMain::closeAll()
{
    //关闭前先保存当前布局 然后延时一下再彻底退出
    saveLayout(AppConfig::LayoutName, 2);
    QtHelper::sleep(10);
    exit(0);
}
```

## 四、相关地址
1. 国内站点:[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 文件地址:[https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 文件名:bin_bigscreen。

## 五、功能特点
1. 采用分层设计,整体总共分3J界面,一级界面是整体布局,二级界面是单个功能模块,3J界面是单个控件。
2. 子控件包括饼图、圆环图、曲线图、柱状图、柱状分组图、横向柱状图、横向柱状分组图、合格率控件、百分比控件、进度控件、设备状态面板、表格数据、地图控件、视频控件等。
3. 二级界面可以自由拖动悬浮,支持最小化隐藏、最大化关闭、响应双击自定义标题栏。
4. 数据源支持模拟数据(默认)、数据库采集、串口通信(需定制)、网络通信(需定制)、网络请求等,可自由设定每个子界面的采集间隔即数据刷新频率。
5. 采用纯QWidget编写,亲测Qt4.6到Qt6.2任意版本,理论上支持后续其他Qt版本。
6. 超强跨平台,亲测windows、linux、mac、国产uos、国产银河麒麟kylin等系统,效果完美,同时还支持嵌入式linux比如树莓派、香橙派、全志、imx6等。
7. 同时集成了自定义控件、qchart饼图、echart地图等功能。
8. 内置多套配色风格样式(紫色、蓝色、深蓝、黑色),默认紫色,自适应任意分辨率。
9. 可设置系统标题、目标分辨率、布局方案,启动立即应用。
10. 可设置主背景颜色、面板颜色、十字线游标颜色等各种颜色。
11. 可设置多条曲线不同颜色,没有设置颜色的情况下内置多套精美颜色随机应用。
12. 可设置标题栏背景颜色、文字颜色。
13. 可设置曲线图表背景颜色、文字颜色、网格颜色。
14. 可设置正常颜色、警戒颜色、报警颜色、禁用颜色、百分比进度颜色。
15. 可分别设置各种字体大小,比如全局字体、软件名称、标题栏、子标题栏、加粗标签等。
16. 可设置标题栏高度、表头高度、行高度。
17. 曲线支持游标、定位线、悬停高亮数据点、悬停显示值。
18. 柱状图支持顶部(可设置顶端、上部、中间、底部)显示数据,全部自适应计算位置。
19. 支持平滑曲线,内置多种平滑曲线算法,还支持面积图平滑。
20. 面积图填充颜色可选多种规则比如单色透明度填充、透明度渐变填充等。
21. 数据库支持sqlite、mysql、postgresql、oracle、国产人大金仓等数据库。
22. 主界面直接鼠标右键切换布局、配色方案、关闭开启某个二级窗体。
23. 自动记忆所有子窗口的大小和位置,下次启动立即应用。
24. 动态加载布局方案菜单,可以动态新建布局、恢复布局、保存布局、另存布局等,用户可以制造任意布局。
25. 二级窗体,双击从主窗体分离出来浮动,可以自由调整大小。再次双击标题栏最大化,再次双击还原。
26. 子模块也可以全屏显示作为一个大屏,这样就可以一个大屏拓展出多个子大屏,放大查看子模块的数据详情,适用多屏展示。
27. 每个模块都可以自定义采集速度,如果是数据库采集会自动排队处理,后期还可以拓展每个子模块都独立的数据库采集。
28. 提供系统设置模块进行整体的配置参数设置,效果立即应用。
29. 提供精美炫酷的大屏地图模块,包括静态图片、闪烁效果、迁徙效果、世界地图、区域地图等,可指定点的经纬度坐标,识别单击响应,可以做地图跳转等,每个点都可以不同的颜色和提示信息。
30. 除了提供大屏系统外,还将每个模块都做了独立的模块示例界面,每个模块都可以独立学习使用,里面用到的控件也单独做了控件示例界面,方便学习每个控件如何使用。
31. 非常详细的开发和使用手册,其中包括数据库说明、模块对照图、控件对照图、项目结构、代码说明(精确到每个类)、演示demo、使用方法等。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了