Qt编写安防视频监控系统56-数据库分页

一、前言

在视频监控系统中也需要对日志记录进行查询显示,有时候查询到的记录并不能一页显示完,最好的做成翻页显示,如果所有记录都在一页显示通过滚动条查看,不是很符合用户习惯,比如搜索引擎的记录也都是分页显示,一页显示十几条记录。数据库的分页算法,大部分人都会选择用select查询结果limit关键字来过滤结果,其实未必所有数据库都支持limit关键字,对应Qt应用程序最常用的sqlite+mysql数据库都是用limit关键字以外,比如sqlserver数据库采用offset 4 rows fetch next 5 rows only这种写法来分页,PostgreSQL和人大金仓数据库kingbase采用的是limit+offset两个关键字组合(为何国产的人大金仓数据库和开源的PostgreSQL机制一样?后面查阅了资料才发现这家伙就是PostgreSQL改的), 而oracle的分页就更加复杂了(暂时没有找到好办法,就这个数据库分页最不好弄)。

数据库分页查询还算是比较基础的东西,一般人都会,稍微难的是对应UI上面有个分页控件,可以单击页码直接跳转到对应页码的数据显示,也可以直接输入页码,还可以单击上一页、下一页、第一页、末一页几个按钮切换,这个切换算法就有点讲究了,本人研究了网上各种翻页展示控件,大致可以总结为以下几点:

(一)翻页功能总结

  • 到了首页要禁用上一页第一页按钮,到了末页要禁用下一页和末一页按钮。
  • 如果记录数不够到页码数,则对应按钮禁用,比如只有1页记录则默认的翻页切换和2/3/4等按钮处于禁用状态。
  • 页码按钮的页码显示动态改变,保持中间按钮页码处于当前页码。
  • 页码标识的动态改变,直到开始页码已经显示了1/2或者末尾已经到了总页码。
  • 页码切换控件可以居中或者左对齐右对齐显示。
  • 页码样式可以改变,方块形状、圆形、不同的颜色等。

根据以上归纳总结,特意将分页功能拆分成一个类,专门负责分页查询返回数据等,将分页按钮展示UI拆分成一个类,负责外观展示,这样更灵活,类似MVC模式,分页功能类查询后触发对应的信号通知UI改变页码,分页按钮切换后发出对应的信号通知分页功能类执行对应的操作,完美搭配干活不累。

(二)通用翻页类

  1. 可设置每页多少行记录,自动按照设定的值进行分页。
  2. 可设置要查询的表名、字段集合、条件语句、排序语句。
  3. 可设置第一页、上一页、下一页、末一页、翻页按钮。
  4. 可设置当前页、总页数、总记录数、每页记录数、查询用时标签页。
  5. 多线程查询总记录数,数据量巨大时候不会卡主界面。
  6. 建议条件字段用整型类型的主键,速度极快。
  7. 提供查询结果返回信号,包括当前页、总页数、总记录数、查询用时等信息。
  8. 可设置所有列或者某一列对齐样式例如居中或者右对齐。
  9. 可增加列用于标识该条记录,设定列的位置、标题、宽度。
  10. 提供函数直接执行第一页、上一页、下一页、末一页。
  11. 提供函数直接跳转到指定页。
  12. 根据是否第一页、末一页自动禁用对应的按钮。
  13. 本控件是翻页功能类,和翻页控件navpage完美搭配,形成超级牛逼的翻页控件。

(三)分页导航控件

  1. 可设置页码按钮的个数。
  2. 可设置字体大小。
  3. 可设置边框圆角角度、大小、颜色。
  4. 可设置正常状态背景颜色、文字颜色。
  5. 可识别悬停状态背景颜色、文字颜色。
  6. 可设置按下状态背景颜色、文字颜色。
  7. 可设置选中状态背景颜色、文字颜色。
  8. 可设置导航位置居中对齐、左对齐、右对齐。
  9. 可设置是否显示提示标签控件。
  10. 自动计算总页码数显示隐藏多余按钮。
  11. 自动计算切换页码导航。
  12. 和分页导航功能类无缝对接完美融合。

二、功能特点

(一)软件模块

  1. 视频监控模块,各种停靠小窗体子模块,包括设备列表、图文警情、窗口信息、云台控制、预置位、巡航设置、设备控制、悬浮地图、网页浏览等。
  2. 视频回放模块,包括本地回放、远程回放、设备播放、图片回放、视频上传等。
  3. 电子地图模块,包括图片地图、在线地图、离线地图、路径规划等。
  4. 日志查询模块,包括本地日志、设备日志等。
  5. 系统设置模块,包括系统设置(基本设置、视频参数、数据库设置、地图配置、串口配置等)、录像机管理、摄像机管理、轮询配置、用户管理等。

(二)基础功能

  1. 支持各种视频流(rtsp、rtmp、http等)、视频文件(mp4、rmvb、avi等)、本地USB摄像机播放。
  2. 支持多画面切换,包括1、4、6、8、9、13、16、25、36、64画面切换。
  3. 支持全屏切换,多种切换方式包括鼠标右键菜单、工具栏按钮、快捷键(alt+enter全屏,esc退出全屏)。
  4. 支持视频轮询,包括1、4、9、16画面轮询,可设置轮询分组(轮询预案)、轮询间隔、码流类型等。
  5. 支持onvif协议,包括设备搜索、云台控制、设备控制(图片参数、校对时间、系统重启,抓拍图片等)。
  6. 支持权限管理,不同的用户可以对应不同的模块权限,比如删除日志、关闭系统等。
  7. 数据库支持多种,包括sqlite、mysql、sqlserver、postgresql、oracle、人大金仓等。
  8. 本地USB摄像机支持设置分辨率、帧率等参数。
  9. 所有停靠模块都自动生成对应的菜单用来控制显示和隐藏,在标题栏右键可以弹出。
  10. 支持显示所有模块、隐藏所有模块、复位普通布局、复位全屏布局。
  11. 双击设备弹出实时预览视频,支持图片地图、在线地图、离线地图等。
  12. 摄像机节点拖曳到对应窗体播放视频,同时支持拖曳本地文件直接播放。
  13. 删除视频支持鼠标右键删除、悬浮条关闭删除、拖曳到视频监控面板外删除等多种方式。
  14. 图片地图上设备按钮可*拖动,自动保存位置信息。百度地图上可以鼠标单击获取经纬度信息,用来更新设备位置。
  15. 视频监控面板窗体中任意通道支持拖曳交换,瞬间响应。
  16. 封装了百度地图,视图切换,运动轨迹,设备点位,鼠标按下获取经纬度等。
  17. 双击节点、拖曳节点、拖曳窗体交换位置等操作,均自动更新保存最后的播放地址,下次软件打开自动应用。
  18. 右下角音量条控件,失去焦点自动隐藏,音量条带静音图标。
  19. 支持视频截图,可指定单个或者对所有通道截图,底部小工具栏也有截图按钮。
  20. 支持超时自动隐藏鼠标指针、自动全屏机制。
  21. 支持onvif云台控制,可上下左右移动云台摄像机,包括复位和焦距调整等。
  22. 支持任意onvif摄像机,包括但不限于海康、大华、宇视、天地伟业、华为等。
  23. 可保存视频,可选定时存储或者单文件存储,可选存储间隔时间。
  24. 可设置视频流通信方式tcp+udp,可设置视频解码是速度优先、质量优先、均衡等。
  25. 可设置软件中文名称、英文名称、LOGO图标等。
  26. 存储的视频文件支持导出到指定目录,支持批量上传到服务器。

(三)特色功能

  1. 主界面采用停靠窗体模式,各种组件以小模块的形式加入,可自定义任意模块加入。
  2. 停靠模块可拖动任意位置嵌入和悬浮,支持最大化全屏,支持多屏幕。
  3. 双重布局文件存储机制,正常模式、全屏模式都对应不同的布局方案,自动切换和保存,比如全屏模式可以突出几个模块透明显示在指定位置,更具科幻感现代化。
  4. 原创onvif协议机制,采用底层协议解析(udp广播搜索+http请求执行命令)更轻量易懂易学习拓展,不依赖任何第三方组件比如gsoap。
  5. 原创数据导入导出机制,跨平台不依赖任何组件,瞬间导出数据。
  6. 内置多个原创组件,宇宙超值超级牛逼,包括数据导入导出组件(导出到xls、pdf、打印)、数据库组件(数据库管理线程、自动清理数据线程、万能分页、数据请求等)、地图组件、视频监控组件、文件多线程收发组件、onvif通信组件、通用浏览器内核组件等。
  7. 自定义信息框+错误框+询问框+右下角提示框(包含多种格式)等。
  8. 精美换肤,高达17套皮肤样式随意更换,所有样式全部统一,包括菜单等。
  9. 视频控件悬浮条可以自行增加多个按钮,监控界面底部小工具栏也可自行增加按钮。
  10. 双击摄像机节点自动播放视频,双击节点自动依次添加视频,会自动跳到下一个,双击父节点自动添加该节点下的所有视频。可选主码流、子码流。
  11. 录像机管理、摄像机管理,可添加删除修改导入导出打印信息,立即应用新的设备信息生成树状列表,不需重启。
  12. 可选多种内核*切换,ffmpeg、vlc、mpv等,均可在pro中设置。推荐用ffmpeg,跨平台最多,默认提供好了linux和mac平台上编译好的库。
  13. 支持硬解码,可设置硬解码类型(qsv、dxva2、d3d11va等)。
  14. 默认采用opengl绘制视频,超低的CPU资源占用,支持yuyv和nv12两种格式绘制,很牛逼。
  15. 高度可定制化,用户可以很方便的在此基础上衍生自己的功能,比如增加自定义模块,增加运行模式、机器人监控、无人机监控、挖掘机监控等。
  16. 支持xp、win7、win10、linux、mac、各种国产系统(UOS、中标麒麟、银河麒麟等)、嵌入式linux等系统。
  17. 注释完整,项目结构清晰,超级详细完整的使用开发手册,精确到每个代码文件的功能说明,不断持续迭代版本。

三、体验地址

  1. 体验地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_system.zip。
  2. 国内站点:https://gitee.com/feiyangqingyun
  3. 国际站点:https://github.com/feiyangqingyun
  4. 个人主页:https://blog.csdn.net/feiyangqingyun
  5. 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
  6. 在线文档:https://feiyangqingyun.gitee.io/qwidgetdemo/video_system/

四、效果图

Qt编写安防视频监控系统56-数据库分页
Qt编写安防视频监控系统56-数据库分页

五、核心代码

void NavPage::initStyle()
{
    if (!this->showStyle) {
        return;
    }

    QStringList list;
    list << QString("*{font:%1px;}").arg(fontSize);
    list << QString("QLabel{qproperty-alignment:AlignCenter;}");
    list << QString("QPushButton{border-radius:%1px;border:%2px solid %3;}")
         .arg(borderRadius).arg(borderWidth).arg(borderColor.name());

    list << QString("QPushButton{background-color:%1;color:%2;}")
         .arg(normalBgColor.name()).arg(normalTextColor.name());
    list << QString("QPushButton:hover{background-color:%1;color:%2;}")
         .arg(hoverBgColor.name()).arg(hoverTextColor.name());
    list << QString("QPushButton:pressed{background-color:%1;color:%2;}")
         .arg(pressedBgColor.name()).arg(pressedTextColor.name());
    list << QString("QPushButton:checked{background-color:%1;color:%2;}")
         .arg(checkedBgColor.name()).arg(checkedTextColor.name());
    //禁用状态将颜色调淡
    list << QString("QPushButton:disabled{color:rgba(%1,%2,%3,100);}")
         .arg(normalTextColor.red()).arg(normalTextColor.green()).arg(normalTextColor.blue());
    this->setStyleSheet(list.join(""));
}

void NavPage::addBtnMove()
{
    qDeleteAll(btnMove);
    btnMove.clear();

    //先生成 第一页 上一页 下一页 末一页 四个按钮
    QList<QString> listName;
    listName << "pageFirst" << "pagePrevious" << "pageNext" << "pageLast";
    QList<int> listText;
    listText << 0xf049 << 0xf04a << 0xf04e << 0xf050;

    int count = listName.count();
    for (int i = 0; i < count; ++i) {
        QPushButton *btn = new QPushButton;
        btn->setFont(iconFont);
        btn->setMinimumWidth(minWidth);
        btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
        btn->setObjectName(listName.at(i));
        btn->setText((QChar)listText.at(i));
        btnMove << btn;
    }
}

void NavPage::addBtnPage()
{
    qDeleteAll(btnPage);
    btnPage.clear();

    //放入按钮分组自动形成选中排斥效果
    QButtonGroup *btnGroup = new QButtonGroup(this);
    //生成中间页码按钮
    for (int i = 1; i <= pageButtonCount; ++i) {
        QPushButton *btn = new QPushButton;
        connect(btn, SIGNAL(clicked(bool)), this, SLOT(btnClicked()));
        btn->setCheckable(true);
        btnGroup->addButton(btn);
        btn->setMinimumWidth(minWidth);
        btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
        btn->setObjectName(QString("page%1").arg(i));
        btn->setText(QString("%1").arg(i));
        btnPage << btn;
    }

    btnPage.first()->setChecked(true);
}

void NavPage::addBtnAll()
{
    //新建左右布局
    if (layout == 0) {
        layout = new QHBoxLayout;
        layout->setContentsMargins(6, 6, 6, 6);
        this->setLayout(layout);
    }

    //实例化左侧右侧弹簧
    if (spacerLeft == 0) {
        spacerLeft = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
    }
    if (spacerRight == 0) {
        spacerRight = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
    }

    //居中对齐或者右对齐则需要在左侧插入弹簧
    if (navPosition == NavPosition_Center || navPosition == NavPosition_Right) {
        layout->addItem(spacerLeft);
    }

    //添加翻页提示信息
    if (labInfo == 0) {
        labInfo = new QLabel;
        labInfo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
        layout->addWidget(labInfo);
        labInfo->setVisible(showLabInfo);
    }

    //添加 第一页 上一页
    layout->addWidget(btnMove.at(0));
    layout->addWidget(btnMove.at(1));

    //循环添加 中间页码
    foreach (QPushButton *btn, btnPage) {
        layout->addWidget(btn);
    }

    //添加 下一页 末一页
    layout->addWidget(btnMove.at(2));
    layout->addWidget(btnMove.at(3));

    //添加 页码微调框
    if (spinbox == 0) {
        spinbox = new QSpinBox;
        spinbox->setRange(1, 1);
        spinbox->setMinimumWidth(minWidth + 20);
        spinbox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);        
    }

    //添加 跳转页码按钮
    if (btnGoPage == 0) {
        btnGoPage = new QPushButton;
        connect(btnGoPage, SIGNAL(clicked(bool)), this, SLOT(btnClicked()));
        btnGoPage->setMinimumWidth(minWidth + 20);
        btnGoPage->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
        btnGoPage->setObjectName("pagego");
        btnGoPage->setText("跳转");
    }

    layout->addWidget(spinbox);
    spinbox->setVisible(showGoPage);
    layout->addWidget(btnGoPage);
    btnGoPage->setVisible(showGoPage);

    //居中对齐或者左对齐则需要在右侧插入弹簧
    if (navPosition == NavPosition_Center || navPosition == NavPosition_Left) {
        layout->addItem(spacerRight);
    }

    btnAll.clear();
    btnAll << btnMove << btnPage << btnGoPage;
}

void NavPage::btnClicked()
{
    //直接跳转到对应页码
    QPushButton *btn = (QPushButton *)sender();
    if (btn == btnGoPage) {
        emit selectPage(spinbox->value());
    } else {
        emit selectPage(btn->text().toInt());
    }
}

void NavPage::receivePage(quint32 pageCurrent, quint32 pageCount, quint32 resultCount, quint32 resultCurrent)
{
    //设置页码微调框范围
    spinbox->setRange(1, pageCount);

    //根据页码总数显示隐藏按钮,比如只有1页的时候隐藏其他几个按钮
    for (int i = 0; i < pageButtonCount; ++i) {
        btnPage.at(i)->setVisible(i < pageCount);
        btnPage.at(i)->setText(QString::number(i + 1));
    }

    //重新设置页码按钮的页码
    //计算出中间页码差值,起始页码和结束页码在差值基础上做加减
    int offset = pageButtonCount / 2;
    //偶数页个数需要-1更精确
    if (pageButtonCount % 2 == 0) {
        offset -= 1;
    }

    //总页数大于页码按钮个数才需要处理
    if (pageCount > pageButtonCount) {
        int pageMin = pageCurrent - offset;
        int pageMax = pageCurrent + offset;
        //最小页码不能小于1
        pageMin = pageMin < 1 ? 1 : pageMin;
        //最大页码不能超过总页码数
        pageMax = pageMax > pageCount ? pageCount : pageMax;
        //差值不等于按钮个数重新修改最小值 7-2=5 但是确是6个按钮所以需要-1
        while (pageMax - pageMin != (pageButtonCount - 1)) {
            if (pageMin == 1) {
                break;
            }
            pageMin--;
        }

        //qDebug() << pageButtonCount << offset << pageCurrent << pageMin << pageMax;
        int pageTemp = pageMin;
        for (int i = 0; i < pageButtonCount; ++i) {
            btnPage.at(i)->setText(QString::number(pageTemp));
            pageTemp++;
        }
    }

    //重新选中当前页码按钮
    foreach (QPushButton *btn, btnPage) {
        if (btn->text().toInt() == pageCurrent) {
            btn->setChecked(true);
            break;
        }
    }
}
上一篇:获取验证码


下一篇:jquery购物车