Qt重寫(xiě)QTreeView自繪實(shí)現(xiàn)酷炫樣式
導(dǎo)語(yǔ)
Hi,各位讀者朋友,大家好。相信大家在日常的工作中,經(jīng)常會(huì)接觸到QTreeView這個(gè)控件吧!
QTreeView,顧名思義,就是一種樹(shù)形的控件,在我們需要做類(lèi)似于文件列表的視圖時(shí),是一個(gè)不錯(cuò)的選擇。然而,僅通過(guò)設(shè)置樣式表,往往無(wú)法完全滿(mǎn)足我們的需求。迫不得已,我們只能選擇自實(shí)現(xiàn)QTreeView的繪制事件,通過(guò)畫(huà)筆,逐個(gè)繪制我們想要的效果。
關(guān)于QTreeView的樣式表部分,Qt官方給出了一些示例:Customizing QTreeView , 本文不作具體介紹,感興趣的讀者可以自行觀看。
接下來(lái),咱們通過(guò)一下幾個(gè)步驟,逐個(gè)分析,怎么達(dá)到上圖的效果。
- 整個(gè)控件的背景色
- 單元格的效果
- 單擊和雙擊選擇的效果
整個(gè)控件的背景色
話不多說(shuō),碼來(lái)!
void MyTreeView::paintEvent(QPaintEvent* event)
{
int count = m_pModel->rowCount(this->rootIndex());
QPainter painter(this->viewport());
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(40, 44, 52));
painter.drawRect(this->viewport()->rect());
// 列表為空時(shí),繪制空狀態(tài)
if (count == 0) {
QPoint startPos(this->width() / 2 - 72, this->height() / 2);
QFont font("Microsoft YaHei UI");
font.setPixelSize(12);
painter.setFont(font);
painter.setPen(QColor(153, 154, 161));
painter.setBrush(Qt::NoBrush);
painter.drawText(startPos.x(), startPos.y() + 64 + 8, 148, 32, Qt::AlignCenter | Qt::TextWordWrap, "文件夾為空");
return;
}
QTreeView::paintEvent(event);
}在paintEvent中,首先在整個(gè)QTreeView的區(qū)域,繪制了一個(gè)背景色。其次判斷model中是否沒(méi)有數(shù)據(jù),如果沒(méi)有數(shù)據(jù),則居中繪制一個(gè)空狀態(tài)。
單元格的效果
繪制單元格,我們通過(guò)drawRow去為每一個(gè)單元格繪制我們想要的效果。
void MyTreeView::drawRow(QPainter* painter, const QStyleOptionViewItem& options, const QModelIndex& index) const
{
if (!index.isValid())
return;
painter->save();
painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
QString path = m_pModel->filePath(index);
QFileInfo file(path);
QRect rect = options.rect;
painter->setPen(Qt::NoPen);
//判斷狀態(tài)
bool bNormal = false, bDClick = false, bClick = false;
if (path == m_selectPath)
{
bDClick = true;
} else if (index == currentIndex()) {
bClick = true;
} else {
bNormal = true;
}
// 計(jì)算當(dāng)前Index的目錄相對(duì)root目錄的層級(jí)
int rootPathSize = m_pModel->rootPath().size();
int indexPathSize = m_pModel->filePath(index).size();
QString relativePath = m_pModel->filePath(index).mid(rootPathSize, indexPathSize - rootPathSize);
int indexTier = relativePath.split('/').size() - 1;
// 當(dāng)前index的縮進(jìn)
int indexIndentation = (indexTier - 1) * TierIndentation + 26;
//QString theme = theme_manager::Instance()->GetThemeName();
#pragma region 繪制底色
if (bNormal)
{
QBrush brush = QBrush(QColor(40, 44, 52));
painter->setBrush(brush);
painter->drawRect(rect);
}
#pragma endregion
#pragma region 繪制選中樣式
if (!file.isDir() && bDClick)
{
QBrush brush = QBrush(QColor(50, 56, 66));
painter->setBrush(brush);
painter->drawRect(rect);
}
#pragma endregion
#pragma region 繪制單擊樣式
if (bClick)
{
QBrush brush = QBrush(QColor(44, 49, 58));
painter->setBrush(brush);
painter->drawRect(rect);
}
#pragma endregion
#pragma region 繪制圖標(biāo)
// 畫(huà)圖標(biāo)
int iconWidth = file.isDir() ? FOLDER_WIDTH : ARROW_WIDTH;
int iconHeight = file.isDir() ? FOLDER_HEIGHT : ARROW_HEIGHT;
// (iconWidth - ARROW_HEIGHT)是繪制普通文件時(shí),應(yīng)該多往右邊移動(dòng)一點(diǎn)
int xIcon = rect.x() + indexIndentation - (iconWidth - FOLDER_WIDTH);
int yIcon = rect.y() + (SectionHeight - iconHeight) / 2;
QRect iconRec(QPoint(xIcon, yIcon), QSize(iconWidth, iconHeight));
if (file.isDir())
{
// 繪制文件夾展開(kāi)與收起樣式
isExpanded(index) ? painter->drawImage(iconRec, m_ImageTreeFolderExpand)
: painter->drawImage(iconRec, m_ImageTreeFolder);
}
else
{
QRect iconRec(QPoint(xIcon, yIcon), QSize(14, 14));
painter->drawImage(iconRec, m_ImageCppFile);
}
#pragma endregion
#pragma region 繪制文字
QString text = index.data(Qt::DisplayRole).toString();
// x坐標(biāo)為當(dāng)前層次縮進(jìn)+圖標(biāo)寬度(20)+文字與圖標(biāo)間距(5)
QRect leverRect(QPoint(rect.x() + indexIndentation + FOLDER_WIDTH + 4, rect.y()),
QSize(rect.width(), rect.height()));
// 繪制文字
QColor themeTextColor(204, 204, 204);
painter->setFont(m_textFont);
painter->setBrush(Qt::NoBrush);
painter->setPen(QPen(themeTextColor));
QFontMetrics fm(m_textFont);
text = fm.elidedText(text, Qt::ElideRight, m_width);
QRect boundingRect;
painter->drawText(leverRect, Qt::AlignLeft | Qt::AlignVCenter, text, &boundingRect);
#pragma endregion
#pragma region 繪制箭頭
//繪制箭頭
bool bExpand = this->isExpanded(index);
int xArrow = rect.x() + MARGIN_WIDTH;
int yArrow = rect.topLeft().y() + (SectionHeight - 8) / 2;
QRect border(xArrow, yArrow, 8, 8);
bool bDir = file.isDir(), bEmpty = true;
if (bDir)
{
QDir dir(path);
bEmpty = dir.isEmpty();
}
if (bDir && !bExpand && !bEmpty)
{
painter->drawImage(border, m_ImageExpand);
} else if (bDir && bExpand && !bEmpty)
{
painter->drawImage(border, m_ImageClose);
}
#pragma endregion
painter->restore();
}繪制的順序,需要根據(jù)各個(gè)部分實(shí)際所在的圖層順序去繪制,比如說(shuō)先要繪制底色,再去繪制上面的內(nèi)容。
單擊選擇和雙擊選擇
這一個(gè)部分,我們通過(guò)重載 mouseDoubleClickEvent 和 mousePressEvent 來(lái)監(jiān)控鼠標(biāo)的事件。
void MyTreeView::mousePressEvent(QMouseEvent* event)
{
QTreeView::mousePressEvent(event);
QModelIndex index = indexAt(event->pos());
if (!index.isValid())
return;
//是否為文件夾
const auto& path = m_pModel->filePath(index);
QFileInfo info(path);
if (!info.isDir())
return;
//是否在箭頭區(qū)域
QPoint point = event->pos();
int width = this->viewport()->width();
QRect rect(width - MARGIN_WIDTH - ARROW_WIDTH, SectionHeight - MARGIN_HEIGHT - ARROW_HEIGHT, ARROW_WIDTH, ARROW_HEIGHT);
bool bContain = rect.contains(QPoint(event->pos().x(), event->pos().y() % SectionHeight));
if (!bContain)
return;
// 如果目錄為空,不觸發(fā)箭頭交互效果
QDir tmpDir(path);
if (tmpDir.isEmpty())
return;
//展開(kāi)收縮節(jié)點(diǎn)
this->isExpanded(index) ? this->collapse(index)
: this->expand(index);
}
void MyTreeView::mouseDoubleClickEvent(QMouseEvent* event)
{
QModelIndex index = this->indexAt(event->pos());
if (index != this->currentIndex())
return;
// 處理普通文件
QString path = m_pModel->filePath(index);
if (!m_pModel->isDir(index)) {
m_selectPath = path;
}
update();
QTreeView::mouseDoubleClickEvent(event);
}通過(guò)上面的幾個(gè)函數(shù),我們就可以實(shí)現(xiàn)自定義繪制QTreeView的需求,如果還需要繪制更多的東西,則只要自己往上面添加繪制的區(qū)域即可。
一些碎碎念
本來(lái)覺(jué)得繪制一個(gè)QTreeView比較麻煩,包括在這篇博客的時(shí)候仍然是這種感覺(jué),但是通過(guò)輸出這篇博客,發(fā)現(xiàn)其實(shí)也很簡(jiǎn)單(當(dāng)然,不考慮繪制效率的情況下)。就像我剛開(kāi)始接觸Qt時(shí),總覺(jué)得自繪是一件很麻煩、很困難的事,想著全部都要自己去畫(huà)。
等到后面真正接觸到的時(shí)候,更慌了。但沒(méi)辦法,任務(wù)來(lái)了,怎么辦?硬著頭皮上唄,一頓捯飭之后,我發(fā)現(xiàn),其實(shí)也沒(méi)那么難。
所以說(shuō),剛接觸Qt的同學(xué),如果樣式表達(dá)不到自己需求,就勇敢的去嘗試自繪吧!
到此這篇關(guān)于Qt重寫(xiě)QTreeView自繪實(shí)現(xiàn)酷炫樣式的文章就介紹到這了,更多相關(guān)Qt QTreeView內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vc++實(shí)現(xiàn)的tcp socket客戶(hù)端和服務(wù)端示例
這篇文章主要介紹了vc++實(shí)現(xiàn)的tcp socket客戶(hù)端和服務(wù)端示例,需要的朋友可以參考下2014-03-03
C++實(shí)現(xiàn)和電腦對(duì)戰(zhàn)三子棋實(shí)例
大家好,本篇文章主要講的是C++實(shí)現(xiàn)和電腦對(duì)戰(zhàn)三子棋實(shí)例,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
教你Visual?Studio?2022如何新建一個(gè)C語(yǔ)言工程(圖文詳解)
這篇文章主要介紹了Visual?Studio?2022如何新建一個(gè)C語(yǔ)言工程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09
C++鏈表實(shí)現(xiàn)通訊錄管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++鏈表實(shí)現(xiàn)通訊錄管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
C++函數(shù)指針與指針函數(shù)有哪些關(guān)系和區(qū)別
函數(shù)指針是一個(gè)指針變量,它可以存儲(chǔ)函數(shù)的地址,然后使用函數(shù)指針,這篇文章主要介紹了C++中函數(shù)指針與指針函數(shù)有哪些關(guān)系和區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值2022-08-08

