做優(yōu)惠卷網(wǎng)站倒閉了多少錢(qián)剪輯培訓(xùn)班一般學(xué)費(fèi)多少
0 環(huán)境
- Windows 11
- Qt 5.15.2 MinGW x64
1 系列文章
簡(jiǎn)介:本系列文章,是以純代碼方式實(shí)現(xiàn) Qt 控件的重構(gòu),盡量不使用 Qss 方式。
《[Qt]QListView 重繪實(shí)例之一:背景重繪》
《[Qt]QListView 重繪實(shí)例之二:列表項(xiàng)覆蓋的問(wèn)題處理》
《[Qt]QListView 重繪實(shí)例之三:滾動(dòng)條覆蓋的問(wèn)題處理》
《[Qt]QListView 重繪實(shí)例之四:效果一講解》
《[Qt]QListView 重繪實(shí)例之五:效果二講解》
2 開(kāi)始
自定義 Qt 控件,無(wú)外乎兩個(gè)主要目的:
- 實(shí)現(xiàn)更漂亮的樣式;
- 實(shí)現(xiàn)更強(qiáng)大的/更合適的功能;
要實(shí)現(xiàn)以上兩個(gè)主要目的,基本上都需要對(duì) Qt 原生控件進(jìn)行一定的重繪,以適應(yīng)需求。
本節(jié)中,主要講解 QListView
的背景繪制。
(之所以單獨(dú)寫(xiě)一文,是因?yàn)樽约簞?dòng)手實(shí)現(xiàn)時(shí)才發(fā)現(xiàn):雖然最后的實(shí)現(xiàn)代碼并不多,但要弄懂這些,還是要花費(fèi)很多精力的。)
→ 解決方案直達(dá) ←
3 paintEvent
重繪與問(wèn)題
通常,重構(gòu)一個(gè)新控件,基本上都是直接重寫(xiě) void paintEvent(QPaintEvent *event)
方法。
void PListView::paintEvent(QPaintEvent *event)
{Q_UNUSED(event)QPainter painter(this); // Errorpainter.setRenderHint(QPainter::Antialiasing);painter.setPen(QPen(Qt::red));painter.setBrush(QBrush(Qt::white));painter.drawRoundedRect(rect(), 5, 5);
}
3.1 問(wèn)題 1 —— 繪圖對(duì)象
通常,進(jìn)行重繪時(shí),新建 QPainter
對(duì)象都是以父控件為對(duì)象,意即在父控件中進(jìn)行繪制。
但是,如果這樣直接對(duì) QListView
進(jìn)行重繪,是會(huì)出錯(cuò)的:
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setRenderHint: Painter must be active to set rendering hints
QPainter::setPen: Painter not active
QPainter::setBrush: Painter not active
(猜測(cè))原因大致應(yīng)該是:QListView
是由多個(gè)子控件組成,實(shí)際負(fù)責(zé)顯示內(nèi)容的只是其中的一個(gè)子控件,所以繪制對(duì)象需要具體指定到負(fù)責(zé)顯示的對(duì)象。
QListView
繼承樹(shù)如下:
而一個(gè)默認(rèn) QListView
對(duì)象包含的子控件如下:
(QWidget(0x1eb4600, name = "qt_scrollarea_viewport"),
QStyledItemDelegate(0x1eb1840),
QItemSelectionModel(0x1eb1ba0),
QWidget(0x1eb1010, name = "qt_scrollarea_hcontainer"),
QWidget(0x1eb1150, name = "qt_scrollarea_vcontainer"))
其中,實(shí)際顯示內(nèi)容的對(duì)象就是 “qt_scrollarea_viewport”,也就是 QListView
的視口(viewport)。這樣做的主要原因,是要實(shí)現(xiàn)對(duì) QListView
內(nèi)容的滾動(dòng)顯示(顯示部分內(nèi)容)。
所以,對(duì)于 QListView
重繪,必須要針對(duì)視口 viewport()
。
void PListView::paintEvent(QPaintEvent *event)
{Q_UNUSED(event)QPainter painter(viewport());painter.setRenderHint(QPainter::Antialiasing);painter.setPen(QPen(Qt::red));painter.setBrush(QBrush(Qt::white));painter.drawRoundedRect(rect(), 5, 5);
}
效果如下圖示:
3.2 問(wèn)題 2 —— 外邊線框
從上圖看,這次倒是繪制出背景框。但首先注意到的問(wèn)題是 QListView
默認(rèn)外連線框,非常顯眼。因此,首要目的是要去掉這個(gè)外連線框。
上文實(shí)現(xiàn)代碼中的重繪過(guò)程,僅做了兩件事:
- 繪制了一個(gè)圓角矩形;
- 阻止了
QListView
的其它默認(rèn)繪制;
因此 ,基本可以肯定,外連線框并不是由 paintEvent()
繪制過(guò)程中引起的??磥?lái)原因得到 QListView
里層查找。
原因查找的具體過(guò)程略過(guò)不述,QListView
的外邊線框其實(shí)就是其父類 QFrame
的邊框(可以理解為一個(gè)底層,其它內(nèi)容都繪制在這個(gè)底層之上,畢竟 QListView
是 UI 控件)。
只需要對(duì) QListView
進(jìn)行如下設(shè)置,改變一下 QFrame
樣式即可去掉外邊線框:
PListVeiw::PListView(QWidget *parent) : QListView(parent)
{setFrameStyle(QFrame::NoFrame);
}
效果如下:
強(qiáng)制隱藏/關(guān)閉垂直滾動(dòng)條,效果如下:
3.3 問(wèn)題 3 —— 繪制區(qū)域
從上圖可知,繪制的背景效果基本出來(lái)了。但是,也被垂直滾動(dòng)條擋住了一部分。
再回來(lái)看一看繪圖代碼,其中有一行如下:
painter.drawRoundedRect(rect(), 5, 5);
此時(shí),指定的繪圖區(qū)域?yàn)?rect()
,即針對(duì)控件的整個(gè)顯示區(qū)域。而我們指定的繪圖對(duì)象是 QListView
的視口,原則上為了保證一致性,在什么上繪圖,就應(yīng)該在該對(duì)象的區(qū)域內(nèi)進(jìn)行繪制。所以,修改以上那行的代碼:
painter.drawRoundedRect(viewport()->rect(), 5, 5);
效果如下:
這種效果,也還可以。一些樣式也確實(shí)是將滾動(dòng)條置于控件之外的。
本文不針對(duì)此樣式進(jìn)行講解,主要考慮滾動(dòng)條內(nèi)含在列表內(nèi)的樣式。
滾動(dòng)條的問(wèn)題,先按下不提,具體詳見(jiàn)本系列后文說(shuō)明。參考《[Qt]QListView 重繪實(shí)例之三:滾動(dòng)條覆蓋的問(wèn)題處理》。
3.4 問(wèn)題 4 —— 滾動(dòng)時(shí)殘留
先前為了重點(diǎn)顯示 QListView
的背景繪制效果,所以沒(méi)有繪制 QListView
的內(nèi)容。
現(xiàn)在,加上內(nèi)容的繪制代碼:
void PListView::paintEvent(QPaintEvent *event)
{QPainter painter(viewport());painter.setRenderHint(QPainter::Antialiasing);painter.setPen(QPen(Qt::red));painter.setBrush(QBrush(Qt::white));painter.drawRoundedRect(rect(), 5, 5); // 理解為視口占據(jù)整個(gè)控件區(qū)域QListView::paintEvent(event);
}
說(shuō)明:繪制順序是有要求的。應(yīng)該先繪制背景,然后繪制列表內(nèi)容(即前景)。
效果圖如下:
但是,如果我們使用鼠標(biāo)滾輪滾動(dòng)或拖動(dòng)滾動(dòng)條,滾動(dòng) QListView
的內(nèi)容,卻出現(xiàn)了如下效果:
這顯然不是想要的效果。
具體原因未深究,暫時(shí)未知,猜測(cè)應(yīng)該是底層代碼的原因。因?yàn)?#xff0c;上文中的重繪代碼其實(shí)很簡(jiǎn)單,并未做多余的動(dòng)作。
但這種殘留效果,顯然不可接受。
因此,至少到目前,這種方式繪制 QListView
的背景是不可行的。
(考慮到添加委托會(huì)對(duì)列表項(xiàng)進(jìn)行繪制,可能會(huì)影響到這個(gè)殘留問(wèn)題。嘗試過(guò)添加委托,但這個(gè)殘留問(wèn)題依然存在。)
4 解決方案
從上文得知,采用 paintEvent()
對(duì) QListView
背景進(jìn)行繪制的方案不可行。
另,考慮到后來(lái)的 Qt 版本對(duì)于 Qss 的性能問(wèn)題,本系列也不考慮 Qss 方案。
于是,已知可行的方案只剩使用 QProxyStyle
代理樣式定制了。
(之前也沒(méi)有實(shí)際使用過(guò)代理樣式,通過(guò)學(xué)習(xí)/練習(xí)/測(cè)試得出了合適的效果。)
關(guān)于 QProxyStyle
的具體內(nèi)容,查找資料的過(guò)程中有發(fā)現(xiàn),有不少介紹的好博文,請(qǐng)酌情參考(文末參考資料有鏈接),本文不另述。
4.1 定義背景繪制樣式
/* .h */
class PListViewStyle : public QProxyStyle
{
public:PListViewStyle();void drawControl(QStyle::ControlElement element,const QStyleOption *option,QPainter *painter,const QWidget *widget = nullptr) const override;
};/* .cpp */
PListViewStyle::PListViewStyle()
{
}
void PListViewStyle::drawControl(QStyle::ControlElement element,const QStyleOption *option,QPainter *painter,const QWidget *widget) const
{switch(element){case QStyle::CE_ShapedFrame:{const QStyleOptionFrame *opt = qstyleoption_cast<const QStyleOptionFrame *>(option);if(nullptr == opt) { return; }painter->save();painter->setRenderHint(QPainter::Antialiasing);painter->setPen(QPen(Qt::red));painter->setBrush(QBrush(Qt::white));painter->drawRoundedRect(opt->rect, 5, 5);painter->restore();return;}default:break;}QProxyStyle::drawControl(element, option, painter, widget);
}
4.2 使用代理樣式
PListVeiw::PListView(QWidget *parent) : QListView(parent)
{// setFrameStyle(QFrame::NoFrame); // Must delete or comment itsetStyle(new PListViewStyle);
}
注意:
- 需要?jiǎng)h除重寫(xiě)函數(shù)
void paintEvent(QPaintEvent *event)
,否則可能覆蓋效果。 - 需要?jiǎng)h除對(duì)
QFrame
的樣式設(shè)置,不能再設(shè)置為QFrame::NoFrame
。因?yàn)榇順邮綄?shí)際是對(duì)QFrame
進(jìn)行繪制的,如果設(shè)置了QFrame::NoFrame
,則繪制的樣式根本就不會(huì)顯示。
效果如下:
至少看上去,基本達(dá)到了預(yù)期的效果。
但是,
但是,
但是,總有但是,哈哈。
將背景的圓角矩形圓角半徑加大一下,再來(lái)看看效果圖:
從上圖可以看出有幾個(gè)問(wèn)題:
- 列表項(xiàng)在背景的上層,即背景繪制先于列表項(xiàng)。而列表項(xiàng)也是有背景的(以及高亮/選中背景),可以理解為列表項(xiàng)就是一個(gè)個(gè)小矩形(默認(rèn)沒(méi)有圓角)。由上可以看出,視口的最上/最下一行,都有矩形直角覆蓋了背景(圓角矩形),因此破壞了背景的效果;
- 同理,滾動(dòng)條也在背景上層,滾動(dòng)條也是一個(gè)直角矩形,矩形直角覆蓋了背景,因此也破壞了背景的效果;
其中:
- 對(duì)于列表項(xiàng)產(chǎn)生的覆蓋問(wèn)題,可以通過(guò)使用委托,控制列表項(xiàng)背景(默認(rèn)背景/高亮背景/選中背景)的繪制,使繪制視口最上/最下一行時(shí),繪制合適的圓角效果。
- 對(duì)于滾動(dòng)條的問(wèn)題,就復(fù)雜得多,具體詳見(jiàn)本系列后文內(nèi)容。參考《[Qt]QListView 重繪實(shí)例之三:滾動(dòng)條覆蓋的問(wèn)題處理》。
5 參考資料
- 《C++ GUI Qt 4編程(第二版)》,第 19 章,19.2 子類化 QStyle
- QStyle類用法總結(jié)(一)
- 繪制自定義QSlider