博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
年底奉献-QT编写视频监管平台(开源)
阅读量:7041 次
发布时间:2019-06-28

本文共 9887 字,大约阅读时间需要 32 分钟。

忙忙碌碌又是一年,算算自己毕业四年半,一直在现在这家公司做研发外加总经理助理,研发起初用的VB.NET,而后全面转为C#,最后又全面转为QT,都是由于项目需要,算下来自己搞QT编程也已经四年了,2010年开始接触QT并编写一些公司需要的辅助工具,其实搞程序的,我感觉绝大部分都是出于本身兴趣爱好,然后持之以恒的钻研,不断成长和进步。

项目需求:某区下面有几百所学校,每个学校都有若干台NVR或者DVR,每台NVRDVR都挂接着NIPC(摄像机)(包括网络摄像机和模拟摄像机),现在需要对所有学校的监控进行查看以及回放和轮询,能够对指定学校进行视频监控,对所有学校的视重点部位视频进行查看轮询,可自定义轮询时间等。

开发过程:本着尽量追求简洁的要求,最终编写了如上图的主界面。没有采用QT自带的界面,而是重写了界面,自定义无边框拖动,自由换肤,全部采用QSS控制,从官网http://qt-project.org/doc/qt-4.8/stylesheet-examples.html彻底学习了下QSS的规则,整理了一套通用的换肤方案。

整个系统在开始架构的时候,本人都是写在草稿纸上的,包括布局,功能点,需要注意的处理等方面,现在要重新一一仔细写出来,还真不容易,这里就说个大概,然后将其中的部分功能处理用代码描述。

功能点罗列:

1:只限定一个实例处理。

视频监管平台是一个独占视频通道资源的系统,不能运行多个实例在同一台电脑上运行,所以在main函数中就限制了一个实例运行。

QSharedMemory mem(
"
VM
");
    
if (!mem.create(
1)) {
        myHelper::ShowMessageBoxError(
"
程序已运行,软件将自动关闭!
");
        
return 
1;

    }

其中VM为自定义的名称,return 1表示退出程序返回1给操作系统。

如果重复运行会弹出如下提示:

 

2F1键进入全屏模式,Esc键退出全屏模式。

几乎所有的视频监控系统,主界面都支持全屏显示及esc退出全屏,在QT中我是这样实现的,重写了主界面的keyPressEvent事件,拦截按键消息,判断对应按键,调用全屏及普通模式的方法。

void frmMain::keyPressEvent(QKeyEvent *
event)
{
    
//
空格键进入全屏,esc键退出全屏
    
switch(
event->key()) {
    
case Qt::Key_F1:
        screen_full();
        
break;
    
case Qt::Key_Escape:
        screen_normal();
        
break;
    
default:
        QDialog::keyPressEvent(
event);
        
break;
    }
}
void frmMain::screen_full()
{    
    
this->setGeometry(qApp->desktop()->geometry());
    
this->layout()->setContentsMargins(
0
0
0
0);
    ui->widget_main->layout()->setContentsMargins(
0
0
0
0);
    ui->widget_title->setVisible(
false);
    ui->treeMain->setVisible(
false);
}
void frmMain::screen_normal()
{    
    
this->setGeometry(qApp->desktop()->availableGeometry());
    
this->layout()->setContentsMargins(
1
1
1
1);
    ui->widget_main->layout()->setContentsMargins(
5
5
5
5);
    ui->widget_title->setVisible(
true);
    ui->treeMain->setVisible(
true);
}

 3:支持QT4QT5各个版本编译运行。

 

QT5与QT4的区别还是让很多搞QT开发的同学着实生气了一把,好端端的把一些方法去除掉了,而且有些头文件重新移到了其他地方,为了兼容QT4与QT5,在项目中就需要增加很多对版本的判断了。

例如头文件的包含:

#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
#include <QtWidgets>
#endif

例如设置UTF-8编码:

static 
void SetUTF8Code() {
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
        QTextCodec *codec = QTextCodec::codecForName(
"
UTF-8
");
        QTextCodec::setCodecForLocale(codec);
        QTextCodec::setCodecForCStrings(codec);
        QTextCodec::setCodecForTr(codec);
#endif

} 

4:基本常用的数据库处理,添加删除修改操作,表格显示。

本人一直喜欢采用拼接sql字符串来执行SQL语句。觉得这样运行效率很高,而且这种方法通用任何编程语言。

void frmIPC::on_btnAdd_clicked()
{
    QString IPCID = ui->cboxIPCID->currentText();
    QString IPCName = ui->txtIPCName->text();
    QString NVRID = ui->cboxNVRID->currentText();
    QString NVRName = ui->cboxNVRName->currentText();
    QString IPCType = ui->cboxIPCType->currentText();
    QString IPCRtspAddrMain = ui->txtIPCRtspAddrMain->text();
    QString IPCRtspAddrSub = ui->txtIPCRtspAddrSub->text();
    QString IPCUserName = ui->txtIPCUserName->text();
    QString IPCUserPwd = ui->txtIPCUserPwd->text();
    QString IPCUse = ui->cboxIPCUse->currentText();
    
if (IPCName == 
"") {
        myHelper::ShowMessageBoxError(
"
名称不能为空,请重新填写!
");
        ui->txtIPCName->setFocus();
        
return;
    }
    
if (NVRName == 
"") {
        myHelper::ShowMessageBoxError(
"
NVR名称不能为空,请先添加好NVR!
");
        
return;
    }
    
if (IPCRtspAddrMain == 
"") {
        myHelper::ShowMessageBoxError(
"
主码流地址不能为空,请重新填写!
");
        ui->txtIPCRtspAddrMain->setFocus();
        
return;
    }
    
if (IPCRtspAddrSub == 
"") {
        myHelper::ShowMessageBoxError(
"
子码流地址不能为空,请重新填写!
");
        ui->txtIPCRtspAddrSub->setFocus();
        
return;
    }
    
//
检测编号是否唯一
    
if (IsExistIPCID(NVRID, IPCID)) {
        myHelper::ShowMessageBoxError(
"
编号已经存在,请重新选择!
");
        
return;
    }
    QSqlQuery query;
    QString sql = 
"
insert into [IPCInfo](
";
    sql += 
"
[IPCID],[IPCName],[NVRID],[NVRName],
";
    sql += 
"
[IPCType],[IPCRtspAddrMain],[IPCRtspAddrSub],
";
    sql += 
"
[IPCUserName],[IPCUserPwd],[IPCUse])
";
    sql += 
"
values('
";
    sql += IPCID + 
"
','
";
    sql += IPCName + 
"
','
";
    sql += NVRID + 
"
','
";
    sql += NVRName + 
"
','
";
    sql += IPCType + 
"
','
";
    sql += IPCRtspAddrMain + 
"
','
";
    sql += IPCRtspAddrSub + 
"
','
";
    sql += IPCUserName + 
"
','
";
    sql += IPCUserPwd + 
"
','
";
    sql += IPCUse + 
"
')
";
    query.exec(sql);
    LoadIPCInfo();
    ui->cboxIPCID->setCurrentIndex(ui->cboxIPCID->currentIndex() + 
1);
    ui->txtIPCName->setText(QString(
"
摄像机%1
").arg(ui->cboxIPCID->currentText()));
}
void frmIPC::on_btnDelete_clicked()
{
    
if (ui->tableMain->currentIndex().row() < 
0) {
        myHelper::ShowMessageBoxError(
"
请选择要删除的摄像机!
");
        
return;
    }
    QString tempIPCID = queryModule->record(
                            ui->tableMain->currentIndex().row())
                        .value(
0).toString();
    
if (myHelper::ShowMessageBoxQuesion(
"
确定要删除摄像机吗?
") == 
1) {
        QSqlQuery query;
        QString sql = 
"
delete from [IPCInfo] where [IPCID]='
" + tempIPCID + 
"
'
";
        query.exec(sql);
        myHelper::Sleep(
100);
        
//
同步删除轮询表中的摄像机信息
        sql = 
"
delete from [PollInfo] where [IPCID]='
" + tempIPCID + 
"
'
";
        query.exec(sql);
        myHelper::Sleep(
100);
        LoadIPCInfo();
    }
}
void frmIPC::on_btnUpdate_clicked()
{
    
if (ui->tableMain->currentIndex().row() < 
0) {
        myHelper::ShowMessageBoxError(
"
请选择要修改的摄像机!
");
        
return;
    }
    QString tempIPCID = queryModule->record(
                            ui->tableMain->currentIndex().row())
                        .value(
0).toString();
    QString IPCID = ui->cboxIPCID->currentText();
    QString IPCName = ui->txtIPCName->text();
    QString NVRID = ui->cboxNVRID->currentText();
    QString NVRName = ui->cboxNVRName->currentText();
    QString IPCType = ui->cboxIPCType->currentText();
    QString IPCRtspAddrMain = ui->txtIPCRtspAddrMain->text();
    QString IPCRtspAddrSub = ui->txtIPCRtspAddrSub->text();
    QString IPCUserName = ui->txtIPCUserName->text();
    QString IPCUserPwd = ui->txtIPCUserPwd->text();
    QString IPCUse = ui->cboxIPCUse->currentText();
    
if (IPCID != tempIPCID) {
        
//
检测编号是否和已经存在的除自己之外的编号相同
        
if (IsExistIPCID(NVRID, IPCID)) {
            myHelper::ShowMessageBoxError(
"
编号已经存在,请重新选择!
");
            
return;
        }
    }
    QSqlQuery query;
    QString sql = 
"
update [IPCInfo] set
";
    sql += 
"
 [IPCID]='
" + IPCID;
    sql += 
"
',[IPCName]='
" + IPCName;
    sql += 
"
',[NVRID]='
" + NVRID;
    sql += 
"
',[NVRName]='
" + NVRName;
    sql += 
"
',[IPCType]='
" + IPCType;
    sql += 
"
',[IPCRtspAddrMain]='
" + IPCRtspAddrMain;
    sql += 
"
',[IPCRtspAddrSub]='
" + IPCRtspAddrSub;
    sql += 
"
',[IPCUserName]='
" + IPCUserName;
    sql += 
"
',[IPCUserPwd]='
" + IPCUserPwd;
    sql += 
"
',[IPCUse]='
" + IPCUse;
    sql += 
"
' where [IPCID]='
" + tempIPCID + 
"
'
";
    query.exec(sql);
    myHelper::Sleep(
100);
    
//
同步修改轮询表的信息
    sql = 
"
update [PollInfo] set
";
    sql += 
"
 [IPCID]='
" + IPCID;
    sql += 
"
',[IPCName]='
" + IPCName;
    sql += 
"
',[NVRID]='
" + NVRID;
    sql += 
"
',[NVRName]='
" + NVRName;
    sql += 
"
',[IPCRtspAddrMain]='
" + IPCRtspAddrMain;
    sql += 
"
',[IPCRtspAddrSub]='
" + IPCRtspAddrSub;
    sql += 
"
' where [IPCID]='
" + tempIPCID + 
"
'
";
    query.exec(sql);
    myHelper::Sleep(
100);
    LoadIPCInfo();

} 

5QTreeViewQTableView数据加载和双击处理。

 

void frmPollConfig::LoadNVRIPC()
{
    ui->treeMain->clear();
    QSqlQuery queryNVR;
    QString sqlNVR = 
"
select [NVRID],[NVRName],[NVRIP] from [NVRInfo] where [NVRUse]='启用'
";
    queryNVR.exec(sqlNVR);
    
while (queryNVR.next()) {
        QString tempNVRID = queryNVR.value(
0).toString();
        QString tempNVRName = queryNVR.value(
1).toString();
        QString tempNVRIP = queryNVR.value(
2).toString();
        QTreeWidgetItem *itemNVR = 
new QTreeWidgetItem
        (ui->treeMain, QStringList(tempNVRName + 
"
[
" + tempNVRIP + 
"
]
"));
        itemNVR->setIcon(
0, QIcon(
"
:/image/nvr.png
"));
        
//
查询没有添加在轮询表中的摄像机信息
        QSqlQuery queryIPC;
        QString sqlIPC = 
"
select [IPCID],[IPCName],[IPCRtspAddrMain] from [IPCInfo]
";
        sqlIPC += 
"
 where [NVRID]='
" + tempNVRID;
        sqlIPC += 
"
' and [IPCUse]='启用'
";
        sqlIPC += 
"
 order by [IPCID] asc
";
        queryIPC.exec(sqlIPC);
        
while (queryIPC.next()) {
            QString tempIPCID = queryIPC.value(
0).toString();
            
//
如果该摄像机已经存在轮询表,则跳过
            
if (IsExistIPCID(tempIPCID)) {
                
continue;
            }
            QString tempIPCName = queryIPC.value(
1).toString();
            QString rtspAddr = queryIPC.value(
2).toString();
            QStringList temp = rtspAddr.split(
"
/
");
            QString ip = temp[
2].split(
"
:
")[
0];
            temp = QStringList(QString(tempIPCName + 
"
[
" + ip + 
"
](
" + tempIPCID + 
"
)
"));
            QTreeWidgetItem *itemIPC = 
new QTreeWidgetItem(itemNVR, temp);
            itemIPC->setIcon(
0, QIcon(
"
:/image/ipc_normal.png
"));
            itemNVR->addChild(itemIPC);
        }
    }
    ui->treeMain->expandAll();
}

 

616通道画面展示区域处理,自由切换1画面4画面9画面16画面。

void frmMain::show_video_4()
{
    removelayout();
    video_max = 
false;
    
int index = 
0;
    QAction *action = (QAction *)sender();
    QString name = action->text();
    
if (name == 
"
通道1-通道4
") {
        index = 
0;
        myApp::VideoType = 
"
1_4
";
    } 
else 
if (name == 
"
通道5-通道8
") {
        index = 
4;
        myApp::VideoType = 
"
5_8
";
    } 
else 
if (name == 
"
通道9-通道12
") {
        index = 
8;
        myApp::VideoType = 
"
9_12
";
    } 
else 
if (name == 
"
通道13-通道16
") {
        index = 
12;
        myApp::VideoType = 
"
13_16
";
    }
    change_video_4(index);
    myApp::WriteConfig();
}
void frmMain::change_video_4(
int index)
{
    
for (
int i = (index + 
0); i < (index + 
2); i++) {
        VideoLay[
0]->addWidget(VideoLab[i]);
        VideoLab[i]->setVisible(
true);
    }
    
for (
int i = (index + 
2); i < (index + 
4); i++) {
        VideoLay[
1]->addWidget(VideoLab[i]);
        VideoLab[i]->setVisible(
true);
    }
}

7:精美开关按钮。

现在流行APP,各种APP上面都带有很精美的开关,参考了360安全卫士以及金山毒霸的开关按钮,用QT也实现了一个,原理很简单,就是贴图。

#include 
"
switchbutton.h
"
/*
 说明:自定义开关按钮控件实现文件
 * 功能:用来控制配置文件的开关设置
 * 作者:刘典武  QQ:517216493
 * 时间:2013-12-19  检查:2014-1-10
 
*/
SwitchButton::SwitchButton(QWidget *parent): QPushButton(parent)
{
    setCursor(QCursor(Qt::PointingHandCursor));
    isCheck = 
false;
    styleOn = 
"
background-image: url(:/image/btncheckon.png); border: 0px;
";
    styleOff = 
"
background-image: url(:/image/btncheckoff.png); border: 0px;
";
    setFocusPolicy(Qt::NoFocus);
    setFixedSize(
87
28); 
//
不允许变化大小
    setStyleSheet(styleOff);
//
设置当前样式
    connect(
this, SIGNAL(clicked()), 
this, SLOT(ChangeOnOff()));
}
void SwitchButton::ChangeOnOff()
{
    
if (isCheck) {
        setStyleSheet(styleOff);
        isCheck = 
false;
    } 
else {
        setStyleSheet(styleOn);
        isCheck = 
true;
    }
}
//
设置当前选中状态
void SwitchButton::SetCheck(
bool isCheck)
{
    
if (
this->isCheck != isCheck) {
        
this->isCheck = !isCheck;
        ChangeOnOff();
    }
}

8:重写过的消息框,错误框,询问框及输入框。

本人不喜欢系统的MessageBox,用QDialog重新布局自定义了一个。只需一句话调用即可。

 

 

 

在win7下运行截图如下:

在XP下运行截图如下:

在ubuntu上运行截图:

 

可执行文件下载:http://pan.baidu.com/s/1hqxhtbA

源码下载:http://pan.baidu.com/s/1mgFWeDU

编译运行后如果提示缺少数据库。将源码下的file文件夹下的配置文件config.txt及VM.db数据库文件复制到bin目录下即可。 

说明:公开的源码去除了视频处理部分及样式部分,其余功能全部保留,并可完整编译运行。欢迎提出建议共同学习进步!

 

转载地址:http://hdxal.baihongyu.com/

你可能感兴趣的文章
MVC是什么
查看>>
关于 emotion 初步使用的笔记
查看>>
PHP 怎样在同一个域名下两个不同的项目做独立的登录机制?
查看>>
SpringCloud(第 005 篇)电影微服务,注册到 EurekaServer 中,通过 Http 协议访问用户微服务...
查看>>
k-邻近算法(kNN)
查看>>
gulp基础和常用插件介绍
查看>>
开发之路(设计模式六:命令模式上)
查看>>
JavaScript:并发模型与Event Loop
查看>>
CSS揭秘之《条纹背景》
查看>>
用Kettle从excel中将导入oracle数据库的简单方法
查看>>
【跨域】跨域的简易实现和测试
查看>>
获得字符串包含↵,渲染到页面不换行的解决办法
查看>>
北哥这篇文讲解yii2权限扩展(yii2-admin) - 下部
查看>>
微信web开发遇到的坑
查看>>
写了一个数字转成简 / 繁体汉字的助手函数
查看>>
vue配合iview/element等ui实现界面效果起步
查看>>
仿饿了么项目-vue的学习笔记总目录
查看>>
Angular 2.x+ 如何动态装载组件
查看>>
React中的setTimeout、setInterval的注意事项
查看>>
如何深入使用scss开发一个简单页面
查看>>