使用 Buffered Paint API 繪製帶有淡入淡出動畫的控制項 發表於2011 年 10 月 23 日 Windows 窗體提供了許多機制來構建與操作系統風格相匹配的專業自定義 UI 控制項;通過結合視覺風格渲染器、系統顏色/畫筆、ControlPaint類等,可以在用戶代碼中重現大多數標準 ...
使用 Buffered Paint API 繪製帶有淡入淡出動畫的控制項
發表於2011 年 10 月 23 日Windows 窗體提供了許多機制來構建與操作系統風格相匹配的專業自定義 UI 控制項;通過結合視覺風格渲染器、系統顏色/畫筆、ControlPaint類等,可以在用戶代碼中重現大多數標準 Windows 控制項。
然而,在托管代碼中很難重新創建內置控制項的一個方面:從 Windows Vista 開始,許多控制項(例如Button、ComboBox、TextBox等)在狀態之間轉換時使用淡入淡出動畫,例如作為焦點,滑鼠懸停和按鈕按下。在內部,這些動畫由緩衝的繪製 API(uxtheme.dll的一部分,負責視覺樣式的庫)處理。
大多數開發人員會滿足於瞬時的視覺狀態變化,但對於受過訓練的眼睛來說,缺乏平滑過渡確實可以使自定義控制項從內置控制項中脫穎而出。好消息是,雖然沒有用於緩衝繪畫的托管 API,但使用 PInvoke 相對容易利用。
緩衝繪畫 API - 基礎知識
Imports
[DllImport("uxtheme")] static extern IntPtr BufferedPaintInit(); [DllImport("uxtheme")] static extern IntPtr BufferedPaintUnInit(); [DllImport("uxtheme")] static extern IntPtr BeginBufferedAnimation( IntPtr hwnd, IntPtr hdcTarget, ref Rectangle rcTarget, BP_BUFFERFORMAT dwFormat, IntPtr pPaintParams, ref BP_ANIMATIONPARAMS pAnimationParams, out IntPtr phdcFrom, out IntPtr phdcTo ); [DllImport("uxtheme")] static extern IntPtr EndBufferedAnimation(IntPtr hbpAnimation, bool fUpdateTarget); [DllImport("uxtheme")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool BufferedPaintRenderAnimation(IntPtr hwnd, IntPtr hdcTarget); [DllImport("uxtheme")] static extern IntPtr BufferedPaintStopAllAnimations(IntPtr hwnd);
用法
- 您使用BufferedPaintInit / BufferedPaintUnInit初始化/結束會話(通常在控制項的生命周期內持續)。
- 你用BeginBufferedAnimation開始一個淡入淡出動畫,傳入一個 BP_ANIMATIONPARAMS結構來描述過渡。返回動畫句柄和兩個空點陣圖。
- 您將開始幀和結束幀繪製到各自的點陣圖上(使用 GDI+,就像您正常繪製控制項一樣)並調用EndBufferedAnimation開始播放它。
- 在播放動畫時,控制項的Paint事件將被觸發多次。通過檢查BufferedPaintRenderAnimation返回的值來檢查是否是由緩衝的繪製動畫觸發的;如果是這樣,請不要自己繪製控制項。
void Control_Paint(object sender, PaintEventArgs e) { IntPtr hdc = e.Graphics.GetHdc(); if (hdc != IntPtr.Zero) { // see if this paint was generated by a soft-fade animation if (!Interop.BufferedPaintRenderAnimation(Control.Handle, hdc)) { BP_ANIMATIONPARAMS animParams = new BP_ANIMATIONPARAMS(); animParams.cbSize = Marshal.SizeOf(animParams); animParams.style = BP_ANIMATIONSTYLE.BPAS_LINEAR; // set duration according to state transition animParams.dwDuration = 125; // begin the animation Rectangle rc = Control.ClientRectangle; IntPtr hdcFrom, hdcTo; IntPtr hbpAnimation = Interop.BeginBufferedAnimation( Control.Handle, hdc, ref rc, BP_BUFFERFORMAT.BPBF_COMPATIBLEBITMAP, IntPtr.Zero, ref animParams, out hdcFrom, out hdcTo ); if (hbpAnimation != IntPtr.Zero) { if (hdcFrom != IntPtr.Zero) /* paint start frame to hdcFrom */; if (hdcTo != IntPtr.Zero) /* paint end frame to hdcTo */; Interop.EndBufferedAnimation(hbpAnimation, true); } else { /* paint control normally */ } } e.Graphics.ReleaseHdc(hdc); } }
關於在 Windows 窗體控制項上使用緩衝繪製 API 的一些進一步說明:
- 如果控制項是雙緩衝的(DoubleBuffered屬性設置為 true 或OptimizedDoubleBuffer樣式標誌設置),則不支持動畫。
- 要減少閃爍,請重寫控制項的OnPaintBackground方法,並且不要調用基類方法。您可以在繪製控制項的其餘部分時手動繪製控制項的背景。
- 每當調整控制項大小時,應使用BufferedPaintStopAllAnimations停止所有正在運行的動畫。
BufferedPainter – 一個簡化緩衝繪畫的托管類
緩衝繪畫涉及一定數量的樣板代碼。您需要向控制項的創建/處置事件添加代碼,使用特定模式覆蓋Paint事件,然後提供用於繪製控制項的替代方法(到屏幕或點陣圖)。
消除這種樣板代碼的一種方法是編寫一個從Control派生的基類,它提供了這個功能。然而,這有點限制,因為所有使用緩衝繪畫的自定義控制項都必須從這個類繼承。實際上,您可能希望在全新控制項中以及在繼承現有控制項(例如ComboBox)時使用緩衝繪製。出於這個原因,我編寫了一個獨立的類並附加到任何類型的控制項。
BufferedPainter是一個泛型類,它允許使用任何類型來表示控制項的視覺狀態;這可能是枚舉、整數甚至更複雜的類型。只要該類型提供Equals方法(或具有合適的預設實現),它就可以用於跟蹤狀態轉換。一個簡單的按鈕控制項可能具有三種狀態;正常,熱和推。BufferedPainter保存有關狀態更改和狀態之間動畫持續時間(如果需要)的信息。它存儲控制項的當前視覺狀態,覆蓋控制項的Paint事件並提供由用戶代碼處理的PaintVisualState事件。
它還提供了一種機制來簡化觸發控制項視覺狀態變化的過程;除了手動設置控制項的狀態(使用State屬性)之外,您還可以添加一個觸發器,該觸發器會根據條件(例如滑鼠懸停在控制項上)更改為特定狀態。這進一步減少了控制中所需的代碼量。條件可以特定於控制項範圍內的區域,並且錨定可用於在調整控制項大小時自動更新區域。
向控制項添加緩衝繪製支持非常簡單:
// using an enum type 'MyVisualStates' to describe the control's visual state BufferedPainter<MyVisualStates> painter = new BufferedPainter<MyVisualStates>(/* control instance */); painter.PaintVisualState += /* event handler which paints the control in a particular state */; // describe the state transitions we want to animate painter.AddTransition(MyVisualStates.Normal, MyVisualStates.Hot, 125); // fade in painter.AddTransition(MyVisualStates.Hot, MyVisualStates.Pushed, 75); painter.AddTransition(MyVisualStates.Hot, MyVisualStates.Normal, 250); // fade out // describe what causes the control to change its visual state painter.AddTrigger(VisualStateTriggerTypes.Hot, MyVisualStates.Hot); // mouse over painter.AddTrigger(VisualStateTriggerTypes.Pushed, MyVisualStates.Pushed); // mouse down
(包括示例控制項)
最後的話
緩衝繪製 API 填補了在托管代碼中編寫與 OS 風格相匹配的自定義控制項的最後一個空白,無論是在靜態外觀還是動畫方面。我希望您發現我的代碼對在您自己的自定義控制項中實現平滑過渡很有用。