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

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

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

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

功能点罗列:

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

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



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

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

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

监控手机APP开发方案 视频监控app开发_c#

 

2:F1键进入全屏模式,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:支持QT4到QT5各个版本编译运行。

 

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();    
}

5:QTreeView及QTableView数据加载和双击处理。

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();    
}

监控手机APP开发方案 视频监控app开发_c/c++_02

6:16通道画面展示区域处理,自由切换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); 
   
    }    
}


监控手机APP开发方案 视频监控app开发_监控手机APP开发方案_03

7:精美开关按钮。

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


"    switchbutton.h 
   " 
   

/*     说明:自定义开关按钮控件实现文件
 * 功能:用来控制


   
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重新布局自定义了一个。只需一句话调用即可。

监控手机APP开发方案 视频监控app开发_sql_04

 

监控手机APP开发方案 视频监控app开发_监控手机APP开发方案_05

 

监控手机APP开发方案 视频监控app开发_数据库_06

 

在win7下运行截图如下:

监控手机APP开发方案 视频监控app开发_c/c++_07

在XP下运行截图如下:

监控手机APP开发方案 视频监控app开发_sql_08

在ubuntu上运行截图: 

监控手机APP开发方案 视频监控app开发_c#_09

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

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

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