Windows Vista for Developers——第三部分补充:控件和桌面窗口管理器
?
作者:Kenny Kerr
翻譯:Dflying Chen
原文:http://weblogs.asp.net/kennykerr/archive/2007/01/23/controls-and-the-desktop-window-manager.aspx
請同時參考《Windows Vista for Developers》系列。
?
在所有《Windows Vista for Developers》系列文章中,《Windows Vista for Developers——第三部分:桌面窗口管理器》是最受歡迎的(通過Blog的流量統計、Email問題的主題等得出)。
目前為止,我所聽到的最常見的問題就是如何在啟用玻璃效果時也能正確地呈現出控件。回憶一下,我寫DMW文章的時候Windows Vista還沒有RTM。在這些較早版本的Vista中,我們可以使用那個透明像素的hack來輕松地在玻璃效果上繪出需要的控件。在那篇文章中我也演示了這個hack的實際應用。不幸的是,當微軟公司正式發布Vista時,這個hack已經沒用了,只留下了滿腹狐疑的開發者……應該怎么辦呢?
不得不說我每天的工作非常繁忙,還有很多其他的委托事項需要處理,以至于沒有時間發布替代的解決方案。不過為了拯救我的email信箱,我還是決定再給出一個解決方案來談談這個最常見的問題。
?
如何才能在玻璃效果上顯示一個文本框?
解決這個問題的辦法有很多種。更明確一些地說,有很多種覆寫默認的標準/常用控件繪圖方式的辦法。
你可以接受WM_PAINT消息并自行繪制控件。這樣做的工作量似乎不少,所以大多數開發者都不喜歡,但這種方法確實管用,讓我們能夠用必需的Alpha通道進行繪制,進而顯示出正確的玻璃效果。我的DMW實例程序就演示了這種方法,雖然其中用的不是某個控件。
另一種方法是owner draw控件。這樣做的工作量也不少,不過卻比接受WM_PAINT消息簡單多了,操作系統卻為你做了不少。owner draw方法是個很不錯的主意,適合大多數但不是所有的控件。值得一提的是對于文本框來說,owner draw就不管用。
還有一種更簡單的方法,就是custom draw,但它所適用的控件更少。
另外,對于少數幾個控件,你也可以處理WM_CTLCOLORxxx消息,并設置其文本和背景顏色。
看看目前我們列出的這幾個選項,只有最后一個支持文本框控件且相對比較簡單。不過這種方法與玻璃效果配合的卻并不怎么好,因為它需要較為原始的GDI支持,而GDI卻并不支持Alpha混合。
?
再重復一遍:如何才能在玻璃效果上顯示一個文本框?
有時候(比如現在)我就在想為什么我不在微軟公司工作呢?微軟公司也不會因為我的這些Blog上的文章給我任何報酬…… :)
在昨天又收到一封Email詢問如何在玻璃效果上顯示出文本框控件之后,我終于決定查看一下Windows SDK,看看有沒有什么新的辦法。順便說一句,若你不經常查閱Windows SDK的話,我強烈見你養成這個好習慣。憑著直覺,我開始在SDK的Themes和Visual Styles節中查看。不管怎樣,這部分內容是負責提供控件的樣式的。
讓我注意到的第一個東西就是Windows Vista 的UxTheme.dll中心添加的一系列函數,用來支持緩沖繪圖。一開始這看起來似乎并不是那么吸引人,因為DMW已經提供了一定程度上的雙緩沖。但若是有了緩沖繪圖,那就意味著我們可以捕獲、修改內存中的位圖之后,再將其顯示出來。當然,這并不是什么新玩意,我們也可以手工實現同樣的功能。但Visual Styles中提供的這些新功能卻簡化了我們的工作,并可以很漂亮地解決這個火燒眉毛的問題。
?
緩沖繪圖API
緩沖繪圖API提供了一系列的函數,用來將圖像繪至設備上下文(DC)。因為圖像將被繪至DC,所以你前面學的GDI還有用武之地。嘿,兄弟,確實如此,現在還沒必要將你整個的程序遷移到Windows Presentation Foundation(WPF)上!
開始之前,你應該在每個線程中都至少調用一次BufferedPaintInit 函數,用來初始化這一系列的API。注意,每次調用BufferedPaintInit 都必須對應著一個同一線程上的BufferedPaintUnInit 調用。
若想開始緩沖繪圖操作,只要簡單地調用BeginBufferedPaint 函數即可。該函數接受一個目的DC,以及一個目的矩形區域,用來指定最終的緩存將要繪制的位置。還有一些額外的參數,可以用來控制某些緩存相關的特性。其中一個就是緩存的類型——謝天謝地它支持設備無關位圖(Device Independent Bitmap,DIB)類型,這就足夠我們進行Alpha混合操作了。BeginBufferedPaint 函數然后返回一個句柄,我們可以將其傳遞到其他緩沖繪圖API,或是某個將要繪制的DC中。
能夠接受該緩沖繪圖句柄的其中一個函數就是BufferedPaintSetAlpha。該函數可以讓我們簡單地更新整個緩存的Alpha通道,并將其設定為一個單一的值,以期實現各種不同級別的透明/半透明效果。需要注意的是緩存內的所有像素都將被更新為同一個的Alpha值。
最后,我們即可將該緩存拷貝到目標DC上了,并調用EndBufferedPaint 函數釋放由BeginBufferedPaint 分配的相關資源。
目前為止,你差不多也能想象到接下來要怎么做了。首先用緩沖繪圖API創建一個緩沖圖像,然后在該緩沖圖像上繪出我們需要的文本框,接下來更新緩沖圖像的Alpha通道,最后將緩沖繪制到窗體的DC上。讓我們看一個實例程序。
?
BufferedPaint類
下面這個類將緩沖繪圖API用C++封裝起來,以簡化其使用。
class BufferedPaint { public: BufferedPaint() : m_handle(0) { COM_VERIFY(::BufferedPaintInit()); } ~BufferedPaint() { COM_VERIFY(::BufferedPaintUnInit()); } HRESULT Begin(HDC targetDC, const RECT& targetRect, BP_BUFFERFORMAT format, __in_opt BP_PAINTPARAMS* options, __out CDCHandle& bufferedDC) { ASSERT(0 == m_handle); m_handle = ::BeginBufferedPaint(targetDC, &targetRect, format, options, &bufferedDC.m_hDC); HRESULT result = S_OK; if (0 == m_handle) { result = HRESULT_FROM_WIN32(::GetLastError()); } return result; } HRESULT End(bool updateTargetDC) { ASSERT(0 != m_handle); HRESULT result = ::EndBufferedPaint(m_handle, updateTargetDC); m_handle = 0; return result; } HRESULT SetAlpha(__in_opt const RECT* rect, BYTE alpha) { ASSERT(0 != m_handle); return ::BufferedPaintSetAlpha(m_handle, rect, alpha); } private: HPAINTBUFFER m_handle; };?
OpaqueEdit類
下面這個C++類繼承于系統的文本框類,這樣我們即可方便地重寫其繪圖相關的方法。
class OpaqueEdit : public CWindowImpl<OpaqueEdit, CEdit> { public: BEGIN_MSG_MAP_EX(OpaqueEdit) MESSAGE_HANDLER(WM_PAINT, OnPaint) REFLECTED_COMMAND_CODE_HANDLER_EX(EN_CHANGE, OnChange) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() private: LRESULT OnPaint(UINT /*message*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*handled*/) { CPaintDC targetDC(m_hWnd); CDCHandle bufferedDC; if (SUCCEEDED(m_bufferedPaint.Begin(targetDC, targetDC.m_ps.rcPaint, BPBF_TOPDOWNDIB, 0, // options bufferedDC))) { SendMessage(WM_PRINTCLIENT, reinterpret_cast<WPARAM>(bufferedDC.m_hDC), PRF_CLIENT); COM_VERIFY(m_bufferedPaint.SetAlpha(0, // entire buffer 255)); // 255 = opaque // Copy buffered DC to target DC COM_VERIFY(m_bufferedPaint.End(true)); } return 0; } void OnChange(UINT /*notifyCode*/, int /*control*/, HWND /*window*/) { VERIFY(InvalidateRect(0, // entire window FALSE)); // don't erase background } BufferedPaint m_bufferedPaint; };可以看到,在WM_PAINT消息的處理函數中,我們將WM_PRINTCLIENT消息發送給了該文本框,讓其繪制到經過緩存的DC上。然后將該緩存的Alpha通道值設置為255(完全不透明)并更新了目標DC。EN_CHANGE的處理函數可能會讓你有些吃驚。因為文本框的繪制發生在WM_PAINT消息之外,當控件中的文本內容發生變化時,我們需要進行再次重繪。在這個示例中,我僅僅是讓該控件失效,這樣它會再次接收到一個新的WM_PAINT消息。這種實現方式還有一定的優化空間,但目前為止對于這個示例程序來說已經足夠用了。需要提到的是因為DMW自動提供了雙緩存,所以重復地進行繪制并不會造成界面閃爍。
?
SampleDialog類
下面的這個類保證了該窗體作為一塊無縫的“玻璃”顯示出來,并在其中添加一個前面定義的OpaqueEdit 類作為文本框。
class SampleDialog : public CDialogImpl<SampleDialog> { public: enum { IDD = IDD_SAMPLE }; BEGIN_MSG_MAP(MainWindow) MSG_WM_INITDIALOG(OnInitDialog) MSG_WM_ERASEBKGND(OnEraseBackground) COMMAND_ID_HANDLER(IDCANCEL, OnCancel) REFLECT_NOTIFICATIONS() END_MSG_MAP() private: bool OnInitDialog(HWND /*control*/, LPARAM /*lParam*/) { const MARGINS margins = { -1 }; COM_VERIFY(::DwmExtendFrameIntoClientArea(m_hWnd, &margins)); VERIFY(m_edit.SubclassWindow(GetDlgItem(IDC_CONTROL))); return true; // Yes, go ahead and set the keyboard focus. } bool OnEraseBackground(CDCHandle dc) { CRect rect; VERIFY(GetClientRect(&rect)); dc.FillSolidRect(&rect, #000000); return true; // Yes, I erased the background. } LRESULT OnCancel(WORD /*notifyCode*/, WORD identifier, HWND /*window*/, BOOL& /*handled*/) { VERIFY(EndDialog(identifier)); return 0; } OpaqueEdit m_edit; };大功告成!希望本文中提到的技術能在你掌握Windows Vista開發技術的過程中助上一臂之力!
總結
以上是生活随笔為你收集整理的Windows Vista for Developers——第三部分补充:控件和桌面窗口管理器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 教你一招画素描, 不写程序时陶冶陶冶情操
- 下一篇: Linux 常见的六大 IPC 通信方式