本文主要分析Windows平臺,Mac、Linux暫不涉及
本文只分析到Win32 API/Windows Com組件/STL庫函數層次,再下層代碼不做探究
本文QT版本5.15.2
領域分層圖
Mac接觸較少暫不列出
QT文件系統相關類所屬的編程框架自然是QT那套,編程語言使用C++。
在Windows和Linux系統中都是使用系統API實現功能,沒有嵌入任何內核驅動程序。
各自的系統操作各自系統內核調用文件系統去實現功能
類關系圖
QTemporaryFile繼承QFile
QFile、QSaveFile繼承QFileDevice
QFileDevice繼承QIODevice
QIODevice、QFileSystemWatcher繼承QObject
QLockFile、QFileInfo、QDir、QFileSelector無任何繼承關系
QObject中有一指向QObjectData指針d_ptr。d_ptr是保護成員變量,這意味著每個子類可以修改d_ptr內容。在QObject中d_ptr指向QObjectPrivate,
QIODevice中d_ptr指向QIODevicePrivate
QFileDevice中d_ptr指向QFileDevicePrivate
QFile中d_ptr指向QFilePrivate
QTemporaryFile中d_ptr指向QTemporaryFilePrivate
QFileInfo、QDir、QLockFile不繼承QObject,因此沒有指向QObjectData指針d_ptr。但是各自同樣聲明了d_ptr變量指向各自的private類
QFileInfo中d_ptr指向QFileInfoPrivate
QDir中d_ptr指向QDirPrivate
QLockFile中d_ptr指向QLockFilePrivate
啟發:
這種Private類書寫方式適合場景是導出接口穩定、不想公開內部實現細節、內部能夠靈活修改
可以用在付費插件、軟件逆向等使用場景
細節分層圖
QLockFile內部依賴QFile實現功能
QTemporaryFile、QSaveFile內部依賴QTemporaryFileEngine調用Win32 API、Com組件、C語言函數實現功能
QFile、QFileInfo、QDir內部依賴QFileSystemEngine調用Win32 API、Com組件、C語言函數實現功能
QFileSelector內部依賴QFileSelectorPrivate調用Win32 API、Com組件、C語言函數實現功能
QFileSystemWatcher內部依賴QWindowsFileSystemWatcherEngine調用Win32 API實現功能
QFile類是對文件進行讀寫的封裝,它提供了一系列的文件操作方法,如打開文件、讀寫文件、關閉文件等。QFile的頭文件是<QtCore/QFile>,源碼文件是qfile.cpp。
#include <QFile>
#include <QByteArray>
#include <QDebug>
int main(int argc, char *argv[])
{
QFile file("D:/1.txt");
if(file.open(QIODevice::WriteOnly))
{
file.write("Hello QFile!");
}
file.copy("2.txt");
file.close();
file.setFileName("2.txt");
QByteArray buf;
if(file.open(QIODevice::ReadOnly))
{
buf = file.readAll();
}
file.close();
qDebug() << "Read File Buffer:"<< buf;
return 0;
}
QFile::size:
QFile::size函數實際上調用的是QFileDevice::size實現,經過多層調用后到QFileSystemEngine
QFileSystemEngine通過調用系統API實現具體功能
QFile::size中QFileSystemEngine通過調用Win32 API函數GetFileAttributesEx獲取文件大小
QFile::open:
QFile::size最后通過Win32 API函數CreateFile打開文件
QFile::exists:
QFile::exists首先通過engine獲得QFSFileEngine,再通過QFileSystemEngine::fillMetaData調用GetFileAttributesEx校驗是否存在
QFile::rename:
先通過QFile::exists校驗目標文件是否存在。如果不存在通過QFSFileEngine::rename調用Win 32 API函數MoveFile實現更改文件名稱
QFile::copy:
QFile::link:
2種實現方案最主要區別是支持平臺不同
QFile::moveToTrash:
這里需要注意win7以下和win7以上具體實現方式不一樣
QFile::remove:
QFileDevice::close:
QFile::remove:
QFile::flush:
QFile::flush實際上是繼承自QFileDevice::flush,調用WriteFile將QIODevicePrivate::writeBuffer寫入到文件中,并調用fflush刷新緩存
QIODevice::write:
QFileDevice::close:
獲取文件的相關屬性
#include <QFileInfo>
#include <QDir>
#include <QDateTime>
#include <QDebug>
int main(int argc, char* argv[])
{
QFileInfo file("D:/1.txt");
file.absoluteDir();
file.absolutePath();
file.fileName();
file.absoluteFilePath();
file.absolutePath();
file.baseName();
file.birthTime();
file.caching();
file.bundleName();
file.canonicalFilePath();
file.canonicalPath();
file.completeBaseName();
file.completeSuffix();
file.created();
file.dir();
file.exists();
file.fileName();
file.filePath();
file.group();
file.groupId();
file.isAbsolute();
file.isBundle();
file.isDir();
file.isExecutable();
file.isFile();
file.isReadable();
file.isRelative();
file.isRoot();
file.isHidden();
file.isNativePath();
file.isShortcut();
file.isSymLink();
file.isWritable();
file.lastModified();
file.lastRead();
file.makeAbsolute();
file.owner();
file.ownerId();
file.path();
file.permissions();
file.refresh();
return 0;
}
QFileInfo::absolutePath
獲取文件目錄的絕對路徑,此函數僅字符串操作。將目標路徑通過分割符計算出目錄位置,如果本身為目錄,獲取上一級目錄。
QFileInfo::absoluteFilePath
獲取文件的絕對路徑,此函數僅字符串操作
QFileInfo::completeSuffix
獲取文件的后綴名。注意為了兼容,第一個“.”以后都是后綴
QFileInfo::group
獲取文件所屬用戶組,windows下無效
QFileInfo::isDir
文件是否為目錄
QFileInfo::isFile
目標路徑是否為文件
QFileInfo中關于路徑文件名操作大都是字符串操作
獲取文件屬性的都是通過QFileSystemEngine::fillMetaData調用GetFileAttributesEx獲取
提供對目錄結構及其內容的訪問
#include <QDir>
int main(int argc, char* argv[])
{
QDir h("D:");
auto h2 = QDir::toNativeSeparators("D:/");
auto h3 = QDir::cleanPath("D:");
auto listFileInfo = h.entryInfoList(QStringList()<<"*", QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::DirsFirst);
auto listFileName = h.entryList(QStringList() << "*", QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::DirsFirst);
return 0;
}
目錄及導航操作:
QDir::path
返回設置的路徑
QFileSystemEntry在Qt中代表具有路徑的實體,文件夾、文件都為一個實體。里面存儲著實體的路徑名稱信息。
QDir::setPath
設置QDir路徑
QDir::absolutePath
來獲得目錄的絕對路徑。
QDir::dirName
該方法返回絕對路徑中的最后一個項目,即目錄名,但如果QDir關聯的是當前工作目錄,則返回"."。
QDir::mkdir
創建目錄,如果父目錄不存在會失敗
具體實現是win32 API函數CreateDirectory
QDir::mkpath
創建目錄,會將未創建的父目錄一起創建
QDir::rmdir
刪除目錄,目錄必須為空
QDir::rmpath
刪除目錄,目錄必須為空,并且還會將空的父目錄給刪除
最后依舊是調用的RemoveDirectory
QDir::removeRecursively
刪除目錄,包括其所有內容。
對于目錄使用QDir::rmdir,對于文件使用QFile::remove
QDir::refresh
刷新目錄內容。
QDir::cd
跳轉當前QDir目錄
QDir::exists
QDir::rename
QDir::remove
QDir::count
當前目錄及其他特定目錄
QDir::home
QDir::homePath
通過OpenProcessToken打開進程當前用戶根目錄
QDir::root
QDir::rootPath
QDir::temp
QDir::tempPath
QDir::current
QDir::currentPath
QDir::drives
文件匹配查找
QDir::match
尋找滿足匹配規則的文件夾、目錄。此函數先遍歷獲取文件名、目錄名再運用三方庫pcre2進行字符串匹配,以下是主要調用流程
QDir::entryList
同樣是通過QDirPrivate::initFileLists調用三方庫進行字符串匹配
QLockFile 是 Qt 框架中提供的一個文件鎖定機制,用于在多個進程之間同步訪問共享文件或資源。
注意此函數文件鎖定僅用在支持Qt這種創建.rmlock鎖定方式的環境使用,與windows win32 API LockFile不兼容。
LockFile是通過底層文件系統所提供的功能進行鎖定。當然如果通過CreateFile讀取整個分區,自寫文件系統讀寫可以繞過操作系統的文件鎖定。
有的軟件提供強制解除文件占用功能,利用的就是關閉系統內核對文件操作句柄。如果是通過文件映射則是通過另外一種方式解除。
在目標文件目錄創建目標文件名+.rmlock文件,在其中依次寫入進程PID、程序名稱、機器名稱、微軟GUID
#include <qlockfile.h>
int main(int argc, char* argv[])
{
QLockFile file("D:/21.txt");
file.tryLock();
return 0;
}
QLockFile::tryLock
QLockFile::lock
QLockFile::isLocked
QLockFile::unlock
創建臨時文件,臨時文件為輸入文件路徑+符號“.”+6個隨機字符
#include <QTemporaryFile>
int main(int argc, char* argv[])
{
QTemporaryFile file("D:/hx1.txt");
auto h = file.autoRemove();
auto h2 = file.open();
auto h3 = file.fileTemplate();
file.write("hello QTemporaryFile");
return 0;
}
此類無論是文件創建還是讀寫關閉都是調用QFile實現對應功能,唯一值得注意的是創建臨時文件流程。
QTemporaryFile::Open
QTemporaryFile中QFileDevicePrivate::fileEngine是指向的QTemporaryFileEngine,最終調用到std::generate創建6位數的隨機數。
注意并不是所有都是6位隨機數
提供了一個用于安全寫入文件的接口。無論程序正在寫入文件時發生崩潰還是斷電等情況,目標文件必定是完整的,要么是修改前的文件,要么是修改后的文件,不會出現數據紊亂。
寫入時先寫到臨時文件中,待寫入完成后將目標文件替換成臨時文件
#include <QSaveFile>
#include <QByteArray>
int main(int argc, char* argv[])
{
QSaveFile file("D:/1.txt");
if (file.open(QIODevice::WriteOnly))
{
file.write("Hello QFile!");
}
file.commit();
return 0;
}
QSaveFile::open
打開文件
QSaveFile::commit
將修改的內容提交。其實就是讓臨時文件替換目標文件
實現在不同環境下自動選擇不同配置文件。
#include <QFileSelector>
#include <QFile>
int main(int argc, char* argv[])
{
QFileSelector tmp;
QString filePath = tmp.select("D:/hx1.txt");
QFile file(filePath);
if (file.open(QIODevice::ReadOnly))
{
auto arrData = file.readAll();
}
return 0;
}
QFileSelector::select
監視文件夾或者文件
XFileSystemWatcher.h:
#pragma once
#include <QObject>
#include <QMap>
#include <QFileSystemWatcher>
class XFileSystemWatcher : public QObject
{
Q_OBJECT
public:
static void addWatchPath(QString path);
static void clean();
public slots:
// 目錄更新時調用,path是監控的路徑
void directoryUpdated(const QString& path);
// 文件被修改時調用,path是監控的路徑
void fileUpdated(const QString& path);
private:
explicit XFileSystemWatcher(QObject* parent = 0);
private:
static XFileSystemWatcher* m_pInstance;
QFileSystemWatcher* m_pSystemWatcher;
// 當前每個監控的內容目錄列表
QMap<QString, QStringList> m_currentContentsMap;
};
XFileSystemWatcher.cpp
#include <QDir>
#include <QFileInfo>
#include <qDebug>
#include <qglobalstatic>
#include "XFileSystemWatcher.h"
XFileSystemWatcher* XFileSystemWatcher::m_pInstance = NULL;
//Q_GLOBAL_STATIC(QFileSystemWatcher, hxTest);
XFileSystemWatcher::XFileSystemWatcher(QObject* parent)
: QObject(parent)
{}
// 監控文件或目錄
void XFileSystemWatcher::addWatchPath(QString path)
{
qDebug() << QString("Add to watch: %1").arg(path);
if (m_pInstance == NULL)
{
m_pInstance = new XFileSystemWatcher();
m_pInstance->m_pSystemWatcher = new QFileSystemWatcher();
// 連接QFileSystemWatcher的directoryChanged和fileChanged信號到相應的槽
connect(m_pInstance->m_pSystemWatcher, SIGNAL(directoryChanged(QString)), m_pInstance, SLOT(directoryUpdated(QString)));
connect(m_pInstance->m_pSystemWatcher, SIGNAL(fileChanged(QString)), m_pInstance, SLOT(fileUpdated(QString)));
}
// 添加監控路徑
m_pInstance->m_pSystemWatcher->addPath(path);
// 如果添加路徑是一個目錄,保存當前內容列表
QFileInfo file(path);
if (file.isDir())
{
const QDir dirw(path);
m_pInstance->m_currentContentsMap[path] = dirw.entryList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
}
}
void XFileSystemWatcher::clean()
{
delete m_pInstance->m_pSystemWatcher;
m_pInstance->m_pSystemWatcher = new QFileSystemWatcher();
}
// 只要任何監控的目錄更新(添加、刪除、重命名),就會調用。
void XFileSystemWatcher::directoryUpdated(const QString& path)
{
qDebug() << QString("Directory updated: %1").arg(path);
// 比較最新的內容和保存的內容找出區別(變化)
QStringList currEntryList = m_currentContentsMap[path];
const QDir dir(path);
QStringList newEntryList = dir.entryList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
QSet<QString> newDirSet = QSet<QString>::fromList(newEntryList);
QSet<QString> currentDirSet = QSet<QString>::fromList(currEntryList);
// 添加了文件
QSet<QString> newFiles = newDirSet - currentDirSet;
QStringList newFile = newFiles.toList();
// 文件已被移除
QSet<QString> deletedFiles = currentDirSet - newDirSet;
QStringList deleteFile = deletedFiles.toList();
// 更新當前設置
m_currentContentsMap[path] = newEntryList;
if (!newFile.isEmpty() && !deleteFile.isEmpty())
{
// 文件/目錄重命名
if ((newFile.count() == 1) && (deleteFile.count() == 1))
{
qDebug() << QString("File Renamed from %1 to %2").arg(deleteFile.first()).arg(newFile.first());
}
}
else
{
// 添加新文件/目錄至Dir
if (!newFile.isEmpty())
{
qDebug() << "New Files/Dirs added: " << newFile;
foreach(QString file, newFile)
{
// 處理操作每個新文件....
}
}
// 從Dir中刪除文件/目錄
if (!deleteFile.isEmpty())
{
qDebug() << "Files/Dirs deleted: " << deleteFile;
foreach(QString file, deleteFile)
{
// 處理操作每個被刪除的文件....
}
}
}
}
// 文件修改時調用
void XFileSystemWatcher::fileUpdated(const QString& path)
{
QFileInfo file(path);
QString strPath = file.absolutePath();
QString strName = file.fileName();
qDebug() << QString("The file %1 at path %2 is updated").arg(strName).arg(strPath);
}
Main.cpp
#include <QCoreApplication>
#include "XFileSystemWatcher.h"
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv);
XFileSystemWatcher::addWatchPath("D:/360");
return a.exec();
}
在內部創建監視線程,等Windows操作系統發送目標變更事件后,讀取事件解析后發出文件刪除、更改等信號。
QFileSystemWatcher::addPath
使用API函數FindFirstChangeNotification、FindNextChangeNotification進行監視。
能夠新建布局,也是數據可視化大屏界面電子看板系統中的必備功能之一,新建布局這樣的功能一般做到右鍵菜單中,單擊新建布局菜單,彈出輸入框要求輸入新的布局的名稱,為了更符合國情,直接支持中文名稱,保存成配置文件直接中文名稱命名,這樣方便用戶理解,Qt5以來對亂碼的問題解決的就比較好了,不像Qt4時代稍不留神就亂碼了,Qt5只要保證源碼文件utf-8編碼基本上就很少遇到亂碼問題了。新建布局必須要有個默認的窗體排列,Qt中的dock窗體,默認布局會以窗體的sizehint作為大小參照標準,也不一定是完全正確的,還跟窗體中的子控件有關系,不過這些都不影響布局以后重新從配置文件加載的布局,QMainWindow提供saveState()函數直接保存當前窗體的所有布局位置大小等信息到配置文件,至于配置文件的內容格式,那是人類無法理解的格式,反正我是看不懂,這些都沒有關系的,你重新用restoreState()函數加載讀取配置文件的信息時,會自動應用,這樣就很爽很完美了。
電子看板是目視化管理的一種表現形式,即對數據的狀況一目了然地表現,主要是對于管理項目,它通過利用形象直觀而又色彩適宜的各種視覺感知信息來組織現場生產活動,目視管理依據人類的生理特征,在生產現場充分利用信號燈、標識牌、符號顏色等方式來發出視覺信號,鮮明準確地刺激人的神經末梢,快速地傳遞信息,形象直觀地將潛在的問題和浪費現象都顯現出來。以便任何人都可以及時掌握管理現狀和必要的情報,從而能夠快速制定并實施應對措施。因此,管理看板是發現問題、解決問題的非常有效且直觀的手段,是優秀的現場管理必不可少的工具之一。
1. 整體總共分三級界面,一級界面是整體布局,二級界面是單個功能模塊,三級界面是單個控件。
2. 子控件包括餅圖+圓環圖+曲線圖+柱狀圖+柱狀分組圖+橫向柱狀圖+橫向柱狀分組圖+合格率控件+百分比控件+進度控件+設備狀態面板+表格數據+地圖控件+視頻控件+其他控件等。
3. 二級界面可以自由拖動懸浮,支持最小化最大化關閉,響應雙擊自定義標題欄。
4. 數據源支持數據庫采集(默認)、網絡通信、網絡請求等,可自由設定每個子界面的采集間隔即數據刷新頻率。
5. 采用純QWidget編寫,支持Qt4.6到Qt5.12.3任何版本,支持嵌入式linux比如樹莓派、香橙派、全志、imx6等。
6. 提供三個內核版本,自定義控件版本+qchart版本+echart版本。
7. 內置多套配色風格樣式,默認紫色,支持任何分辨率。
8. 可設置標題+目標分辨率+布局方案,啟動立即應用。
9. 可設置主背景顏色+面板顏色+十字線游標顏色。
10. 可設置多條曲線顏色,沒有設置顏色的情況下內置15套精美顏色隨機應用。
11. 可設置標題欄背景顏色+文字顏色。
12. 可設置曲線圖表背景顏色+文字顏色+網格顏色。
13. 可設置正常顏色+警戒顏色+報警顏色+禁用顏色+百分比進度顏色。
14. 可分別設置各種字體大小,比如全局+軟件名稱+標題欄+子標題欄+加粗標簽等。
15. 可設置標題欄高度+表頭高度+行高度。
16. 曲線支持游標+懸停高亮數據點和顯示值,柱狀圖支持頂部(可設置頂端+上部+中間+底部)顯示數據,全部自適應計算位置。
17. 主界面直接鼠標右鍵切換布局+配色方案+關閉開啟某個二級窗體。
18. 自動記憶所有子窗口的大小和位置,下次啟動立即應用。
19. 動態加載布局方案菜單,可以動態新建布局、恢復布局、保存布局、另存布局等,用戶可以制造任意布局。
20. 二級窗體,雙擊從主窗體分離出來浮動,可以自由調整大小。再次雙擊標題欄最大化,再次雙擊還原。
21. 每個模塊都可以自定義采集速度,如果是數據庫采集會自動排隊處理。
1. 可執行文件同級文件夾有layout+layout_1440+layout_1920,程序默認自動識別分辨率并加載對應的布局文件夾,比如1920分辨率則從layout_1920文件夾加載布局,并作為整體布局文件夾。
2. 程序默認是模擬數據,如果需要從數據庫采集則修改配置文件WorkMode=db即可。
3. 如果發現布局拖動亂了,可以直接鼠標右鍵選擇恢復布局即可,在保存布局以前。
4. 在中間地圖模塊鼠標右鍵可以彈出菜單,切換布局和配色方案等。
5. 在模塊的標題欄上右鍵可以彈出默認的dock菜單,用來顯示和隱藏各模塊。
6. 軟件關閉過程中會自動保存布局,下次啟動以后自動應用。
7. 如果使用的默認的默認的配色方案比如紫色風格,則配置文件中的顏色全部無效,會自動應用代碼中的顏色,如果需要啟用自定義的顏色,則將配置文件的 Theme=\x81ea\x5b9a\x4e49\x98ce\x683c 即可。此時打開軟件會應用配置文件中的顏色。
8. 右鍵菜單可以截圖保存,默認命名為 配色方案名稱_布局方案名稱.png 保存在snap目錄下。
9. 如果是XP系統請先執行fixff.cmd,用來修復ffmpeg在XP上不可用的BUG。