自定義QMenu樣式


自定義QMenu樣式

最近工作中需要實現一個自定義外觀的菜單,但在網上搜索后發現很少有QMenu的樣式自定義相關的深入解析。請教了公司的一位前輩,他提到QMenu自定義樣式不方便,於是他一般是自己實現一個菜單控件。但這樣未免太過於麻煩,因此經過一番摸索后基本實現了自己所需的樣式。

QMenu的子部件布局

使用過QSS(Qt Style Sheet)自定義過比較復雜的控件,如QSlider等一般都知道Qt中的控件包含一到多個subcontrol(下文翻譯為子控件)。Qt自帶的控件所包含的子控件可以在Qt stylesheet reference上查閱。但文檔上並沒有給出子控件間的相對關系。

經查閱文檔中的QMenu一欄,我們可以知道QMenu包含itemindicator,separator,right-arrow,scroller,tearoff,相對來說屬於子控件比較多的控件了,其中itemindicator,separator,right-arrow是較為為常見的子控件。為了確定子控件的相對位置關系,我們可以通過對子控件設置不同的背景色和邊框來進行查看。效果如圖所示。
QMenu subcontrol
根據圖片,我們可以得知,QMenu由若干行組成,每一行可能是一個::item::separator。注意到::indicator::right-arrow位於::item邊框內部,我們可以推出::indicatorright-arrow兩個子控件包含在::item內。::indicator位於::item的左側中央,::right-arrow位於::item的右側中央。圖片中::item的文字是和::indicator::right-arrow重疊在一起的,因此為了避免遮擋,我們需要為::item設置合適的padding-leftpadding-right

QMenu的邊框陰影

QMenu的邊框陰影我們可以通過設置背景圖片來實現。但在設置過程中,我發現QMenu的border-width屬性存在bug。
下圖是在設置了以下QSS的效果

1
2
3
4
5
6
QMenu {
background-color: #F00000;
border-width: 10px 20px 30px 40px;
border-style: solid;
border-color: #00000F;
}

wrong border width

經過多次調整比較,我發現,無論四邊border-width設置了什么樣的值,實際上的邊框寬度均為最后一個數值,設置的邊框寬度部分會顯示邊框顏色,對於多出來的邊框部分,則填充了背景色。這就導致我無法很好地顯示邊框陰影,邊框陰影往往會有一定的偏移,四邊的陰影寬度不會完全一致。經過一番嘗試,依然無解,只好與設計師協商,提供以最寬的陰影邊為邊框寬度,其他邊填充合適的透明部分的切圖暫時避開了這個問題。

另一個問題是QMenu默認是有陰影的,我們需要去掉默認的陰影。我們可以通過為QMenu添加Qt::NoDropShadowWindowHint的WindowFlag解決這個問題。

QMenu的尺寸問題

QMenu默認是會根據條目的內容動態調整自身的顯示大小的,然而在實際中當我在QSS中調整了文字的字體大小后,QMenu並沒有調整為合適的大小,導致文字顯示不全。這個問題困擾了我很久,后來在和另一個人討論這個問題時突然想到,是否可能是在QMenu根據內容確定大小時QSS尚未生效。由於QSS實際上是通過QStyle來實現具體效果的,而QStyle則是通過調用QStyle::polish方法來對控件樣式做初始化。在這個方法的文檔中有這樣一段說明:

This function is called for every widget at some point after it has been fully created but just before it is shown for the very first time.
說明QSS生效是在控件創建完成后,這個時候控件的大小已經確定,所以我們需要在控件創建完成前設置好空間的字體屬性,而不能通過QSS來設置。

QMenu的彈出位置

QMenu默認彈出位置是和設置該菜單的控件位置相關的。但有時我們需要控制菜單的彈出位置。例如,我們需要在點擊一個按鈕后彈出菜單。通常情況下,我們可以通過QPushButton::setMenu來設置一個菜單。但這種方式QMenu固定以和按鈕左對齊的方式顯示,如果我們希望彈出菜單和按鈕保持居中就無法實現。因此我們需要換一種方式。QMenu提供了QMenu::exec方法,我們可以傳入QPoint來指定菜單彈出位置。這里有兩處需要額外注意的地方。

  1. QMenu其實是一個獨立的頂層窗口,因此其位置是相對於整個桌面的,而不是相對於程序主窗口。在Qt的文檔中有以下說明:

    Pops up the menu so that the action action will be at the specified global position p. To translate a widget’s local coordinates into global coordinates, use QWidget::mapToGlobal().
    因此我們需要通過坐標轉換來得出菜單實際彈出的位置。

  2. 在計算QMenu的彈出位置時我們可能需要使用到QMenu的窗口大小屬性,然而文檔中提到了

    When positioning a menu with exec() or popup(), bear in mind that you cannot rely on the menu’s current size(). For performance reasons, the menu adapts its size only when necessary. So in many cases, the size before and after the show is different. Instead, use sizeHint() which calculates the proper size depending on the menu’s current contents.
    所以正確的獲取QMenu的窗口大小的姿勢是通過sizeHint()方法。

小結

相對來說,QMenu算是一個比較復雜的控件了。對於這類控件,我們必須先仔細閱讀官方的API文檔,Qt的文檔是相當優秀的工具,利用好這個工具可以是我們少踩很多坑。但從目前來看,Qt還存在一定的實現BUG,在實際開發中我們需要及時看清問題原因,不要在工具的BUG上花費過多時間,要善於尋找繞過BUG的途徑。

轉載:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM