簡介
本項目是一個桌面管理器的Demo,使用了DIconButton,DSpinner,DImageViewer等 DTK元件,可以檢視本地系統桌面和線上的桌面,還可以可以實作檢視桌面大圖,儲存圖檔設定桌面等功能
github倉庫:https://github.com/mhduiy/DTKWallpaperManager
建構步驟
mkdir build
cd build
cmake ..
make
詳細開發文檔
一、安裝dtk元件
因為項目采用許多dtk元件,需要我們提前安裝好dtk的各種核心庫和依賴
方法一
一句指令即可安裝Dtk相關開發環境
libdtk{core,widget,gui}-dev
等價于
deepin-sdk
sudo apt install deepin-sdk qtcreator-template-dtk
方法二
安裝 dtkcommon
git clone https://github.com/linuxdeepin/dtkcommon.git -b 5.6.4
cd dtkcommon
# 安裝依賴
sudo apt build-dep .
# 建構
dpkg-buildpackage -us -uc -b
# 安裝上一級目錄的所有deb檔案,若上一級有其他的deb檔案,請手動安裝
sudo dpkg -i ../*.deb
安裝 dtkcore
dtkcore 是一個基于Qt的C++庫,它提供了一些常用的工具類,以及一些基礎的子產品,如日志、插件、網絡、線程、資料庫、檔案、圖形、音頻、視訊、系統資訊等
git clone https://github.com/linuxdeepin/dtkcommon.git -b 5.6.4
cd dtkcommon
sudo apt build-dep .
dpkg-buildpackage -us -uc -b
sudo dpkg -i ../*.deb
安裝 dtkgui
git clone https://github.com/linuxdeepin/dtkcommon.git -b 5.6.4
cd dtkcommon
sudo apt build-dep .
dpkg-buildpackage -us -uc -b
sudo dpkg -i ../*.deb
安裝 dtkwidget
dtkwidget是dtk對于qtwidget的封裝與擴充,友善使用者快速開發符合dtk視覺風格的應用。其中重寫了部分qtwidget的控件,以及提供了一些新的控件,提供給開發者一個更輕松美觀的選擇。
git clone https://github.com/linuxdeepin/dtkcommon.git -b 5.6.4
cd dtkcommon
sudo apt build-dep .
dpkg-buildpackage -us -uc -b
sudo dpkg -i ../*.deb
dtk官方文檔連結:https://docs.deepin.org/info/%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/%E5%9F%BA%E7%A1%80%E7%8E%AF%E5%A2%83/DTK/%E5%BC%80%E5%8F%91
二、編寫CMakeLists.txt
此處列出完整的CMakeLists.txt檔案
# 配置需求cmake的最低版本
cmake_minimum_required(VERSION 3.1.0)
# 設定項目名稱,版本,語言
project(DTKWallpaperManager VERSION 1.0.0 LANGUAGES CXX)
# 指定c++的标準要求,這裡選擇c++11以上
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 設定支援Qt moc檔案
set(CMAKE_AUTOMOC ON)
# 設定支援Qt 資源檔案
set(CMAKE_AUTORCC ON)
# 尋找Qt核心庫 widget gui
find_package(Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt5 COMPONENTS Gui REQUIRED)
# 尋找Dtk核心庫 widget core gui
find_package(Dtk COMPONENTS Widget REQUIRED)
find_package(Dtk COMPONENTS Core REQUIRED)
find_package(Dtk COMPONENTS Gui)
# 尋找 src 檔案夾下的所有源檔案并儲存在 SRC 中
file(GLOB SRC src/*.cpp)
# 尋找 include檔案夾下的所有頭檔案并儲存在 HEAD 中
file(GLOB HEAD include/*.h)
# 包含 include 檔案夾下的頭檔案
include_directories(include)
# 添加可執行檔案目标,源檔案,頭檔案和資源檔案
add_executable(${PROJECT_NAME}
${SRC} ${HEAD}
resources.qrc)
# 設定項目需要的子產品及其庫檔案
target_link_libraries(DTKWallpaperManager PRIVATE
Qt5::Widgets
Qt5::Gui
Qt5::Network
${DtkGui_LIBRARIES}
${DtkCore_LIBRARIES}
${DtkWidget_LIBRARIES}
)
三、界面設計
簡要介紹
本項目目前主要功能有本地圖檔,線上圖檔,檢視大圖,儲存桌面,設定桌面等
因為控件不算多,這裡将所有的空間都放在主界面(MainWindow)中,具體細節如下:
- MainWindow的中心控件是一個DStackedWidget
- 界面1:包含左側選擇本地桌面和線上桌面的功能清單以及右側的圖檔浏覽界面
- 界面2:包含一個DImageViewer控件,用于顯示圖檔
- 最外層DStackedWidget左側功能界面使用DListView
- 最外層DStackedWidget右側圖檔浏覽界面使用一個DStackedWidget的兩個界面來分别顯示本地圖檔和線上圖檔
- 将設定桌面和再來一組功能合并為一個按鈕放在titlebar的右側,titlebar右側還有儲存桌面和一個加載動畫的控件(DSpinner)
- titlebar左側是一個傳回按鈕,在特定的場景才會出現
設計圖展示

程式設計
該部分代碼截取自
src/mainWindow.cpp
和
include/mainWindow.h
//代碼截取自mainWindow.h
class MainWindow {
private:
QThreadPool *pool; //全局線程池
DListView *funcModLV; //線上桌面和本地桌面的功能子產品
DStackedWidget *mainStacked; //最外層stack,一個顯示主界面,一個顯示圖檔詳情
DStackedWidget *imgStacked; //圖檔流界面deepin
DWidget *mainWidget; //主界面
DImageViewer *imgViewer; //圖檔顯示
QStandardItemModel *model; //左側功能區model
DFlowLayout *imgFlowOnline; //線上桌面flow
DFlowLayout *imgFlowLocal; //本地桌面flow
DWidget *imgWidgetOnline; //線上桌面視窗
DWidget *imgWidgetLocal; //本地桌面視窗
DScrollArea *scrollareaLocal; //本地桌面滾動區域
DScrollArea *scrollareaOnline; //線上桌面滾動區域
DWidget *imgfixWidgetLocal; //本地桌面外層視窗,在scrollArea内一層,用于居中其内部的Widget(該widget為流布局),實作流布局的居中
DWidget *imgfixWidgetOnline; //線上桌面外層視窗,...
QNetworkAccessManager *networkAccessManager; //網絡連接配接管理
QVector<DIconButton*> imgsLocal; //本地圖檔
QVector<DIconButton*> imgsOnline; //線上圖檔
QHash<int, QImage*> imgsLocalMap; //從硬碟讀取的本地圖檔
QHash<int, QImage*> imgsOnlineMap; //從硬碟中讀取的線上圖檔
DPushButton *returnBtn; //傳回按鍵
DPushButton *funcBtn; //功能按鍵(重新整理頁面,設定桌面)
DPushButton *saveBtn; //儲存桌面按鈕
DSpinner *spinner; //加載spinner
};
//代碼截取自mainWindow.cpp
void MainWindow::initUI()
{
//設定titleBar部分
returnBtn = new DPushButton();
funcBtn = new DPushButton("再來一組");
spinner = new DSpinner();
saveBtn = new DPushButton("儲存桌面");
//設定加入titlebar各個元件的預設大小
returnBtn->setFixedWidth(40);
saveBtn->setFixedWidth(80);
funcBtn->setFixedWidth(80);
spinner->setFixedSize(30,30);
returnBtn->setIcon(QIcon(":/images/left-arrow.png"));
returnBtn->setIconSize(QSize(20,20));
//将設定的元件預設隐藏
returnBtn->hide();
funcBtn->hide();
spinner->hide();
saveBtn->hide();
//添加元件到titlebar并設定對齊方式
this->titlebar()->addWidget(returnBtn, Qt::AlignLeft);
this->titlebar()->addWidget(spinner, Qt::AlignRight);
this->titlebar()->addWidget(saveBtn, Qt::AlignRight);
this->titlebar()->addWidget(funcBtn, Qt::AlignRight);
//最外層stackedWidget
mainStacked = new DStackedWidget(this);
//主界面視窗
mainWidget = new DWidget();
//設定主界面的layout
QHBoxLayout *mainLayout = new QHBoxLayout(mainWidget);
//左側功能子產品按鈕區
funcModLV = new DListView();
model = new QStandardItemModel();
//向功能區添加兩個項目
model->appendRow(new DStandardItem("本地桌面"));
model->appendRow(new DStandardItem("線上桌面"));
funcModLV->setModel(model);
//設定固定大小
funcModLV->setFixedWidth(150);
//設定DListView不可編輯
funcModLV->setEditTriggers(DListView::EditTrigger::NoEditTriggers);
//右側圖檔流顯示區域
//建立滾動區域
scrollareaLocal = new DScrollArea();
scrollareaOnline = new DScrollArea();
imgStacked = new DStackedWidget();
imgWidgetLocal = new DWidget();
imgWidgetOnline = new DWidget();
imgFlowLocal = new DFlowLayout(imgWidgetLocal);
imgFlowOnline = new DFlowLayout(imgWidgetOnline);
//設定流式布局屬性
imgFlowLocal->setSpacing(20);
imgFlowOnline->setSpacing(20);
//将本地和線上的圖檔視窗設定為流式布局
imgWidgetLocal->setLayout(imgFlowLocal);
imgWidgetOnline->setLayout(imgFlowOnline);
//建立一個窗體,内部存放流布局的另外一個widget,可實作流布局視窗的居中
QVBoxLayout *localVLayout = new QVBoxLayout();
//設定居中
localVLayout->setAlignment(Qt::AlignHCenter);
imgfixWidgetLocal = new DWidget();
localVLayout->addWidget(imgWidgetLocal);
imgfixWidgetLocal->setLayout(localVLayout);
QVBoxLayout *onlineVLayout = new QVBoxLayout();
onlineVLayout->setAlignment(Qt::AlignHCenter);
imgfixWidgetOnline = new DWidget();
onlineVLayout->addWidget(imgWidgetOnline);
imgfixWidgetOnline->setLayout(onlineVLayout);
//将兩個流式布局的外層視窗添加到滾動區域中
scrollareaLocal->setWidget(imgfixWidgetLocal);
scrollareaOnline->setWidget(imgfixWidgetOnline);
//将兩個滾動區域添加到imgStacked中
imgStacked->addWidget(scrollareaLocal);
imgStacked->addWidget(scrollareaOnline);
//将左側功能區和右側圖檔流區域添加在mainLayout
mainLayout->addWidget(funcModLV);
mainLayout->addWidget(imgStacked);
//将主界面和顯示圖檔大圖的界面添加到最外層StackedWidget
mainStacked->addWidget(mainWidget);
imgViewer = new DImageViewer();
mainStacked->addWidget(imgViewer);
//設定中心界面
this->setCentralWidget(mainStacked);
}
圖檔資訊流主要利用了DFLowLayout布局,他是Dtk中的一種流布局,可以很友善的展示不确定數目的控件,搭配上DScrollAare即可實作資訊流浏覽,相關功能文檔已經放在下方
DFlowLayout 文檔連結: https://docs.deepin.org/linuxdeepin/master/dtkwidget/classDtk_1_1Widget_1_1DFlowLayout.html
四、功能設計
主要分為5個小功能子產品,分别是:
- 多線程讀取本地圖檔
- 多線程擷取網絡圖檔url
- 下載下傳圖檔
- 檢視大圖
- 儲存桌面
- 設定桌面
讀取本地圖檔
讀取本地圖檔是一種比較慢的IO操作,當圖檔的數量比較大的時候,程式啟動會十分緩慢,并會卡住一段時間,通過把讀取圖檔的操作放在子線程中執行不僅可以提升加載速度,還能避免界面阻塞。
實作本部分的功能思路如下:
- 建立一個繼承自QRunnable的類,重寫
方法,用于讀取一張本地桌面檔案run()
- MainWindow檢測系統桌面檔案夾下的檔案數量,設定同樣數量DIconButton在前端占位置,将路徑發送給讀取檔案類(FileRead)的對象,然後丢給Qt的線程池,執行讀取
- 子線程讀取成功發出讀取成功信号,将圖檔儲存QImage對象中,信号發送一個QImage指針,儲存在MainWindow中
- MainWindow将讀取到的圖檔加載到前端對應DIconButton中
這就是讀取本地圖檔的思路,相關代碼可在fileRead.cpp和mainWindow.cpp中找到
DIconButton 文檔連結: https://docs.deepin.org/linuxdeepin/master/dtkwidget/classDtk_1_1Widget_1_1DIconButton.html
讀取線上圖檔
程式代碼使用的是 unsplash 平台的api,需要自行注冊開發者擷取
Access Key
才能使用線上桌面功能,可在
void MainWindow::readOnlineWallPaper()
函數中自行修改
unsplash 官網: https://unsplash.dogedoge.com/
unsplash api 文檔:https://unsplash.dogedoge.com/documentation
注意:該平台的 api 的Demo等級每小時隻能發送50次請求(每次最多擷取30個圖檔url),若有更高需求,需要到官網提升等級
該部分的讀取方式與本地有較大差別,需要發送請求擷取圖檔url位址,然後通過url位址下載下傳圖檔,詳細思路如下:
- 擷取圖檔url,QNetworkAccessManager對象發送get請求,擷取到包含圖檔url的json格式資料
- 解析json資料,提取url
- 根據url的數量在前端設定相同數量的QIconButton占位,後續加載成功後再陸續設定button的icon
- 更換url的域名,因為 unsplash 使用了 Imgix 提供圖檔處理服務,我們将域名轉換到Imgix中,不僅可以改變圖檔寬高等樣式,經過測試,相比原url,Imgix的url讀取速度更快
- 将經過處理的url發給DownloadImage類的構造函數建立一個對象,這個類繼承自QRunnable,設定好url等資訊後,丢入線程池,進行圖檔下載下傳操作
- 下載下傳完成後發送相關信号,通知mainWindow讀取下載下傳完成的圖檔(通過QImage儲存)
- 将下載下傳完成的圖檔顯示到前端
api傳回的json資料如下(截取部分)
[
{
"id": "Hv9CS6KZayQ",
"created_at": "2022-08-31T14:36:55Z",
"updated_at": "2023-02-02T18:29:48Z",
"promoted_at": null,
"width": 8256,
"height": 5504,
"color": "#c0c0c0",
"blur_hash": "LFJ89A_N9K?cbVxbx^t6DQ-pWRt7",
"description": null,
"alt_description": null,
"urls": {
"raw": "https://images.unsplash.com/photo-1661956602944-249bcd04b63f?ixid=MnwzOTA1Mzd8MXwxfGFsbHwxfHx8fHx8Mnx8MTY3NTQyMzU4NA\u0026ixlib=rb-4.0.3",
"full": "https://images.unsplash.com/photo-1661956602944-249bcd04b63f?crop=entropy\u0026cs=tinysrgb\u0026fm=jpg\u0026ixid=MnwzOTA1Mzd8MXwxfGFsbHwxfHx8fHx8Mnx8MTY3NTQyMzU4NA\u0026ixlib=rb-4.0.3\u0026q=80",
"regular": "https://images.unsplash.com/photo-1661956602944-249bcd04b63f?crop=entropy\u0026cs=tinysrgb\u0026fit=max\u0026fm=jpg\u0026ixid=MnwzOTA1Mzd8MXwxfGFsbHwxfHx8fHx8Mnx8MTY3NTQyMzU4NA\u0026ixlib=rb-4.0.3\u0026q=80\u0026w=1080",
"small": "https://images.unsplash.com/photo-1661956602944-249bcd04b63f?crop=entropy\u0026cs=tinysrgb\u0026fit=max\u0026fm=jpg\u0026ixid=MnwzOTA1Mzd8MXwxfGFsbHwxfHx8fHx8Mnx8MTY3NTQyMzU4NA\u0026ixlib=rb-4.0.3\u0026q=80\u0026w=400",
"thumb": "https://images.unsplash.com/photo-1661956602944-249bcd04b63f?crop=entropy\u0026cs=tinysrgb\u0026fit=max\u0026fm=jpg\u0026ixid=MnwzOTA1Mzd8MXwxfGFsbHwxfHx8fHx8Mnx8MTY3NTQyMzU4NA\u0026ixlib=rb-4.0.3\u0026q=80\u0026w=200",
"small_s3": "https://s3.us-west-2.amazonaws.com/images.unsplash.com/small/photo-1661956602944-249bcd04b63f"
},
"links": {
"self": "https://api.unsplash.com/photos/Hv9CS6KZayQ",
"html": "https://unsplash.com/photos/Hv9CS6KZayQ",
"download": "https://unsplash.com/photos/Hv9CS6KZayQ/download?ixid=MnwzOTA1Mzd8MXwxfGFsbHwxfHx8fHx8Mnx8MTY3NTQyMzU4NA",
"download_location": "https://api.unsplash.com/photos/Hv9CS6KZayQ/download?ixid=MnwzOTA1Mzd8MXwxfGFsbHwxfHx8fHx8Mnx8MTY3NTQyMzU4NA"
},
"likes": 252,
"liked_by_user": false,
"current_user_collections": [],
"sponsorship": {
"impression_urls": [
"https://ad.doubleclick.net/ddm/trackimp/N1224323.3286893UNSPLASH/B29258209.358599301;dc_trk_aid=549473985;dc_trk_cid=185855956;ord=[timestamp];dc_lat=;dc_rdid=;tag_for_child_directed_treatment=;tfua=;ltd=?",
"https://pixel.adsafeprotected.com/rfw/st/1337634/69218574/skeleton.gif?gdpr=${GDPR}\u0026gdpr_consent=${GDPR_CONSENT_278}\u0026gdpr_pd=${GDPR_PD}",
"https://track.activemetering.com/pixel/v1/all/pixel.gif?cid=b7348795-483d-4f08-879b-cb4f93b6f5dc\u0026creativeId=185855956\u0026placementId=358599301"
],
"tagline": "Reach the right shoppers and increase orders",
"tagline_url": "https://ad.doubleclick.net/ddm/trackclk/N1224323.3286893UNSPLASH/B29258209.358599301;dc_trk_aid=549473985;dc_trk_cid=185855956;dc_lat=;dc_rdid=;tag_for_child_directed_treatment=;tfua=;ltd=",
"sponsor": {
"id": "D-bxv1Imc-o",
"updated_at": "2023-02-03T09:48:45Z",
"username": "mailchimp",
"name": "Mailchimp",
"first_name": "Mailchimp",
"last_name": null,
"twitter_username": "Mailchimp",
"portfolio_url": "https://mailchimp.com/",
"bio": "The all-in-one Marketing Platform built for growing businesses.",
"location": null,
"links": {
"self": "https://api.unsplash.com/users/mailchimp",
"html": "https://unsplash.com/de/@mailchimp",
"photos": "https://api.unsplash.com/users/mailchimp/photos",
"likes": "https://api.unsplash.com/users/mailchimp/likes",
"portfolio": "https://api.unsplash.com/users/mailchimp/portfolio",
"following": "https://api.unsplash.com/users/mailchimp/following",
"followers": "https://api.unsplash.com/users/mailchimp/followers"
},
"profile_image": {
"small": "https://images.unsplash.com/profile-1609545740442-928866556c38image?ixlib=rb-4.0.3\u0026crop=faces\u0026fit=crop\u0026w=32\u0026h=32",
"medium": "https://images.unsplash.com/profile-1609545740442-928866556c38image?ixlib=rb-4.0.3\u0026crop=faces\u0026fit=crop\u0026w=64\u0026h=64",
"large": "https://images.unsplash.com/profile-1609545740442-928866556c38image?ixlib=rb-4.0.3\u0026crop=faces\u0026fit=crop\u0026w=128\u0026h=128"
},
"instagram_username": "mailchimp",
"total_collections": 0,
"total_likes": 19,
"total_photos": 13,
"accepted_tos": true,
"for_hire": false,
"social": {
"instagram_username": "mailchimp",
"portfolio_url": "https://mailchimp.com/",
"twitter_username": "Mailchimp",
"paypal_email": null
}
}
}
}
]
注意:這裡第個步驟顯示到前端的時候,需要設定一個1ms的事件循環來等待ui成功加載,再通過
DFlowLayout::heightForWidth()
設定正确高度,否則這裡擷取到的高度會不正确,導緻界面顯示異常
相關代碼可以在downloadImage.cpp和mainwindow.cpp中找到
下載下傳圖檔
這個子產品在downloadImage中,擷取到mainWindow發送來的url後,QNetworkAccessManager發出圖檔url的 get請求,下載下傳圖檔在QNetworkReply對象中,這裡設定一個局部事件循環,事件循環用于等待下載下傳圖檔的完成,若下載下傳完成則結束事件循環,若下載下傳逾時,則事件循環退出,發送下載下傳失敗的信号
檢視大圖
這個功能主要使用到了Dtk的DImageViewr控件,控件提供了基本的圖像檢視能力
DImageViewer 文檔連結: https://docs.deepin.org/linuxdeepin/master/dtkwidget/classDtk_1_1Widget_1_1DImageViewer.html
使用方法非常簡單,通過
setImage()
函數将我們的QImage指針傳遞進去即可,除此之外,還可以使用
autoFitImage()
來讓圖檔顯示到合适的比例,其他的功能還請閱讀相關文檔
儲存桌面
這個功能使用了Dtk的DFileDialog對話框控件,這裡隻是使用了最基礎的功能,Dtk還提供在檔案選擇對話框上添加下拉框,文本輸入框的功能,詳見文檔:
DFileDialog 文檔連結: https://docs.deepin.org/linuxdeepin/master/dtkwidget/classDtk_1_1Widget_1_1DFileDialog.html
程式中使用QImage儲存來自網絡或者本地的圖檔資訊,利用QImage的
save()
函數輸出圖檔到檔案,預設jpg格式
設定桌面
在mainWindow中使用QImage來儲存圖檔資訊,設定桌面的時候需要先将桌面臨時儲存在本地再進行設定,這裡使用xrandr指令擷取螢幕資訊,dbus指令來設定桌面,通過QProcess對象來調用這個指令完成設定,設定成功後mainWindow會發送設定成功的消息,并且删除臨時檔案
指令
析構圖檔記憶體
程式中主要使用申請在堆的記憶體來存儲QImage形式的圖檔,如果在第二次擷取線上桌面的時候不及時析構的話會造成很嚴重的記憶體洩露問題,本程式中在imgsLocalMap和imgsOnlineMap中儲存了QImage的的指針,隻需要析構這裡的就可以,另外因為設定在前端的QIconButton也是動态配置設定記憶體的,重新整理頁面的時候需要移除這些DIconButton并且釋放掉這些記憶體,這部分邏輯在mainWindow的
removeAllImgs()
中實作
總結
到這裡,整個程式基本結構已經分析完成,在程式源代碼中每一個部分都有詳細的代碼注釋,歡迎大家交流學習