Qt在透明控件上自由繪畫
初學Qt,需要實現這樣的效果:
在透明的
QWidget
上自由的繪圖,看起來就像是直接在桌面繪圖一樣。
要達到這樣的效果,需要分成兩個步驟:
- 如何將
QWidget
變得透明。 - 如何在透明的
QWidget
控件上使用QPainter
繪製圖形。
當然,第二點是Widget透明之後才發現的需求,發現的過程後面會說。
兩個需求完成總共花了大概3個小時。這期間剛開始一直中文搜索國內博客,沒有解決任何一個問題。最後20來分鐘沒辦法了,英文Google一下吧,結果沒多久就找到了答案。
不知道爲啥,國內各大博客論壇,關於Qt方面的內容又舊又亂,難道是沒有什麼新鮮血液進入了麼?
爲了讓這3個小時價值最大化,於是有了這篇博客,也算是爲Qt社羣增磚添瓦吧。
此前,先把給我了幫助的兩個網頁掛出來鳴謝一下:
關於透明Widget的
Qt centre(關於透明繪製的)
接下來是正題:
如何讓QWidget變透明
先說一下被誤導的嘗試,關於讓窗體透明,網友辦法很多。
setWindowOpacity(0) ;
:設置完之後,窗體不會有任何顯示,就像你從來沒運行過程序一樣。除了特殊用途,一般也不會有這種需求。setPalette(palette)
: 其中palette給一個透明的圖片當背景,或者透明的顏色值。錯誤,設置完後,窗體會變成黑色。- …不一一列舉了…
進入整體,其實透明窗體實現相對簡單,對你想要透明的QWidget對象設置三個屬性就行了(構造函數還是main函數中設置,看你心情)。僞代碼如下:
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
稍微解釋一下:
-
Qt::FramelessWindowHint:
Produces a borderless window. The user cannot move or resize a borderless window via the window system.
產生一個無邊框的窗體。用戶無法通過窗體系統移動窗體或者改變窗體的大小。
上面是Qt幫助文檔中關於FramelessWindowHint標籤的解釋,該說的都說明了。需要關注的是,用戶無法通過普通的方式改變窗體的大小和位置了。
-
Qt::WA_NoSystemBackground:
Indicates that the widget has no background.
表明指定的widget沒有背景。
而默認的QWidget通常會自帶一個白色的背景。計算機中,對於顏色,沒有的意思一般都是黑色。例如,RGB值爲0,0,0時。eum~有點懵,爲啥要設置這個??不糾結
-
Qt::WA_TranslucentBackground:
Indicates that the widget should have a translucent background, i.e., any non-opaque regions of the widgets will be translucent because the widget will have an alpha channel. Setting this flag causes WA_NoSystemBackground to be set.
表明widget應該具有半透明的背景,即任何不透明的區域都將是半透明的。設置這個標籤將導致
Qt::WA_NoSystemBackground
被同時設置。嗯~~不透明的區域半透明!!!什麼鬼話。翻譯一下,大概就是,應該透明的地方透明,不應該透明的地方不透明,例如:沒有任何控件的地方就是應該透明的,有子
QWidget
或者QPainter
畫出的線條的地方就不是透明的。還有一點挺有意思的,這個標籤被設置後,
Qt::WA_NoSystemBackground
也會被設置,所以其實僞代碼中的setAttribute(Qt::WA_NoSystemBackground);
是可以省略的。 -
Qt::WA_TransparentForMouseEvents:
When enabled, this attribute disables the delivery of mouse events to the widget and its children.
啓用該屬性時,將禁止想widget和它的子widget傳遞鼠標事件。
這個屬性後面會用,順便在這裏解釋先。
最後,來看一下每個屬性的效果,我的工程主窗體就是一個普通的QWidget
,方便起見,顯示了一個Label
,並重寫了paintEvent
函數,使用QPainter
畫了一個橢圓。源碼會在文末附上。
上面是什麼屬性都沒有設置的QWidget
,藍色文字部分是一個Label
,紅色的橢圓是用QPainter
畫在QWidget
上的。我們要達到的透明效果是,只能看到藍色文字和紅色橢圓,其它都是桌面背景。下面通過屬性的一步步設置,仔細觀察一下界面的變化吧。
上面是setWindowFlags(Qt::FramelessWindowHint);
代碼執行後的界面。QWidget的標題框消失了,但是還有默認背景。
上面是setAttribute(Qt::WA_NoSystemBackground);
執行後的界面,還真的變黑了。不糾結
噹噹噹~,這不就是我要的效果麼。這是執行了setAttribute(Qt::WA_TranslucentBackground);
的效果。背景其實就是我的桌面背景。
接下來一個問題就是,當QWidget
變成透明背景後,居然沒法響應鼠標了,鼠標的事件沒有發給我的widget。來看看效果:
途中的
Lable
的拖動效果,是因爲我重寫了QWidget
的mouseMoveEvent
和mousePressEvent函數
。代碼大概是這樣的:void Widget::mousePressEvent(QMouseEvent *event) { mousePosition = event->globalPos(); } void Widget::mouseMoveEvent(QMouseEvent *event) { const QPoint position = pos() + event->globalPos() - mousePosition; move(position.x(), position.y()); mousePosition = event->globalPos(); }
只有鼠標在Label
上時,widget
才能夠響應鼠標的拖拽動作,在QPainter
畫出的紅色橢圓上,鼠標的效果和在桌面的效果沒啥區別。無法效應鼠標,肯定就不能作畫,畢竟我們的手繪是需要靠鼠標或者觸控筆輸入的。那麼,怎麼讓透明的窗體相應鼠標的各種事件呢?
透明QWidget上響應鼠標事件
在透明的窗體上,不能響應鼠標事件,應該是透明這個因素造成的。更深層次的原因,暫時還不瞭解。但實驗結果如此。
說說最終的解決思路吧:
- 申明一個QPixmap,變量名爲cacheMap。它將作爲整個窗體(QWidget)的底圖。
- 初始化cacheMap,用近乎透明的顏色填充。
- 在paintEvent函數中,通過QPainter將cacheMap繪製到窗體上,充作背景。
最終,雖然整個QWidget將會有一個近乎透明的背景圖,也就是cacheMap存在,但它幾乎時透明到人眼無法識別。對於計算機來說,就不是這樣了。於是我們完成了目標。
核心代碼如下:
/* 頭文件 */
QPixmap *cacheMap;
/* cpp文件 構造函數中 */
cacheMap = new QPixmap(this->size());
cacheMap->fill(QColor(0, 0, 0, 1));
/* cpp文件 paintEvent函數中 */
void Widget::paintEvent(QPaintEvent *e) {
QPainter painter(this);
painter.drawPixmap(0, 0, *cacheMap);
}
最終效果如下:
對於繪圖來講,拿到了鼠標的事件,只需要記錄鼠標軌跡,就可以通過QPainter來繪製出軌跡了。簡單粗暴點的,可以直接將鼠標的軌跡點全部用直線鏈接起來,也可以用Bezier曲線擬合,另外可以通過控制cacheMap的繪製與否,來控制鼠標是否穿透到桌面來實現特殊的效果。當然,這些又是另的話題了。
源碼下載鏈接:https://download.csdn.net/download/qq_25333681/11979920
在csdn下載可能需要點積分,如果你恰好沒有,留言留下郵箱,我發給你。