在使用控件布局的時(shí)候,用QFrame做容器,用的最多的就是垂直布局和水平布局,再加上垂直和水平的Spacer控件,基本上可以搞定所有布局效果,這里要注意的是,在QtCreator中為控件設(shè)置布局的Layout屬性是有默認(rèn)值的,包括Margin和Spacing,導(dǎo)致布局控件的子控件之間有空隙,所以這里最好是手工都設(shè)置為0,按照上面的布局,我們最外層是一個(gè)QMainWindow,在其頭文件和源文件中重寫鼠標(biāo)的按下、移動(dòng)、釋放按鈕如下:
1
2
3
4
5
//mainwindow.h 頭文件
protected:
? ? virtual void mousePressEvent(QMouseEvent *event);
? ? virtual void mouseMoveEvent(QMouseEvent *event);
? ? virtual void mouseReleaseEvent(QMouseEvent *event);
重新實(shí)現(xiàn)鼠標(biāo)的這3個(gè)事件代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//重寫鼠標(biāo)按下事件
void MainWindow::mousePressEvent(QMouseEvent *event)
{
? ? mMoveing = true;
? ? //記錄下鼠標(biāo)相對(duì)于窗口的位置
? ? //event->globalPos()鼠標(biāo)按下時(shí),鼠標(biāo)相對(duì)于整個(gè)屏幕位置
? ? //pos() this->pos()鼠標(biāo)按下時(shí),窗口相對(duì)于整個(gè)屏幕位置
? ? mMovePosition = event->globalPos() - pos();
? ? QWidget::mousePressEvent(event);
}
//重寫鼠標(biāo)移動(dòng)事件
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
? ? //(event->buttons() & Qt::LeftButton)按下是左鍵
? ? //鼠標(biāo)移動(dòng)事件需要移動(dòng)窗口,窗口移動(dòng)到哪里呢?就是要獲取鼠標(biāo)移動(dòng)中,窗口在整個(gè)屏幕的坐標(biāo),然后move到這個(gè)坐標(biāo),怎么獲取坐標(biāo)?
? ? //通過事件event->globalPos()知道鼠標(biāo)坐標(biāo),鼠標(biāo)坐標(biāo)減去鼠標(biāo)相對(duì)于窗口位置,就是窗口在整個(gè)屏幕的坐標(biāo)
? ? if (mMoveing && (event->buttons() & Qt::LeftButton)
? ? ? ? && (event->globalPos()-mMovePosition).manhattanLength() > QApplication::startDragDistance())
? ? {
? ? ? ? move(event->globalPos()-mMovePosition);
? ? ? ? mMovePosition = event->globalPos() - pos();
? ? }
? ? QWidget::mouseMoveEvent(event);
}
//鼠標(biāo)釋放
void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
? ? mMoveing = false;
? ? QWidget::mouseReleaseEvent(event);
}
這樣就實(shí)現(xiàn)了無邊框窗體的拖動(dòng)效果,除此之外我們要重寫關(guān)閉按鈕的功能,在關(guān)閉窗體的時(shí)候不去close,這個(gè)代碼就不展示了,百度一下就有,下面我們看看如何自定義QListWidget
回到頂部
自定義QListWidget項(xiàng)目
默認(rèn)的QListWidget項(xiàng)目很簡(jiǎn)單,每一個(gè)QListWidget中的項(xiàng)目是一個(gè)QListWidgetItem對(duì)象,該對(duì)象提供 setText() 為該項(xiàng)目設(shè)置文字,setIcon() 用于為項(xiàng)目設(shè)置圖標(biāo)。如果僅僅是這樣的話,這是完全滿足不了需求的,我們需要設(shè)置圖片,并且需要在圖片上顯示圖片的描述,日期,地址等信息。我們可以在QtCreator中創(chuàng)建一個(gè)ui文件(包括頭文件和對(duì)應(yīng)的cpp文件),在這個(gè)ui文件中,我們以QWidget作為最頂層的容器(不像窗體用的QMainWindow),他就像我們?nèi)魏巫远x的ui控件一樣,我們可以利用QtCreator的布局功能做任何復(fù)雜的布局,最后這個(gè)ui文件對(duì)應(yīng)的頭文件與cpp文件實(shí)際上就是一個(gè)C++類,我們?cè)谛枰牡胤绞褂眠@個(gè)類就可以了。我們?yōu)檫@個(gè)QListWidget的每一項(xiàng)定義的ui界面布局如下:
可以看到我們這個(gè)ui布局中黑色實(shí)線的矩形框一共有4個(gè)都是用QFrame,最外面的QFrame0是整個(gè)ui的容器他是被一個(gè)QWidget包裹,每一個(gè)項(xiàng)目的圖片被設(shè)置為QFrame0的背景圖片,該項(xiàng)目的區(qū)域被分為上下兩部分,上面是QFrame1,下面是QFrame3,其中QFrame1里面嵌套了一個(gè)小的QFrame2,這個(gè)QFrame2中有2個(gè)水平布局的按鈕,一個(gè)是預(yù)覽(軟件中的放大鏡),一個(gè)是設(shè)置當(dāng)前圖片為桌面壁紙(軟件中的顯示器圖標(biāo))。默認(rèn)情況下這兩個(gè)按鈕不顯示,當(dāng)鼠標(biāo)移動(dòng)到QFrame0上的時(shí)候,整個(gè)QFrame1將以動(dòng)畫的形式從上切入,并且有一個(gè)透明度的變化。如何將我們自定義的ui類,設(shè)置為該QListWidget的項(xiàng)呢,可以用下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void MainWindow::initListWigdet(){
? ? QFile file2(":/qss/listwidget.scrollbar3.qss");
? ? file2.open(QFile::ReadOnly);
? ? ui->listWidget->verticalScrollBar()->setStyleSheet(file2.readAll());
? ? file2.close();
? ? //初始化QListWidget
? ? //ui->listWidget->setIconSize(QSize(300,225));
? ? ui->listWidget->setResizeMode(QListView::Adjust);
? ? ui->listWidget->setViewMode(QListView::IconMode);
? ? ui->listWidget->setMovement(QListView::Static);
? ? ui->listWidget->setSpacing(10);
? ? ui->listWidget->horizontalScrollBar()->setDisabled(true);
? ? ui->listWidget->verticalScrollBar()->setDisabled(true);
? ? // 創(chuàng)建單元項(xiàng)
? ? // 這里創(chuàng)建的Item使用的背景圖是樣式表中的默認(rèn)背景圖
? ? for (int i = 0; i<6; ++i)
? ? {
? ? ? ? QListWidgetItem *item = new QListWidgetItem;
? ? ? ? ImageInfoItem2 *widget = new ImageInfoItem2;
? ? ? ? item->setSizeHint(QSize(288,180));
? ? ? ? ui->listWidget->addItem(item);
? ? ? ? ui->listWidget->setSizeIncrement(150,190);
? ? ? ? ui->listWidget->setItemWidget(item,widget);//最重要的是這句將Item設(shè)置為一個(gè)Widget,而這個(gè)Widget就是自定義的ui
? ? }
? ? //給item綁定真實(shí)數(shù)據(jù)
? ? updateListWidget(1); // page 從1開始
}
我們首先為這個(gè)QListWidget加載了一個(gè)樣式表,然后設(shè)置了一些參數(shù),最后為其設(shè)置了6個(gè)item項(xiàng)目,每個(gè)item項(xiàng)目是一個(gè)Widget對(duì)象,也就是我們自定義的ui類(ImageInfoItem2),其實(shí)際上是繼承自QWidget類的。最重要的是使用QListWidget::setItemWidget()成員函數(shù)設(shè)置item為一個(gè)QWidget,設(shè)置好item對(duì)象之后,最后有一個(gè)函數(shù)去設(shè)置每個(gè)item的數(shù)據(jù),例如背景圖片、圖片描述、日期、地址信息等。當(dāng)我們?nèi)ケ闅v該QListWidget的每一個(gè)item并將item轉(zhuǎn)換為當(dāng)初設(shè)置的QWidget對(duì)象,調(diào)用該ui對(duì)象的成員方法來更新其上的數(shù)據(jù)即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//將圖片列表更新為第page頁的圖片數(shù)據(jù)
void MainWindow::updateListWidget(int page){
? ? //獲取第page頁面的數(shù)據(jù)當(dāng)在imageInfoList中
? ? QList<BingImageDataInfo> imageInfoList = DataManager::GetImageInfoList(page);
? ? //先初始化listWidget列表的每一項(xiàng)為空數(shù)據(jù)
? ? for (int i = 0; i<ui->listWidget->count(); ++i)
? ? {
? ? ? ? QListWidgetItem *item = ui->listWidget->item(i);
? ? ? ? QWidget * widget =? ui->listWidget->itemWidget(item);
? ? ? ? ImageInfoItem2* imageInfoItem = dynamic_cast<ImageInfoItem2*>(widget);
? ? ? ? if(imageInfoItem!=NULL){
? ? ? ? ? ? BingImageDataInfo info;//空數(shù)據(jù)
? ? ? ? ? ? imageInfoItem->updateImageInfo(info);
? ? ? ? }
? ? }
? ? //根據(jù)實(shí)際上得到的imageInfoList初始化listWidget
? ? for (int i = 0; i<imageInfoList.size(); ++i)
? ? {
? ? ? ? //qDebug()<<imageInfoList[i].Url<<endl;
? ? ? ? QListWidgetItem *item = ui->listWidget->item(i);
? ? ? ? QWidget * widget =? ui->listWidget->itemWidget(item);
? ? ? ? ImageInfoItem2* imageInfoItem = dynamic_cast<ImageInfoItem2*>(widget);
? ? ? ? if(imageInfoItem!=NULL){
? ? ? ? ? ? imageInfoItem->updateImageInfo(imageInfoList[i]);
? ? ? ? }
? ? }
}
上面的代碼中,先用空數(shù)據(jù)初始化了一遍item,然后用真實(shí)數(shù)據(jù)填充,因?yàn)檫@里按分頁獲取的數(shù)據(jù)也許個(gè)數(shù)并不滿足一頁的數(shù)據(jù)項(xiàng)6個(gè)(比如最后一頁也許只有5個(gè)),這里先通過QListWidget::itemWidget()得到一個(gè)綁定在該item上的QWidget對(duì)象,然后將其轉(zhuǎn)換成我們真實(shí)的 ImageInfoItem2 對(duì)象(就是那個(gè)ui類對(duì)象),并通過該對(duì)象的函數(shù)去更新item的數(shù)據(jù)。
回到頂部
QListWidget滾動(dòng)條樣式表設(shè)置
本例中我們實(shí)際上并沒有允許滾動(dòng)條出現(xiàn),不過在最開始的時(shí)候確實(shí)考慮過使用滾動(dòng)條,也就是讓每頁顯示不止6個(gè)圖片,但是發(fā)現(xiàn)對(duì)于這個(gè)軟件的這種布局使用滾動(dòng)條比較奇怪,實(shí)際上滾動(dòng)條通過QSS設(shè)置出來的效果還是很好的,在這里將QSS放出來給需要的人。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* QSS中不能用雙斜杠的注釋這會(huì)導(dǎo)致qss無效 */
QScrollBar:vertical {
? border: 0px;
? background: #202020;
? width: 8px; /*設(shè)置一個(gè)非0的寬度讓滾動(dòng)條顯示*/
? margin: 0;
? padding:0;
}
/*滑塊的樣式*/
QScrollBar::handle:vertical {
? border: 0; /*設(shè)置border-radius屬性并不需要border屬性有值*/
? background: rgba(70,70,70,85%);
? min-height: 20px;
? border-radius:4px; /*坑:這個(gè)圓角要注意,不能超過寬度的一半,否則沒有圓角效果*/
}
/*鼠標(biāo)放上滑塊的樣式,顏色透明度變一下*/
QScrollBar::handle:vertical:hover {
? border: 0;
? background: rgba(70,70,70,100%);
? min-height: 20px;
? border-radius:4px;
}
QScrollBar::add-line:vertical,QScrollBar::sub-line:vertical {
? border: 0;
? background: #202020;
? height: 20px;
? subcontrol-position: bottom;
? subcontrol-origin: margin;
}
/*這兩個(gè)屬性必須都要設(shè)置否則背景不會(huì)繼承基礎(chǔ)設(shè)置中的背景*/
QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical
{
? ? background:#202020;
}
其顯示的滾動(dòng)條效果如下:
這里在對(duì)QScrollBar的滾動(dòng)條的QSS屬性加以說明,以便使用:
為方便,我這里是自己在草稿紙上畫的,沒有用作圖工具,就將就著看一下吧。
回到頂部
分頁效果
分頁做起來很簡(jiǎn)單,也是在QtCreator中自定義了一個(gè)ui界面類,將這個(gè)ui整體當(dāng)作一個(gè)分頁控件來用,在類里面提供成員函數(shù)來設(shè)置各個(gè)按鈕的樣式,這個(gè)ui界面里面就是一些分頁按鈕,上一頁,下一頁,一個(gè)輸入文本框,以及一個(gè)GO按鈕,根據(jù)當(dāng)前分頁是第幾頁高亮表示當(dāng)前分頁的按鈕,根據(jù)頁數(shù)的多少隱藏或顯示某些多余的按鈕。每次點(diǎn)擊上一頁,下一頁或者在文本框中輸入數(shù)字轉(zhuǎn)到某一頁之后,分頁ui中的一系列的數(shù)字按鈕要重新初始化,并設(shè)置樣式。弄一個(gè)成員函數(shù)去統(tǒng)一更新就可以了。其余的都是在這個(gè)ui類的構(gòu)造函數(shù)中加載QSS樣式文件去設(shè)置其界面,這些都是最基本的樣式?jīng)]有什么復(fù)雜的。當(dāng)分頁控件中的當(dāng)前頁改變之后,我們可以發(fā)射一個(gè)信號(hào)出去,通知主窗體上進(jìn)行QListWidget的更新。這部分的邏輯沒什么好說的,就不放代碼了。在QtCreator看起來布局是這樣:
應(yīng)用QSS之后的效果是這樣的:
回到頂部
Qt中讀取數(shù)據(jù)庫
在Qt中讀寫數(shù)據(jù)庫是很方便的,這里以Sqlite為例子,主要是通過 QSqlDatabase 去連接數(shù)據(jù)庫,通過 QSqlQuery 去做查詢并獲取結(jié)果。這里在查詢數(shù)據(jù)的時(shí)候,我們可以做一下簡(jiǎn)單的封裝,我們首先需要有一個(gè) QSqlDatabase 的對(duì)象實(shí)例,調(diào)用該實(shí)例的 open() 函數(shù)去打開數(shù)據(jù)庫連接,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//得到一個(gè)QSqlDatabase的實(shí)例
SqliteDBAOperator::SqliteDBAOperator(QString dbName)
{
? ? this->dbName = dbName;
? ? if (QSqlDatabase::contains(dbName))
? ? {
? ? ? ? //這個(gè)類里面應(yīng)該有一個(gè)靜態(tài)的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ) 連接名 -> QSqlDatabase 對(duì)象的映射
? ? ? ? db = QSqlDatabase::database(dbName);
? ? }
? ? else
? ? {
? ? ? ? //建立和sqlite數(shù)據(jù)的連接
? ? ? ? db = QSqlDatabase::addDatabase("QSQLITE",this->dbName);
? ? ? ? //設(shè)置數(shù)據(jù)庫文件的名字
? ? ? ? QString dbFilePath = QCoreApplication::applicationDirPath() +QString("/")+ this->dbName;
? ? ? ? db.setDatabaseName(dbFilePath);
? ? }
}
//打開數(shù)據(jù)庫連接,之后就可以用這個(gè) db 對(duì)象了
bool SqliteDBAOperator::openDb(void)
{
? ? //打開數(shù)據(jù)庫
? ? if(db.open() == false){
? ? ? ? qDebug() << "connect db fail";
? ? ? ? return false;
? ? }
? ? qDebug() << "connect db success";
? ? return true;
}
這樣我們就得到了一個(gè) QSqlDatabase 的對(duì)象變量 db ,之后就可以用這個(gè)QSqlQuery 去查詢:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
QList<QMap<QString, QVariant> > SqliteDBAOperator::queryData(QString& str)
{
? ? QSqlQuery query(this->db);
? ? bool res = query.exec(str);
? ? QList<QMap<QString, QVariant> > list;
? ? if(res==true){
? ? ? ? while(query.next()){
? ? ? ? ? ? QSqlRecord record = query.record();
? ? ? ? ? ? QMap<QString, QVariant> info;
? ? ? ? ? ? for(int i=0;i<record.count();i++){//遍歷該record的所有列
? ? ? ? ? ? ? ? QString field = record.fieldName(i);//得到字段名,作為該map的key
? ? ? ? ? ? ? ? QVariant var = record.value(field);
? ? ? ? ? ? ? ? info[field] = var;
? ? ? ? ? ? }
? ? ? ? ? ? list<<info;
? ? ? ? }
? ? }
? ? return list;
}
我們將查詢出來的數(shù)據(jù)放到了一個(gè)QList中,其每一個(gè)元素是一個(gè)QMap,實(shí)際上就是表中的一行數(shù)據(jù),用QMap可以非常方便的通過表的字段來引用該行數(shù)據(jù)的某一列的數(shù)據(jù)值,這里的值是 QVariant 類型,那么在使用的時(shí)候可以像下面這樣用:
1
2
3
4
5
6
7
8
9
10
11
12
QString sql = QString("select aa,bb from table ");
QList<QMap<QString, QVariant> > listMap = sqliteAdapter->queryData(sql);
QList<BingImageDataInfo> list;?
if(listMap.size()>0){
? ? for(int i=0;i<listMap.size();i++){
? ? ? ? QMap<QString, QVariant> &row = listMap[0];
? ? ? ? BingImageDataInfo info;
? ? ? ? info.Url = row["url"].toString();
? ? ? ? info.Time = row["time"].toInt();
? ? ? ? list<<info;
? ? }
}
使用表的字段名來引用該字段的值看起來是不是很直觀方便,另外要使用Sql功能,我們還需要在項(xiàng)目的pro文件中加上 QT += sql
回到頂部
自定義菜單項(xiàng)
我們這里所說的自定義菜單,不是給菜單項(xiàng)添加一個(gè)圖標(biāo),在Qt中,可以使用QSS給每個(gè)菜單項(xiàng)設(shè)置樣式,比如邊框,背景顏色,菜單項(xiàng)文字前面的圖標(biāo)等。但是我們這里不僅限于此,我們的軟件中有一個(gè)菜單按鈕,點(diǎn)開之后是如下的菜單:
首先這個(gè)菜單是圓角的,其次這個(gè)菜單看起來不是常規(guī)的每個(gè)菜單項(xiàng)就是一行文字(最多前面加個(gè)圖標(biāo)修飾一下),這里面有復(fù)選框,有文字,還有按鈕,其實(shí)這個(gè)框框中的3個(gè)復(fù)選框,以及3個(gè)QLabel,還有一個(gè)按鈕,他們僅僅包含在一個(gè)菜單項(xiàng)中,也就是說這個(gè)菜單只有一個(gè)菜單項(xiàng),這個(gè)菜單項(xiàng)是我們自定義的,同樣的這個(gè)菜單項(xiàng),是我們自定義的一個(gè)ui文件類(包含了對(duì)應(yīng)的頭文件以及cpp文件),當(dāng)我們以自定義的ui文件類的方式來定義菜單項(xiàng)的時(shí)候,我們可以想做多復(fù)雜就做多復(fù)雜,像很多音樂播放器的任務(wù)托盤上的菜單有播放,調(diào)整音量,有的甚至有專輯封面,這些都是小菜一碟。首先截圖中的菜單按鈕(信封按鈕的右邊)是一個(gè)QPushButton,在Qt中可以為某一個(gè)按鈕關(guān)聯(lián)一個(gè)彈出菜單的,只需要調(diào)用這個(gè)按鈕的 setMenu() 函數(shù)傳遞一個(gè) QMenu 就可以將這個(gè) QMenu 與這個(gè)按鈕關(guān)聯(lián)起來,默認(rèn)情況下這個(gè) QMenu 彈出的位置是與這個(gè)QPushButton沿左邊線對(duì)齊的,可以看到,上面的截圖顯然不是,上面的截圖中,彈出的菜單看起來與按鈕是居中對(duì)齊的(這里左對(duì)齊不好看),為了改變這種左對(duì)齊的默認(rèn)行為,我們自己定義了一個(gè)繼承自QMenu的類,叫做 PopMenu ,在這個(gè)類中重寫了QMenu的 showEvent 函數(shù),在此函數(shù)中,重新調(diào)整菜單顯示的位置,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PopMenu::PopMenu(QPushButton* button, QWidget* parent) : QMenu(parent), b(button)
{
? ? this->b = button; //保存其關(guān)聯(lián)的按鈕
}
void PopMenu::showEvent(QShowEvent* event)
{
? ? //根據(jù)按鈕的位置,調(diào)整菜單的位置
? ? QPoint p = this->pos();
? ? int diff = (this->width() - b->width())/2;
? ? int newx = p.x()-diff;
? ? int newy = p.y()+5;
? ? this->move(newx,newy);
}
與菜單按鈕關(guān)聯(lián)的菜單實(shí)際上是 PopMenu 其也是一個(gè) QMenu 對(duì)象,那么我們自定義的表示菜單項(xiàng)的ui類(下面代碼中的 MenuSetting 類)如何設(shè)置到這個(gè) PopMenu 菜單里面的呢,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void MainWindow::initMenuSetting()
{
? ? //構(gòu)造 PopMenu 對(duì)象
? ? this->menuSetting = new PopMenu(ui->btnMenu,this);
? ? //構(gòu)造自定義的菜單項(xiàng)的ui類對(duì)象,實(shí)際上是一個(gè)繼承自QWidget的子類
? ? MenuSetting *menuSettingWidget = new MenuSetting(this);
? ? //下面兩句是關(guān)鍵,這里定義的菜單項(xiàng)不是QAction 而是 QWidgetAction 就是用來將菜單項(xiàng)設(shè)置為一個(gè)Wdiget的,這個(gè)QWidgetAction實(shí)際上是QAction的子類
? ? QWidgetAction *action = new QWidgetAction(this->menuSetting);
? ? //將這個(gè)Action設(shè)置為一個(gè)QWdiget,就是我們自定義的ui類對(duì)象
? ? action->setDefaultWidget(menuSettingWidget);
? ? //為這個(gè)菜單添加Action
? ? this->menuSetting->addAction(action); //這個(gè)一定要有
? ? //這個(gè)寫在主樣式表里面沒效果,要寫在代碼中才行
? ? this->ui->btnMenu->setStyleSheet("QPushButton#btnMenu::menu-indicator{image:none;background-color:transparent;}");
? ? //給按鈕綁定此彈出菜單
? ? ui->btnMenu->setMenu(this->menuSetting);?
? ? //這里為什么需要,因?yàn)椴藛巫鳛橐粋€(gè)獨(dú)立的存在,其地位跟窗口是一樣的,菜單并不是被包含在主窗口的任何控件中
? ? //沒有任何控件是菜單的容器,他其實(shí)就是一個(gè)獨(dú)立的窗口,只不過菜單是一種比較特殊的窗口,所以我們需要單獨(dú)為其設(shè)置窗體標(biāo)志,設(shè)置背景透明
? ? menuSetting->setWindowFlags(Qt::Popup|Qt::FramelessWindowHint);
? ? menuSetting->setAttribute(Qt::WA_TranslucentBackground);
}
至于菜單的圓角效果,那就更簡(jiǎn)單了,其實(shí)是我們自定義菜單項(xiàng)中的ui類對(duì)象中的容器QFrame,我們?cè)O(shè)置QSS的時(shí)候設(shè)置成圓角就可以。這里有一點(diǎn)要注意的是,一旦一個(gè)QPushButton 設(shè)置了一個(gè)關(guān)聯(lián)的彈出菜單之后,該QPushButton的行為就有了變化,單擊這個(gè)按鈕的時(shí)候他的行為就是彈出其關(guān)聯(lián)的彈出菜單,他不會(huì)再響應(yīng)其 click 槽函數(shù),這點(diǎn)是需要注意的,此時(shí)這個(gè)按鈕的唯一功能就是單擊之后,彈出菜單。
回到頂部
Qt中托盤程序的實(shí)現(xiàn)
在Qt中實(shí)現(xiàn)托盤程序也比較容易,Qt中的托盤程序主要是通過 QSystemTrayIcon 以及其關(guān)聯(lián)的上下文菜單對(duì)象來實(shí)現(xiàn)的,具體代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void MainWindow::initTraySystem(){
? ? ? ? //初始化托盤圖標(biāo)對(duì)象
? ? ? ? this->trayIcon = new QSystemTrayIcon( this );
? ? ? ? //設(shè)定托盤圖標(biāo)
? ? ? ? this->trayIcon->setIcon( QIcon( QPixmap( ":/images/title/icon_16.ico" ) ) );
? ? ? ? //設(shè)置提示文字
? ? ? ? this->trayIcon->setToolTip(QString(WINDOW_TITLE));
? ? ? ? //讓托盤圖標(biāo)顯示在系統(tǒng)托盤上
? ? ? ? this->trayIcon->show();
? ? ? ? //連接信號(hào)與槽,實(shí)現(xiàn)單擊圖標(biāo)恢復(fù)窗口的功能,槽是自定義的槽函數(shù)TrayIconAction
? ? ? ? connect( this->trayIcon, SIGNAL( activated( QSystemTrayIcon::ActivationReason ) ), this, SLOT( TrayIconAction( QSystemTrayIcon::ActivationReason ) ) );
? ? ? ? //初始化托盤菜單及功能,這里定義一個(gè)菜單,并定義各個(gè)菜單項(xiàng)
? ? ? ? this->trayMenu = new QMenu(this);//初始化菜單
? ? ? ? this->trayMenu->setObjectName("trayMenu");
? ? ? ? this->trayActionOpen = new QAction(this);//打開主窗口菜單
? ? ? ? this->trayActionOpen->setText( "主窗口" );
? ? ? ? connect(this->trayActionOpen, SIGNAL(triggered()), this, SLOT(showNormal()));//菜單中的顯示窗口,單擊顯示窗口
? ? ? ? this->trayActionquit = new QAction(this);//退出菜單
? ? ? ? this->trayActionquit->setText( "退出" );
? ? ? ? connect(this->trayActionquit, SIGNAL( triggered()), qApp, SLOT(quit()));// 菜單中的退出程序,單擊退出
? ? ? ? this->trayActionAbout = new QAction(this);//關(guān)于
? ? ? ? this->trayActionAbout->setText("關(guān)于");
? ? ? ? connect(this->trayActionAbout, SIGNAL( triggered()), this, SLOT(TriggerAbout()));// 彈出關(guān)于窗口
? ? ? ? //將定義好的菜單與托盤圖標(biāo)關(guān)聯(lián)起來
? ? ? ? this->trayIcon->setContextMenu(this->trayMenu);
? ? ? ? this->trayMenu->addAction(this->trayActionOpen);
? ? ? ? this->trayMenu->addAction(this->trayActionAbout);
? ? ? ? this->trayMenu->addAction(this->trayActionquit);
? ? ? ? //為菜單設(shè)置樣式
? ? ? ? QFile file(":/qss/traymenu.qss");
? ? ? ? file.open(QFile::ReadOnly);
? ? ? ? QTextStream filetext(&file);
? ? ? ? QString stylesheet = filetext.readAll();
? ? ? ? this->trayMenu->setStyleSheet(stylesheet);
? ? ? ? file.close();
}
這里還要注意一點(diǎn),由于Qt程序中,當(dāng)所有的窗口都關(guān)閉之后,程序就退出了,但是我們希望窗口都關(guān)閉之后,程序仍然還在運(yùn)行,進(jìn)程是不能退出的,因?yàn)橥斜P圖標(biāo)還在,這個(gè)時(shí)候就需要在Qt程序的main函數(shù)中,設(shè)置一下如下:
1
2
3
4
5
6
int main(int argc, char *argv[])
{
? ? QApplication app(argc, argv);
? ? app.setQuitOnLastWindowClosed(false);
? ? .... 其他代碼省略 ....
}
回到頂部
Qt單進(jìn)程的實(shí)現(xiàn)
我們希望在已經(jīng)有一個(gè)進(jìn)程打開的情況下,用戶再雙擊打開程序直接觸發(fā)之前打開的程序窗口,而不是同時(shí)打開多個(gè)程序,就像目前這個(gè)程序,如果用戶已經(jīng)有一個(gè)進(jìn)程最小化到了任務(wù)托盤中,當(dāng)用戶再次打開程序的時(shí)候,直接將當(dāng)前運(yùn)行在任務(wù)托盤的程序觸發(fā)其顯示主窗口即可。要實(shí)現(xiàn)單進(jìn)程,就必須有一種方案,能在進(jìn)程啟動(dòng)的時(shí)候標(biāo)識(shí)一個(gè)全局?jǐn)?shù)據(jù),在進(jìn)程退出之后自動(dòng)取消標(biāo)識(shí)。基于共享內(nèi)存、文件鎖等方式的實(shí)現(xiàn)不是很完美,總會(huì)有一些問題。我們這里使用 QLocalServer 的方式來實(shí)現(xiàn),實(shí)現(xiàn)代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
int main(int argc, char *argv[])
{
? ? QApplication a(argc, argv);
? ? //MainWindow w;
? ? /*
? ? * MainWindow的定義不能放在這里,如果已經(jīng)有一個(gè)實(shí)例在運(yùn)行的情況下,這里勢(shì)必會(huì)進(jìn)入if分支
? ? * 然后調(diào)用 return -1 我們預(yù)期的情況下當(dāng)前這個(gè)進(jìn)程應(yīng)該會(huì)退出,但是實(shí)際實(shí)驗(yàn)中發(fā)現(xiàn),windows 的任務(wù)管理器中
? ? * 會(huì)多出一個(gè)進(jìn)程出來,在已經(jīng)運(yùn)行一個(gè)客戶端的情況下,后面的每雙擊一下程序,任務(wù)管理器都會(huì)多出一個(gè)進(jìn)程
? ? * 很奇怪,通過日志打印這里也確實(shí)是進(jìn)入了if分支,并且確實(shí)是return -1 也就是這個(gè)main函數(shù)已經(jīng)退出了
? ? * 但是進(jìn)程就在任務(wù)管理器里面,并且沒有任何界面(都沒有走到下面的w.show()不會(huì)有窗口顯示)
? ? */
? ? //初始化一個(gè)LocalServer
? ? LocalServer server;
? ? if(!server.init(LOCAL_SERVER_NAME)){
? ? ? ? // 初使化Server失敗, 說明已經(jīng)有一個(gè)在運(yùn)行了,通過客戶端連接這個(gè)已經(jīng)運(yùn)行的Server
? ? ? ? // 并給他發(fā)個(gè)消息讓已經(jīng)運(yùn)行的那個(gè)進(jìn)程顯示主窗口,自己則退出
? ? ? ? LocalClient::ConnectSendMessage(LOCAL_SERVER_NAME,ACTIVE_MESSAGE);
? ? ? ? qDebug()<<"localserver init false exit -1"<<endl;
? ? ? ? a.quit();
? ? ? ? return -1;
? ? }
? ? //初始化數(shù)據(jù)
? ? if(!DbDataManager::Init()){
? ? ? ? qDebug()<<"DbDataManager::Init() false exit -1"<<endl;
? ? ? ? return -1;
? ? }
? ? /*
? ? * 這個(gè)定義放在這里就不會(huì)有上面描述的后打開的進(jìn)程不退出的問題,不知道為什么在上面那種情況下
? ? * main函數(shù)都return了進(jìn)程卻不退出。以為是MainWindow中的構(gòu)造函數(shù)中有初始化任務(wù)托盤可能跟任務(wù)托盤有關(guān)系
? ? * 但是注釋掉初始化任務(wù)托盤的相關(guān)函數(shù)之后還是一樣的情況,具體原因還不清楚
? ? */
? ? MainWindow w;
? ? //LocalServer 初始化成功,當(dāng)server收到消息之后用一個(gè)槽函數(shù)處理
? ? //收到激活消息之后顯示主窗口
? ? QObject::connect(&server, &LocalServer::newMessage, [&](const QString &message){
? ? ? ? if(message == ACTIVE_MESSAGE){
? ? ? ? ? ? qDebug()<<"recv active message show normal window"<<endl;
? ? ? ? ? ? w.showNormal();
? ? ? ? }
? ? });
? ? //所有窗口都關(guān)閉之后不退出程序,不設(shè)置的話如果關(guān)閉主窗口之后,在任務(wù)托盤上打開"關(guān)于"窗口之后,關(guān)閉"關(guān)于"窗口
? ? //會(huì)導(dǎo)致進(jìn)程退出(可能是所有可見窗口都關(guān)閉了所以進(jìn)程自動(dòng)退出了)
? ? a.setQuitOnLastWindowClosed(false);
? ? //根據(jù)命令行參數(shù)來決定程序啟動(dòng)的時(shí)候是否顯示主窗口,還是直接最小化到任務(wù)托盤
? ? //一般如果程序是隨系統(tǒng)自啟動(dòng)的情況下,讓他直接最小化到任務(wù)托盤
? ? //如果是用戶自己雙擊啟動(dòng)的情況,就顯示主窗口
? ? //判斷程序是隨系統(tǒng)自啟動(dòng)還是用戶雙擊啟動(dòng)的區(qū)別,就是有沒有命令行參數(shù)
? ? //用戶雙擊啟動(dòng)是不會(huì)有命令行參數(shù)的
? ? bool showMainForm = true;
? ? if(argc >= 2){
? ? ? ? QString twoParam = argv[1];
? ? ? ? if(twoParam==CMD_PARAM_AUTO_RUN){
? ? ? ? ? ? showMainForm = false;
? ? ? ? }
? ? }
? ? if(showMainForm){
? ? ? ? w.show();
? ? }
? ? return a.exec();
}
上面的代碼中 LocalServer,LocalClient 是我們基于 QLocalServer,QLocalSocket 包裝的本地服務(wù)和客戶端??蛻舳说闹饕a如下:
1
2
3
4
5
6
7
8
9
10
11
//localclient.cpp
void LocalClient::ConnectSendMessage(QString serverName,QString message){
? ? QLocalSocket ls;
? ? ls.connectToServer(serverName);
? ? if (ls.waitForConnected()){
? ? ? ? QTextStream ts(&ls);
? ? ? ? ts << message;
? ? ? ? ts.flush();
? ? ? ? ls.waitForBytesWritten();
? ? }
}
服務(wù)端的主要代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//localserver.cpp
LocalServer::LocalServer(QObject *parent) : QObject(parent)
{
? ? m_server = 0;
}
LocalServer::~LocalServer()
{
? ? if (m_server)
? ? {
? ? ? ? delete m_server;
? ? }
}
bool LocalServer::init(const QString & servername)
{
? ? // 如果已經(jīng)有一個(gè)實(shí)例在運(yùn)行了就返回0
? ? if (isServerRun(servername)) {
? ? ? ? return false;
? ? }
? ? m_server = new QLocalServer;
? ? // 先移除原來存在的,如果不移除那么如果
? ? // servername已經(jīng)存在就會(huì)listen失敗
? ? QLocalServer::removeServer(servername);
? ? // 進(jìn)行監(jiān)聽
? ? m_server->listen(servername);
? ? connect(m_server, SIGNAL(newConnection()), this, SLOT(newConnection()));
? ? return true;
}
// 有新的連接來了
void LocalServer::newConnection()
{
? ? QLocalSocket *newsocket = m_server->nextPendingConnection();
? ? connect(newsocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
}
// 可以讀數(shù)據(jù)了
void LocalServer::readyRead()
{
? ? // 取得是哪個(gè)localsocket可以讀數(shù)據(jù)了
? ? QLocalSocket *local = static_cast<QLocalSocket *>(sender());
? ? if (!local)
? ? ? ? return;
? ? QTextStream in(local);
? ? QString? ? readMsg;
? ? // 讀出數(shù)據(jù)
? ? readMsg = in.readAll();
? ? // 發(fā)送收到數(shù)據(jù)信號(hào)
? ? emit newMessage(readMsg);
}
// 判斷是否有一個(gè)同名的服務(wù)器在運(yùn)行
bool LocalServer::isServerRun(const QString & servername)
{
? ? // 用一個(gè)localsocket去連一下,如果能連上就說明
? ? // 有一個(gè)在運(yùn)行了
? ? QLocalSocket ls;
? ? ls.connectToServer(servername);
? ? if (ls.waitForConnected(1000)){
? ? ? ? // 說明已經(jīng)在運(yùn)行了
? ? ? ? ls.disconnectFromServer();
? ? ? ? ls.close();
? ? ? ? return true;
? ? }
? ? return false;
}
這樣就通過 QLocalServer 和 QLocalSocket 實(shí)現(xiàn)了單進(jìn)程,實(shí)際上這里的服務(wù)端并沒有監(jiān)聽一個(gè)端口,在windows的實(shí)現(xiàn)中是通過命名管道的,這個(gè)管道在我們程序啟動(dòng)之后被打開,在進(jìn)程退出之后就自動(dòng)沒有了。當(dāng)程序啟動(dòng)的時(shí)候啟動(dòng)服務(wù)端,如果另一個(gè)程序也啟動(dòng),他會(huì)發(fā)現(xiàn)啟動(dòng)通過一個(gè)名字的服務(wù)端失敗了,說明之前已經(jīng)有一個(gè)服務(wù)端已經(jīng)啟動(dòng)了,這個(gè)時(shí)候他只需要以客戶端的方式連接之前啟動(dòng)的服務(wù)端,給他發(fā)一個(gè)消息,那個(gè)服務(wù)端收到消息之后顯示其主窗口即可。
回到頂部
Qt寫注冊(cè)表實(shí)現(xiàn)自啟動(dòng)
我們通過在注冊(cè)表項(xiàng)中添加項(xiàng)目來實(shí)現(xiàn)程序的自啟動(dòng),在Qt中寫注冊(cè)表是很簡(jiǎn)單的,主要代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//開機(jī)自啟動(dòng)注冊(cè)表鍵
#define REG_RUN "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
//設(shè)置程序開機(jī)自啟動(dòng)
void Tools::setAutoRunning(bool is_auto_start)
{
? ? QString application_name = QApplication::applicationName();
? ? QSettings *settings = new QSettings(REG_RUN, QSettings::NativeFormat);
? ? if(is_auto_start)
? ? {
? ? ? ? //程序執(zhí)行文件的路徑 F:\\xxx\\xxx\zz.exe
? ? ? ? QString application_path = QApplication::applicationFilePath();
? ? ? ? application_path += " " ;
? ? ? ? //加上命令行參數(shù)? F:\\xxx\\xxx\zz.exe autorun
? ? ? ? application_path += CMD_PARAM_AUTO_RUN;
? ? ? ? settings->setValue(application_name, application_path.replace("/", "\\"));
? ? }
? ? else
? ? {
? ? ? ? settings->remove(application_name);
? ? }
? ? delete settings;
}
效果是這樣:
回到頂部
在線程中請(qǐng)求http或者h(yuǎn)ttps資源
我們需要通過網(wǎng)絡(luò)去請(qǐng)求json數(shù)據(jù),以及下載壁紙圖片,在Qt中是通過 QNetworkAccessManager 來實(shí)現(xiàn)的,線程的功能代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//定時(shí)任務(wù)讀取json數(shù)據(jù),并從解析出的json數(shù)據(jù)中獲取要下載的圖片url
//進(jìn)一步下載圖片,這里面刪除了一些無關(guān)的代碼,保留了關(guān)鍵的代碼
void DownloadThread::run(){
? ? QNetworkAccessManager* manager = new QNetworkAccessManager();
? ? while(true){
? ? ? ? QThread::sleep(TIME_INTERVAL_MINS*60);//secs<br>? ? ? ? //輸出當(dāng)前Qt支持的ssl版本情況
? ? ? ? //qDebug()<<"QSslSocket="<<QSslSocket::sslLibraryBuildVersionString();<br>? ? ? ? //輸出當(dāng)前Qt是否支持ssl,如果支持這里返回true,否則返回false
? ? ? ? //qDebug() << "OpenSSL支持情況:" << QSslSocket::supportsSsl();
? ? ? ? QEventLoop loop;
? ? ? ? QObject::connect(manager, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
? ? ? ? //request對(duì)象
? ? ? ? QNetworkRequest request;
? ? ? ? //設(shè)置UserAgent頭
? ? ? ? request.setHeader(QNetworkRequest::UserAgentHeader, USER_AGENT_FIREFOX);
? ? ? ? //設(shè)置需要請(qǐng)求的資源url,例如 http://www.abc.com/xx.jpg
? ? ? ? request.setUrl(QUrl(BING_JSON_URL));
? ? ? ? //調(diào)用 QNetworkAccessManager 的get函數(shù)發(fā)送http請(qǐng)求
? ? ? ? QNetworkReply *reply = manager->get(request);
? ? ? ? loop.exec();
? ? ? ? //當(dāng)請(qǐng)求完成之后通過 QNetworkReply 的readAll()讀取響應(yīng)結(jié)果
? ? ? ? //readAll()函數(shù)返回的是一個(gè) QByteArray 如果響應(yīng)是一個(gè)文本,我們可以賦值給一個(gè)QString,比如這里是一個(gè)json文本
? ? ? ? QString json = reply->readAll();
? ? ? ? //刪除該reply對(duì)象
? ? ? ? reply->deleteLater();
? ? ? ? BingImageDataInfo info;
? ? ? ? //解析json數(shù)據(jù),這里面通過 QJsonDocument 相關(guān)的類來解析json數(shù)據(jù),很簡(jiǎn)單,就不展示 parseJson() 的實(shí)現(xiàn)了
? ? ? ? bool parseRes = parseJson(json,info);
? ? ? ? qDebug()<<"parse"<<endl;
? ? ? ? if(parseRes){ // added to db and mem
? ? ? ? ? ? //待下載的圖片的url路徑
? ? ? ? ? ? QString downloadUrl = BING_DOMAIN+info.Url;
? ? ? ? ? ? //重新設(shè)置request的url
? ? ? ? ? ? request.setUrl(QUrl(downloadUrl));
? ? ? ? ? ? //發(fā)送請(qǐng)求獲取圖片內(nèi)容
? ? ? ? ? ? QNetworkReply *replyDownload = manager->get(request);
? ? ? ? ? ? loop.exec();
? ? ? ? ? ? QFile fDownload(imageFilePath);
? ? ? ? ? ? if(fDownload.open(QIODevice::WriteOnly)){
? ? ? ? ? ? ? ? fDownload.write(replyDownload->readAll());//讀取響應(yīng)結(jié)果并寫入磁盤中
? ? ? ? ? ? ? ? fDownload.close();
? ? ? ? ? ? }
? ? ? ? ? ? replyDownload->deleteLater();
? ? ? ? ? ? }
? ? ? ? }
? ? } // end while true
}
USB Microphone https://www.soft-voice.com/
Wooden Speakers? https://www.zeshuiplatform.com/
亞馬遜測(cè)評(píng) www.yisuping.cn
深圳網(wǎng)站建設(shè)www.sz886.com