忙忙碌碌又是一年,算算本身畢業四年半,一直在如今這家公司作研發外加總經理助理,研發起初用的VB.NET,然後全面轉爲C#,最後又全面轉爲QT,都是因爲項目須要,算下來本身搞QT編程也已經四年了,2010年開始接觸QT並編寫一些公司須要的輔助工具,其實搞程序的,我感受絕大部分都是出於自己興趣愛好,而後鍥而不捨的鑽研,不斷成長和進步。html
項目需求:某區下面有幾百所學校,每一個學校都有若干臺NVR或者DVR,每臺NVR和DVR都掛接着N個IPC(攝像機)(包括網絡攝像機和模擬攝像機),如今須要對全部學校的監控進行查看以及回放和輪詢,可以對指定學校進行視頻監控,對全部學校的視重點部位視頻進行查看輪詢,可自定義輪詢時間等。sql
開發過程:本着儘可能追求簡潔的要求,最終編寫了如上圖的主界面。沒有采用QT自帶的界面,而是重寫了界面,自定義無邊框拖動,自由換膚,所有采用QSS控制,從官網http://qt-project.org/doc/qt-4.8/stylesheet-examples.html完全學習了下QSS的規則,整理了一套通用的換膚方案。數據庫
PS:這是6年前寫的項目,新版在這,不開源 http://www.javashuo.com/article/p-cwjvmulv-kn.html編程
整個系統在開始架構的時候,本人都是寫在草稿紙上的,包括佈局,功能點,須要注意的處理等方面,如今要從新一一仔細寫出來,還真不容易,這裏就說個大概,而後將其中的部分功能處理用代碼描述。ubuntu
功能點羅列:安全
1:只限定一個實例處理。網絡
視頻監管平臺是一個獨佔視頻通道資源的系統,不能運行多個實例在同一臺電腦上運行,因此在main函數中就限制了一個實例運行。架構
QSharedMemory mem(
"
VM
");
if (!mem.create(
1)) {
myHelper::ShowMessageBoxError(
"
程序已運行,軟件將自動關閉!
");
return
1;
}編程語言
其中VM爲自定義的名稱,return 1表示退出程序返回1給操做系統。ide
若是重複運行會彈出以下提示:
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();
}

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

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目錄下便可。
說明:公開的源碼去除了視頻處理部分及樣式部分,其他功能所有保留,並可完整編譯運行。歡迎提出建議共同窗習進步!