重載windows的ncarea來自繪視窗標題列是條死路(在Vista系統上,如果主題帶有毛半透明效果,那麼無論你的視窗標題列如何自繪,都會把系統的標題列覆蓋在自繪的標題列上),因此自繪標題列的唯一辦法就是在客戶區類比非客戶區,"偽造"實現標題列的功能。我的這篇文章有些誤人。這篇文章的目的只是為了講述以前曾經遇到的一個問題。
NCLBUTTONUP表示non-client left button up, 視窗的非客戶區(也就是標題列以及邊框地區)的滑鼠左鍵點擊被釋放的訊息。
WangWang的網頁型外掛程式是一個在獨立進程裡的內嵌IE控制項的普通視窗,為了讓視窗的風格盡量和WangWang的風格一致,因此我重畫了視窗的標題列和最小化按鈕等(另外一種辦法就是乾脆不要標題列,
直接自己在客戶區裡仿造標題列)。
最小化按鈕的處理是響應WM_NCLBUTTONUP訊息,收到該訊息並且滑鼠在最小化表徵圖內,則將視窗最小化。
今天同事發現網頁型外掛程式的視窗的最小化按鈕點擊後,不太靈。我找了找原因,發現當在標題列上點擊滑鼠左鍵後,能收到WM_NCLBUTTONDOWN(按下的訊息),但是不一定能收到WM_NCLBUTTONUP
的訊息(釋放左鍵的訊息)。Google了一下,發現Windows的確有這個問題(MSDN乾脆完全就沒說明)。當在非客戶區點擊滑鼠左鍵並釋放時,系統會發送視窗的WM_LBUTTONUP(注意不是WM_NCLBUTTONUP,這是客戶區的滑鼠釋放訊息,但是我們現在在非客戶區釋放滑鼠!如之奈何)訊息到線程的訊息佇列,但線程的訊息佇列壓根就不把這個訊息發送給這個視窗的視窗過程!
既然不發送到視窗過程,那哥們兒是怎麼發現的呢?他是通過spy++仔細看產生的訊息發現的(因為spy++截取視窗的原理是通過使用HOOK的方式,而My Code裡為了處理IE的熱鍵也用到了HOOK,
但是由於粗心沒有針對所有訊息都調用CallNextHookEx,以致於我用spy++看的時候沒有這個哥們兒牛逼,調的半死,被工具陷害,造化弄人(因為spy++也是hook,因為這個訊息被我的hook獨佔了,它老兄就hook不到了,因此也就看不見這個WM_LBUTTONUP訊息了)。
解決的辦法基本原理就是當滑鼠左鍵在非客戶區按下釋放後,自己偽造發送一條WM_NCLBUTTONUP訊息給視窗過程。
因此要解決的核心問題是: 怎麼判斷當前滑鼠左鍵在非客戶區按下並釋放了 (如果視窗過程收到了WM_NCLBUTTONDOWN,並且接著訊息HOOK的回呼函數裡發現了WM_LBUTTONUP,就是表明
當前滑鼠在非客戶區按下並且釋放了,此時偽造WM_NCLBUTTONUP訊息)
解決辦法如下:
1. 響應非客戶區滑鼠按下的訊息WM_NCLBUTTONDOWN, 定義一個flag, m_bNCLButtonDowned = true; 表示當前在非客戶區按下了滑鼠左鍵
2. 使用WH_GETMESSAGE的HOOK, hook整個線程的訊息佇列,從而得到在非客戶區釋放滑鼠時的WM_LBUTTONUP訊息(因為spy++就是這種方式,因此我們也可以採用這種方式,當然google裡的那哥們使用的是mouse hook,忒複雜了些)
3. 在hook回呼函數裡的WM_LBUTTONUP訊息判斷當前m_bNCLButtonDowned == true?非客戶區裡是否按下了滑鼠左鍵,如果是,則PostMessage一個WM_NCLBUTTONUP,將真正的非客戶區的
滑鼠釋放訊息發送給視窗的視窗過程
4. 在視窗過程裡的WM_NCLBUTTONUP的處理函數裡判斷m_bNCLButtonDowned == true?如果是,則執行代碼,同時置位m_bNCLButtonDowned,表示當前已經釋放了滑鼠左鍵,因此滑鼠左鍵沒有按下
前輩實驗出來的系統會發送WM_NCLBUTTONUP的時刻:
1. 雙擊標題列
2. 在客戶區按住滑鼠左鍵,但是在非客戶區釋放滑鼠左鍵