使用Qt實現一個必應壁紙客戶端

在使用控件布局的時候,用QFrame做容器,用的最多的就是垂直布局和水平布局,再加上垂直和水平的Spacer控件,基本上可以搞定所有布局效果,這里要注意的是,在QtCreator中為控件設置布局的Layout屬性是有默認值的,包括Margin和Spacing,導致布局控件的子控件之間有空隙,所以這里最好是手工都設置為0,按照上面的布局,我們最外層是一個QMainWindow,在其頭文件和源文件中重寫鼠標的按下、移動、釋放按鈕如下:

1

2

3

4

5

//mainwindow.h 頭文件

protected:

? ? virtual void mousePressEvent(QMouseEvent *event);

? ? virtual void mouseMoveEvent(QMouseEvent *event);

? ? virtual void mouseReleaseEvent(QMouseEvent *event);

重新實現鼠標的這3個事件代碼如下:

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

//重寫鼠標按下事件

void MainWindow::mousePressEvent(QMouseEvent *event)

{

? ? mMoveing = true;

? ? //記錄下鼠標相對于窗口的位置

? ? //event->globalPos()鼠標按下時,鼠標相對于整個屏幕位置

? ? //pos() this->pos()鼠標按下時,窗口相對于整個屏幕位置

? ? mMovePosition = event->globalPos() - pos();

? ? QWidget::mousePressEvent(event);

}

//重寫鼠標移動事件

void MainWindow::mouseMoveEvent(QMouseEvent *event)

{

? ? //(event->buttons() & Qt::LeftButton)按下是左鍵

? ? //鼠標移動事件需要移動窗口,窗口移動到哪里呢?就是要獲取鼠標移動中,窗口在整個屏幕的坐標,然后move到這個坐標,怎么獲取坐標?

? ? //通過事件event->globalPos()知道鼠標坐標,鼠標坐標減去鼠標相對于窗口位置,就是窗口在整個屏幕的坐標

? ? if (mMoveing && (event->buttons() & Qt::LeftButton)

? ? ? ? && (event->globalPos()-mMovePosition).manhattanLength() > QApplication::startDragDistance())

? ? {

? ? ? ? move(event->globalPos()-mMovePosition);

? ? ? ? mMovePosition = event->globalPos() - pos();

? ? }

? ? QWidget::mouseMoveEvent(event);

}

//鼠標釋放

void MainWindow::mouseReleaseEvent(QMouseEvent *event)

{

? ? mMoveing = false;

? ? QWidget::mouseReleaseEvent(event);

}

這樣就實現了無邊框窗體的拖動效果,除此之外我們要重寫關閉按鈕的功能,在關閉窗體的時候不去close,這個代碼就不展示了,百度一下就有,下面我們看看如何自定義QListWidget

回到頂部

自定義QListWidget項目

默認的QListWidget項目很簡單,每一個QListWidget中的項目是一個QListWidgetItem對象,該對象提供 setText() 為該項目設置文字,setIcon() 用于為項目設置圖標。如果僅僅是這樣的話,這是完全滿足不了需求的,我們需要設置圖片,并且需要在圖片上顯示圖片的描述,日期,地址等信息。我們可以在QtCreator中創建一個ui文件(包括頭文件和對應的cpp文件),在這個ui文件中,我們以QWidget作為最頂層的容器(不像窗體用的QMainWindow),他就像我們任何自定義的ui控件一樣,我們可以利用QtCreator的布局功能做任何復雜的布局,最后這個ui文件對應的頭文件與cpp文件實際上就是一個C++類,我們在需要的地方使用這個類就可以了。我們為這個QListWidget的每一項定義的ui界面布局如下:

可以看到我們這個ui布局中黑色實線的矩形框一共有4個都是用QFrame,最外面的QFrame0是整個ui的容器他是被一個QWidget包裹,每一個項目的圖片被設置為QFrame0的背景圖片,該項目的區域被分為上下兩部分,上面是QFrame1,下面是QFrame3,其中QFrame1里面嵌套了一個小的QFrame2,這個QFrame2中有2個水平布局的按鈕,一個是預覽(軟件中的放大鏡),一個是設置當前圖片為桌面壁紙(軟件中的顯示器圖標)。默認情況下這兩個按鈕不顯示,當鼠標移動到QFrame0上的時候,整個QFrame1將以動畫的形式從上切入,并且有一個透明度的變化。如何將我們自定義的ui類,設置為該QListWidget的項呢,可以用下面的代碼:

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

? ? // 創建單元項

? ? // 這里創建的Item使用的背景圖是樣式表中的默認背景圖

? ? 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設置為一個Widget,而這個Widget就是自定義的ui

? ? }

? ? //給item綁定真實數據

? ? updateListWidget(1); // page 從1開始

}

我們首先為這個QListWidget加載了一個樣式表,然后設置了一些參數,最后為其設置了6個item項目,每個item項目是一個Widget對象,也就是我們自定義的ui類(ImageInfoItem2),其實際上是繼承自QWidget類的。最重要的是使用QListWidget::setItemWidget()成員函數設置item為一個QWidget,設置好item對象之后,最后有一個函數去設置每個item的數據,例如背景圖片、圖片描述、日期、地址信息等。當我們去遍歷該QListWidget的每一個item并將item轉換為當初設置的QWidget對象,調用該ui對象的成員方法來更新其上的數據即可:

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頁的圖片數據

void MainWindow::updateListWidget(int page){

? ? //獲取第page頁面的數據當在imageInfoList中

? ? QList<BingImageDataInfo> imageInfoList = DataManager::GetImageInfoList(page);

? ? //先初始化listWidget列表的每一項為空數據

? ? 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;//空數據

? ? ? ? ? ? imageInfoItem->updateImageInfo(info);

? ? ? ? }

? ? }

? ? //根據實際上得到的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]);

? ? ? ? }

? ? }

}

上面的代碼中,先用空數據初始化了一遍item,然后用真實數據填充,因為這里按分頁獲取的數據也許個數并不滿足一頁的數據項6個(比如最后一頁也許只有5個),這里先通過QListWidget::itemWidget()得到一個綁定在該item上的QWidget對象,然后將其轉換成我們真實的 ImageInfoItem2 對象(就是那個ui類對象),并通過該對象的函數去更新item的數據。

回到頂部

QListWidget滾動條樣式表設置

本例中我們實際上并沒有允許滾動條出現,不過在最開始的時候確實考慮過使用滾動條,也就是讓每頁顯示不止6個圖片,但是發現對于這個軟件的這種布局使用滾動條比較奇怪,實際上滾動條通過QSS設置出來的效果還是很好的,在這里將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中不能用雙斜杠的注釋這會導致qss無效 */

QScrollBar:vertical {

? border: 0px;

? background: #202020;

? width: 8px; /*設置一個非0的寬度讓滾動條顯示*/

? margin: 0;

? padding:0;

}

/*滑塊的樣式*/

QScrollBar::handle:vertical {

? border: 0; /*設置border-radius屬性并不需要border屬性有值*/

? background: rgba(70,70,70,85%);

? min-height: 20px;

? border-radius:4px; /*坑:這個圓角要注意,不能超過寬度的一半,否則沒有圓角效果*/

}

/*鼠標放上滑塊的樣式,顏色透明度變一下*/

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;

}

/*這兩個屬性必須都要設置否則背景不會繼承基礎設置中的背景*/

QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical

{

? ? background:#202020;

}

其顯示的滾動條效果如下:

這里在對QScrollBar的滾動條的QSS屬性加以說明,以便使用:

為方便,我這里是自己在草稿紙上畫的,沒有用作圖工具,就將就著看一下吧。

回到頂部

分頁效果

分頁做起來很簡單,也是在QtCreator中自定義了一個ui界面類,將這個ui整體當作一個分頁控件來用,在類里面提供成員函數來設置各個按鈕的樣式,這個ui界面里面就是一些分頁按鈕,上一頁,下一頁,一個輸入文本框,以及一個GO按鈕,根據當前分頁是第幾頁高亮表示當前分頁的按鈕,根據頁數的多少隱藏或顯示某些多余的按鈕。每次點擊上一頁,下一頁或者在文本框中輸入數字轉到某一頁之后,分頁ui中的一系列的數字按鈕要重新初始化,并設置樣式。弄一個成員函數去統一更新就可以了。其余的都是在這個ui類的構造函數中加載QSS樣式文件去設置其界面,這些都是最基本的樣式沒有什么復雜的。當分頁控件中的當前頁改變之后,我們可以發射一個信號出去,通知主窗體上進行QListWidget的更新。這部分的邏輯沒什么好說的,就不放代碼了。在QtCreator看起來布局是這樣:

應用QSS之后的效果是這樣的:

回到頂部

Qt中讀取數據庫

在Qt中讀寫數據庫是很方便的,這里以Sqlite為例子,主要是通過 QSqlDatabase 去連接數據庫,通過 QSqlQuery 去做查詢并獲取結果。這里在查詢數據的時候,我們可以做一下簡單的封裝,我們首先需要有一個 QSqlDatabase 的對象實例,調用該實例的 open() 函數去打開數據庫連接,代碼如下:

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

//得到一個QSqlDatabase的實例

SqliteDBAOperator::SqliteDBAOperator(QString dbName)

{

? ? this->dbName = dbName;

? ? if (QSqlDatabase::contains(dbName))

? ? {

? ? ? ? //這個類里面應該有一個靜態的數據結構存儲 連接名 -> QSqlDatabase 對象的映射

? ? ? ? db = QSqlDatabase::database(dbName);

? ? }

? ? else

? ? {

? ? ? ? //建立和sqlite數據的連接

? ? ? ? db = QSqlDatabase::addDatabase("QSQLITE",this->dbName);

? ? ? ? //設置數據庫文件的名字

? ? ? ? QString dbFilePath = QCoreApplication::applicationDirPath() +QString("/")+ this->dbName;

? ? ? ? db.setDatabaseName(dbFilePath);

? ? }

}

//打開數據庫連接,之后就可以用這個 db 對象了

bool SqliteDBAOperator::openDb(void)

{

? ? //打開數據庫

? ? if(db.open() == false){

? ? ? ? qDebug() << "connect db fail";

? ? ? ? return false;

? ? }

? ? qDebug() << "connect db success";

? ? return true;

}

這樣我們就得到了一個 QSqlDatabase 的對象變量 db ,之后就可以用這個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;

}

我們將查詢出來的數據放到了一個QList中,其每一個元素是一個QMap,實際上就是表中的一行數據,用QMap可以非常方便的通過表的字段來引用該行數據的某一列的數據值,這里的值是 QVariant 類型,那么在使用的時候可以像下面這樣用:

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功能,我們還需要在項目的pro文件中加上 QT += sql

回到頂部

自定義菜單項

我們這里所說的自定義菜單,不是給菜單項添加一個圖標,在Qt中,可以使用QSS給每個菜單項設置樣式,比如邊框,背景顏色,菜單項文字前面的圖標等。但是我們這里不僅限于此,我們的軟件中有一個菜單按鈕,點開之后是如下的菜單:

首先這個菜單是圓角的,其次這個菜單看起來不是常規的每個菜單項就是一行文字(最多前面加個圖標修飾一下),這里面有復選框,有文字,還有按鈕,其實這個框框中的3個復選框,以及3個QLabel,還有一個按鈕,他們僅僅包含在一個菜單項中,也就是說這個菜單只有一個菜單項,這個菜單項是我們自定義的,同樣的這個菜單項,是我們自定義的一個ui文件類(包含了對應的頭文件以及cpp文件),當我們以自定義的ui文件類的方式來定義菜單項的時候,我們可以想做多復雜就做多復雜,像很多音樂播放器的任務托盤上的菜單有播放,調整音量,有的甚至有專輯封面,這些都是小菜一碟。首先截圖中的菜單按鈕(信封按鈕的右邊)是一個QPushButton,在Qt中可以為某一個按鈕關聯一個彈出菜單的,只需要調用這個按鈕的 setMenu() 函數傳遞一個 QMenu 就可以將這個 QMenu 與這個按鈕關聯起來,默認情況下這個 QMenu 彈出的位置是與這個QPushButton沿左邊線對齊的,可以看到,上面的截圖顯然不是,上面的截圖中,彈出的菜單看起來與按鈕是居中對齊的(這里左對齊不好看),為了改變這種左對齊的默認行為,我們自己定義了一個繼承自QMenu的類,叫做 PopMenu ,在這個類中重寫了QMenu的 showEvent 函數,在此函數中,重新調整菜單顯示的位置,代碼如下:

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; //保存其關聯的按鈕

}

void PopMenu::showEvent(QShowEvent* event)

{

? ? //根據按鈕的位置,調整菜單的位置

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

}

與菜單按鈕關聯的菜單實際上是 PopMenu 其也是一個 QMenu 對象,那么我們自定義的表示菜單項的ui類(下面代碼中的 MenuSetting 類)如何設置到這個 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()

{

? ? //構造 PopMenu 對象

? ? this->menuSetting = new PopMenu(ui->btnMenu,this);

? ? //構造自定義的菜單項的ui類對象,實際上是一個繼承自QWidget的子類

? ? MenuSetting *menuSettingWidget = new MenuSetting(this);

? ? //下面兩句是關鍵,這里定義的菜單項不是QAction 而是 QWidgetAction 就是用來將菜單項設置為一個Wdiget的,這個QWidgetAction實際上是QAction的子類

? ? QWidgetAction *action = new QWidgetAction(this->menuSetting);

? ? //將這個Action設置為一個QWdiget,就是我們自定義的ui類對象

? ? action->setDefaultWidget(menuSettingWidget);

? ? //為這個菜單添加Action

? ? this->menuSetting->addAction(action); //這個一定要有

? ? //這個寫在主樣式表里面沒效果,要寫在代碼中才行

? ? this->ui->btnMenu->setStyleSheet("QPushButton#btnMenu::menu-indicator{image:none;background-color:transparent;}");

? ? //給按鈕綁定此彈出菜單

? ? ui->btnMenu->setMenu(this->menuSetting);?

? ? //這里為什么需要,因為菜單作為一個獨立的存在,其地位跟窗口是一樣的,菜單并不是被包含在主窗口的任何控件中

? ? //沒有任何控件是菜單的容器,他其實就是一個獨立的窗口,只不過菜單是一種比較特殊的窗口,所以我們需要單獨為其設置窗體標志,設置背景透明

? ? menuSetting->setWindowFlags(Qt::Popup|Qt::FramelessWindowHint);

? ? menuSetting->setAttribute(Qt::WA_TranslucentBackground);

}

至于菜單的圓角效果,那就更簡單了,其實是我們自定義菜單項中的ui類對象中的容器QFrame,我們設置QSS的時候設置成圓角就可以。這里有一點要注意的是,一旦一個QPushButton 設置了一個關聯的彈出菜單之后,該QPushButton的行為就有了變化,單擊這個按鈕的時候他的行為就是彈出其關聯的彈出菜單,他不會再響應其 click 槽函數,這點是需要注意的,此時這個按鈕的唯一功能就是單擊之后,彈出菜單。

回到頂部

Qt中托盤程序的實現

在Qt中實現托盤程序也比較容易,Qt中的托盤程序主要是通過 QSystemTrayIcon 以及其關聯的上下文菜單對象來實現的,具體代碼如下:

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(){

? ? ? ? //初始化托盤圖標對象

? ? ? ? this->trayIcon = new QSystemTrayIcon( this );

? ? ? ? //設定托盤圖標

? ? ? ? this->trayIcon->setIcon( QIcon( QPixmap( ":/images/title/icon_16.ico" ) ) );

? ? ? ? //設置提示文字

? ? ? ? this->trayIcon->setToolTip(QString(WINDOW_TITLE));

? ? ? ? //讓托盤圖標顯示在系統托盤上

? ? ? ? this->trayIcon->show();

? ? ? ? //連接信號與槽,實現單擊圖標恢復窗口的功能,槽是自定義的槽函數TrayIconAction

? ? ? ? connect( this->trayIcon, SIGNAL( activated( QSystemTrayIcon::ActivationReason ) ), this, SLOT( TrayIconAction( QSystemTrayIcon::ActivationReason ) ) );

? ? ? ? //初始化托盤菜單及功能,這里定義一個菜單,并定義各個菜單項

? ? ? ? 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);//關于

? ? ? ? this->trayActionAbout->setText("關于");

? ? ? ? connect(this->trayActionAbout, SIGNAL( triggered()), this, SLOT(TriggerAbout()));// 彈出關于窗口

? ? ? ? //將定義好的菜單與托盤圖標關聯起來

? ? ? ? this->trayIcon->setContextMenu(this->trayMenu);

? ? ? ? this->trayMenu->addAction(this->trayActionOpen);

? ? ? ? this->trayMenu->addAction(this->trayActionAbout);

? ? ? ? this->trayMenu->addAction(this->trayActionquit);

? ? ? ? //為菜單設置樣式

? ? ? ? QFile file(":/qss/traymenu.qss");

? ? ? ? file.open(QFile::ReadOnly);

? ? ? ? QTextStream filetext(&file);

? ? ? ? QString stylesheet = filetext.readAll();

? ? ? ? this->trayMenu->setStyleSheet(stylesheet);

? ? ? ? file.close();

}

這里還要注意一點,由于Qt程序中,當所有的窗口都關閉之后,程序就退出了,但是我們希望窗口都關閉之后,程序仍然還在運行,進程是不能退出的,因為托盤圖標還在,這個時候就需要在Qt程序的main函數中,設置一下如下:

1

2

3

4

5

6

int main(int argc, char *argv[])

{

? ? QApplication app(argc, argv);

? ? app.setQuitOnLastWindowClosed(false);

? ? .... 其他代碼省略 ....

}

回到頂部

Qt單進程的實現

我們希望在已經有一個進程打開的情況下,用戶再雙擊打開程序直接觸發之前打開的程序窗口,而不是同時打開多個程序,就像目前這個程序,如果用戶已經有一個進程最小化到了任務托盤中,當用戶再次打開程序的時候,直接將當前運行在任務托盤的程序觸發其顯示主窗口即可。要實現單進程,就必須有一種方案,能在進程啟動的時候標識一個全局數據,在進程退出之后自動取消標識。基于共享內存、文件鎖等方式的實現不是很完美,總會有一些問題。我們這里使用 QLocalServer 的方式來實現,實現代碼如下:

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的定義不能放在這里,如果已經有一個實例在運行的情況下,這里勢必會進入if分支

? ? * 然后調用 return -1 我們預期的情況下當前這個進程應該會退出,但是實際實驗中發現,windows 的任務管理器中

? ? * 會多出一個進程出來,在已經運行一個客戶端的情況下,后面的每雙擊一下程序,任務管理器都會多出一個進程

? ? * 很奇怪,通過日志打印這里也確實是進入了if分支,并且確實是return -1 也就是這個main函數已經退出了

? ? * 但是進程就在任務管理器里面,并且沒有任何界面(都沒有走到下面的w.show()不會有窗口顯示)

? ? */

? ? //初始化一個LocalServer

? ? LocalServer server;

? ? if(!server.init(LOCAL_SERVER_NAME)){

? ? ? ? // 初使化Server失敗, 說明已經有一個在運行了,通過客戶端連接這個已經運行的Server

? ? ? ? // 并給他發個消息讓已經運行的那個進程顯示主窗口,自己則退出

? ? ? ? LocalClient::ConnectSendMessage(LOCAL_SERVER_NAME,ACTIVE_MESSAGE);

? ? ? ? qDebug()<<"localserver init false exit -1"<<endl;

? ? ? ? a.quit();

? ? ? ? return -1;

? ? }

? ? //初始化數據

? ? if(!DbDataManager::Init()){

? ? ? ? qDebug()<<"DbDataManager::Init() false exit -1"<<endl;

? ? ? ? return -1;

? ? }

? ? /*

? ? * 這個定義放在這里就不會有上面描述的后打開的進程不退出的問題,不知道為什么在上面那種情況下

? ? * main函數都return了進程卻不退出。以為是MainWindow中的構造函數中有初始化任務托盤可能跟任務托盤有關系

? ? * 但是注釋掉初始化任務托盤的相關函數之后還是一樣的情況,具體原因還不清楚

? ? */

? ? MainWindow w;

? ? //LocalServer 初始化成功,當server收到消息之后用一個槽函數處理

? ? //收到激活消息之后顯示主窗口

? ? QObject::connect(&server, &LocalServer::newMessage, [&](const QString &message){

? ? ? ? if(message == ACTIVE_MESSAGE){

? ? ? ? ? ? qDebug()<<"recv active message show normal window"<<endl;

? ? ? ? ? ? w.showNormal();

? ? ? ? }

? ? });

? ? //所有窗口都關閉之后不退出程序,不設置的話如果關閉主窗口之后,在任務托盤上打開"關于"窗口之后,關閉"關于"窗口

? ? //會導致進程退出(可能是所有可見窗口都關閉了所以進程自動退出了)

? ? a.setQuitOnLastWindowClosed(false);

? ? //根據命令行參數來決定程序啟動的時候是否顯示主窗口,還是直接最小化到任務托盤

? ? //一般如果程序是隨系統自啟動的情況下,讓他直接最小化到任務托盤

? ? //如果是用戶自己雙擊啟動的情況,就顯示主窗口

? ? //判斷程序是隨系統自啟動還是用戶雙擊啟動的區別,就是有沒有命令行參數

? ? //用戶雙擊啟動是不會有命令行參數的

? ? 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 包裝的本地服務和客戶端。客戶端的主要代碼如下:

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

? ? }

}

服務端的主要代碼如下:

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)

{

? ? // 如果已經有一個實例在運行了就返回0

? ? if (isServerRun(servername)) {

? ? ? ? return false;

? ? }

? ? m_server = new QLocalServer;

? ? // 先移除原來存在的,如果不移除那么如果

? ? // servername已經存在就會listen失敗

? ? QLocalServer::removeServer(servername);

? ? // 進行監聽

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

}

// 可以讀數據了

void LocalServer::readyRead()

{

? ? // 取得是哪個localsocket可以讀數據了

? ? QLocalSocket *local = static_cast<QLocalSocket *>(sender());

? ? if (!local)

? ? ? ? return;

? ? QTextStream in(local);

? ? QString? ? readMsg;

? ? // 讀出數據

? ? readMsg = in.readAll();

? ? // 發送收到數據信號

? ? emit newMessage(readMsg);

}

// 判斷是否有一個同名的服務器在運行

bool LocalServer::isServerRun(const QString & servername)

{

? ? // 用一個localsocket去連一下,如果能連上就說明

? ? // 有一個在運行了

? ? QLocalSocket ls;

? ? ls.connectToServer(servername);

? ? if (ls.waitForConnected(1000)){

? ? ? ? // 說明已經在運行了

? ? ? ? ls.disconnectFromServer();

? ? ? ? ls.close();

? ? ? ? return true;

? ? }

? ? return false;

}

這樣就通過 QLocalServer 和 QLocalSocket 實現了單進程,實際上這里的服務端并沒有監聽一個端口,在windows的實現中是通過命名管道的,這個管道在我們程序啟動之后被打開,在進程退出之后就自動沒有了。當程序啟動的時候啟動服務端,如果另一個程序也啟動,他會發現啟動通過一個名字的服務端失敗了,說明之前已經有一個服務端已經啟動了,這個時候他只需要以客戶端的方式連接之前啟動的服務端,給他發一個消息,那個服務端收到消息之后顯示其主窗口即可。

回到頂部

Qt寫注冊表實現自啟動

我們通過在注冊表項中添加項目來實現程序的自啟動,在Qt中寫注冊表是很簡單的,主要代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//開機自啟動注冊表鍵

#define REG_RUN "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"

//設置程序開機自啟動

void Tools::setAutoRunning(bool is_auto_start)

{

? ? QString application_name = QApplication::applicationName();

? ? QSettings *settings = new QSettings(REG_RUN, QSettings::NativeFormat);

? ? if(is_auto_start)

? ? {

? ? ? ? //程序執行文件的路徑 F:\\xxx\\xxx\zz.exe

? ? ? ? QString application_path = QApplication::applicationFilePath();

? ? ? ? application_path += " " ;

? ? ? ? //加上命令行參數? 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;

}

效果是這樣:

回到頂部

在線程中請求http或者https資源

我們需要通過網絡去請求json數據,以及下載壁紙圖片,在Qt中是通過 QNetworkAccessManager 來實現的,線程的功能代碼如下:

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

//定時任務讀取json數據,并從解析出的json數據中獲取要下載的圖片url

//進一步下載圖片,這里面刪除了一些無關的代碼,保留了關鍵的代碼

void DownloadThread::run(){

? ? QNetworkAccessManager* manager = new QNetworkAccessManager();

? ? while(true){

? ? ? ? QThread::sleep(TIME_INTERVAL_MINS*60);//secs<br>? ? ? ? //輸出當前Qt支持的ssl版本情況

? ? ? ? //qDebug()<<"QSslSocket="<<QSslSocket::sslLibraryBuildVersionString();<br>? ? ? ? //輸出當前Qt是否支持ssl,如果支持這里返回true,否則返回false

? ? ? ? //qDebug() << "OpenSSL支持情況:" << QSslSocket::supportsSsl();

? ? ? ? QEventLoop loop;

? ? ? ? QObject::connect(manager, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);

? ? ? ? //request對象

? ? ? ? QNetworkRequest request;

? ? ? ? //設置UserAgent頭

? ? ? ? request.setHeader(QNetworkRequest::UserAgentHeader, USER_AGENT_FIREFOX);

? ? ? ? //設置需要請求的資源url,例如 http://www.abc.com/xx.jpg

? ? ? ? request.setUrl(QUrl(BING_JSON_URL));

? ? ? ? //調用 QNetworkAccessManager 的get函數發送http請求

? ? ? ? QNetworkReply *reply = manager->get(request);

? ? ? ? loop.exec();

? ? ? ? //當請求完成之后通過 QNetworkReply 的readAll()讀取響應結果

? ? ? ? //readAll()函數返回的是一個 QByteArray 如果響應是一個文本,我們可以賦值給一個QString,比如這里是一個json文本

? ? ? ? QString json = reply->readAll();

? ? ? ? //刪除該reply對象

? ? ? ? reply->deleteLater();

? ? ? ? BingImageDataInfo info;

? ? ? ? //解析json數據,這里面通過 QJsonDocument 相關的類來解析json數據,很簡單,就不展示 parseJson() 的實現了

? ? ? ? bool parseRes = parseJson(json,info);

? ? ? ? qDebug()<<"parse"<<endl;

? ? ? ? if(parseRes){ // added to db and mem

? ? ? ? ? ? //待下載的圖片的url路徑

? ? ? ? ? ? QString downloadUrl = BING_DOMAIN+info.Url;

? ? ? ? ? ? //重新設置request的url

? ? ? ? ? ? request.setUrl(QUrl(downloadUrl));

? ? ? ? ? ? //發送請求獲取圖片內容

? ? ? ? ? ? QNetworkReply *replyDownload = manager->get(request);

? ? ? ? ? ? loop.exec();

? ? ? ? ? ? QFile fDownload(imageFilePath);

? ? ? ? ? ? if(fDownload.open(QIODevice::WriteOnly)){

? ? ? ? ? ? ? ? fDownload.write(replyDownload->readAll());//讀取響應結果并寫入磁盤中

? ? ? ? ? ? ? ? fDownload.close();

? ? ? ? ? ? }

? ? ? ? ? ? replyDownload->deleteLater();

? ? ? ? ? ? }

? ? ? ? }

? ? } // end while true

}

USB Microphone https://www.soft-voice.com/

Wooden Speakers? https://www.zeshuiplatform.com/

亞馬遜測評 www.yisuping.cn

深圳網站建設www.sz886.com

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容

  • 界面 主窗口界面設計 標題欄:直接設Window-Title屬性;Window-icon屬性可加圖標。底部狀態欄:...
    碼園老農閱讀 3,778評論 1 13
  • 此文章將記錄我在Qt開發過程中遇到的問題及相關知識技術點 ,方便自己以后查閱..... 博客原文: http://...
    遇見_c256閱讀 1,429評論 2 17
  • Qt樣式表示是一個可以自定義部件外觀的十分強大的機制 使用代碼設置樣式表ui->pushButton->setSt...
    愛笑的人26閱讀 476評論 0 0
  • 1、帶菜單欄的窗口2、資源文件3、對話框4、界面布局5、常用控件6、自定義控件 1QMainWindow1.1菜單...
    MagicalGuy閱讀 626評論 0 0
  • 彩排完,天已黑
    劉凱書法閱讀 4,260評論 1 3