前言
Qt中自定義無邊框對話框 很少有人這麼做, 但是項目上遇到這種需求,對話框使用上存在的問題就是需要實現模態對話框非激活/激活切換時候出現需要出現閃爍效果,增加用戶體驗,網上沒有很明確的解決辦法,今天花點時間研究了一下,把實現方式的關鍵寫出來給大家分享。
首先,Qt 沒有支持具體平臺的類,但是都留有平臺相關的類,這裏雖然以windows爲例,但是linux/mac下類似,都會有平臺相關的系統庫支持,下面會看到Qt的支持。
Qt 相關類和接口
對話框想要實現邊框閃爍, 關鍵是獲取系統的消息,Qt提供兩種方式接收處理處理系統消息:
1. QCoreApplication安裝NativeEventFilter全局處理的方式
通過void QCoreApplication::installNativeEventFilter(QAbstractNativeEventFilter *filterObj) 函數
安裝一個自定義本地事件過濾器QAbstractNativeEventFilter ,
QAbstractNativeEventFilter 過濾處理事件接口:
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) = 0
2. 實現QWidget 類的bool nativeEvent(const QByteArray &eventType, void *message, long *result) 函數,裏面直接處理系統消息
Windows 平臺下各個參數的含義:
MSG 即是Windows 下消息的結構體,其他平臺從Qt幫助文檔也能看到對應的消息類型:
接下來以Windows爲例,如何處理我們想要的消息類型,由於我們只想在對畫框處理系統消息,所以我使用第二種方式。
處理我們想要的消息
需要頭文件:<dwmapi.h>
、<windowsx.h>
bool nativeEvent(const QByteArray &eventType, void *message, long *result)
這裏eventType 這裏爲window 消息類型 "windows_generic_MSG"
不同系統上eventType不同
X11 爲 "xcb_generic_event_t"
macOS 爲 "mac_generic_NSEvent"
windows 爲 "windows_generic_MSG" 和 "windows_dispatcher_MSG"
message windows 下爲MSG 的結構體
result爲處理後的結果,可以修改
以下爲MSG結構體的介紹:
在Windows程序中,消息是由MSG結構體來表示的。MSG結構體的定義如下(參見MSDN):
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
第一個成員變量hwnd表示消息所屬的窗口。我們通常開發的程序都是窗口應用程序,一個消息一般都是與某個窗口相關聯的。例如,在某個活動窗口中按下鼠標左鍵,產生的按鍵消息就是發給該窗口的。在Windows程序中,用HWND類型的變量來標識窗口。
第二個成員變量message指定了消息的標識符。在Windows中,消息是由一個數值來表示的,不同的消息對應不同的數值。但是由於數值不便於記憶,所以Windows將消息對應的數值定義爲WM_XXX宏(WM是Window Message的縮寫)的形式,XXX對應某種消息的英文拼寫的大寫形式。例如,鼠標左鍵按下消息是WM_LBUTTONDOWN,鍵盤按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我們通常都是以WM_XXX宏的形式來使用消息的。
提示:如果想知道WM_XXX消息對應的具體數值,可以在Visual C++開發環境中選中WM_XXX,然後單擊鼠標右鍵,在彈出菜單中選擇goto definition,即可看到該宏的具體定義。跟蹤或查看某個變量的定義,都可以使用這個方法。
第三、第四個成員變量wParam和lParam,用於指定消息的附加信息。例如,當我們收到一個字符消息的時候,message成員變量的值就是WM_CHAR,但用戶到底輸入的是什麼字符,那麼就由wParam和lParam來說明。wParam、lParam表示的信息隨消息的不同而不同。如果想知道這兩個成員變量具體表示的信息,可以在MSDN中關於某個具體消息的說明文檔查看到。讀者可以在VC++的開發環境中通過goto definition查看一下WPARAM和LPARAM這兩種類型的定義,可以發現這兩種類型實際上就是unsigned int和long。
最後兩個變量分別表示消息投遞到消息隊列中的時間和鼠標的當前位置。
這麼多消息類型,那個是和對話框激活狀態切換有關的呢?
通過測試和查閱資料,窗口閃爍兩個狀態分別對應對話框的激活和非激活,以下打印代碼:
#ifdef Q_OS_WIN
MSG* msg = (MSG*)message;
msgTimes ++;
qDebug()<<QString(eventType)<<"msg Type:"<<"0x"+QString::number(msg->message,16)<<" times:"<<msgTimes
<<"winId:"<<msg->hwnd<<"Qt winId:"<<"0x"+QString::number(this->winId(),16)
<<"isActive:"<< this->isActiveWindow()<<msg->wParam<<msg->lParam;
#endif
我發現對話框閃爍的時候打印這一行:
"windows_generic_MSG" msg Type: "0x86" times: 2 winId: 0x350b58 Qt winId: "0x350b58" isActive: true 0 0
"windows_generic_MSG" msg Type: "0x86" times: 3 winId: 0x350b58 Qt winId: "0x350b58" isActive: true 1 0
到這裏相信大家明白了,就是0x86 這個消息類型,通過查詢頭文件
發現即是 WM_NCACTIVATE 這個消息類型,MSG所有消息類型的介紹,請參考:
https://blog.csdn.net/wanghaofeng/article/details/6632165
通過上面對MSG結構體的介紹,我們知道消息帶有兩個參數 wParam和lParam ,結合上面的程序的輸出,我們馬上就能猜想到 WParam = true/false 就是代表當前窗口是否激活狀態,注意這裏的激活和激活不是一般說的激活意思(模態窗口一般都是激活的),我們就表示閃爍的兩種狀態-明色和暗色吧,網上找到這麼一段解釋 WM_NCACTIVATE 的wParam參數:
到這裏我們就知道怎麼辦了,通過過濾我們處理 WM_NCACTIVATE 消息,通過其 wParam 參數,我們在 paintEvent裏面繪製明暗兩種顏色狀態,就實現閃爍了-快速多次閃爍時,WM_NCACTIVATE 消息會連續發送多次,wParam 1/0切換。簡單貼一下我的代碼:
FrameLessDialog::FrameLessDialog(QWidget *parent)
:QDialog(parent),m_actived(false)
{
this->setWindowFlags(Qt::FramelessWindowHint| this->windowFlags());
}
void FrameLessDialog::paintEvent(QPaintEvent *e)
{
QDialog::paintEvent(e);
QColor bkColor = Qt::darkGray;
if(m_actived)
bkColor = Qt::lightGray;
QPainter pt(this);
pt.fillRect(this->rect(),bkColor);
}
bool FrameLessDialog::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
#ifdef Q_OS_WIN
MSG* msg = (MSG*)message;
if(msg->message == WM_NCACTIVATE && (HWND)this->winId() == msg->hwnd ){
m_actived = msg->wParam;
this->update();
}
#endif
return QDialog::nativeEvent(eventType,message,result);
}
注意
if(msg->message == WM_NCACTIVATE && (HWND)this->winId() == msg->hwnd ) 這兩個判斷條件順序不能改變,否則有可能會出現段錯誤