使用 Buffered Paint API 绘制带有淡入淡出动画的控件

  • 使用 Buffered Paint API 绘制带有淡入淡出动画的控件已关闭评论
  • 160 次浏览
  • A+
所属分类:.NET技术
摘要

Windows 窗体提供了许多机制来构建与操作系统风格相匹配的专业自定义 UI 控件; 通过结合视觉风格渲染器、系统颜色/画笔、 ControlPaint类等,可以在用户代码中重现大多数标准 Windows 控件。


使用 Buffered Paint API 绘制带有淡入淡出动画的控件

Windows 窗体提供了许多机制来构建与操作系统风格相匹配的专业自定义 UI 控件;通过结合视觉风格渲染器、系统颜色/画笔、ControlPaint类等,可以在用户代码中重现大多数标准 Windows 控件。

然而,在托管代码中很难重新创建内置控件的一个方面:从 Windows Vista 开始,许多控件(例如ButtonComboBoxTextBox等)在状态之间转换时使用淡入淡出动画,例如作为焦点,鼠标悬停和按钮按下。在内部,这些动画由缓冲的绘制 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

 

BufferedPainting.zip

 

包括示例控件

最后的话

缓冲绘制 API 填补了在托管代码中编写与 OS 风格相匹配的自定义控件的最后一个空白,无论是在静态外观还是动画方面。我希望您发现我的代码对在您自己的自定义控件中实现平滑过渡很有用。