ToB企服应用市场:ToB评测及商务社交产业平台

标题: 使用 Buffered Paint API 绘制带有淡入淡出动画的控件 [打印本页]

作者: 一给    时间: 2022-9-20 02:05
标题: 使用 Buffered Paint API 绘制带有淡入淡出动画的控件
使用 Buffered Paint API 绘制带有淡入淡出动画的控件

发表于2011 年 10 月 23 日 Windows 窗体提供了许多机制来构建与操作系统风格相匹配的专业自定义 UI 控件;通过结合视觉风格渲染器、系统颜色/画笔、ControlPaint类等,可以在用户代码中重现大多数标准 Windows 控件。
然而,在托管代码中很难重新创建内置控件的一个方面:从 Windows Vista 开始,许多控件(例如ButtonComboBoxTextBox等)在状态之间转换时使用淡入淡出动画,例如作为焦点,鼠标悬停和按钮按下。在内部,这些动画由缓冲的绘制 API(uxtheme.dll的一部分,负责视觉样式的库)处理。
大多数开发人员会满足于瞬时的视觉状态变化,但对于受过训练的眼睛来说,缺乏平滑过渡确实可以使自定义控件从内置控件中脱颖而出。好消息是,虽然没有用于缓冲绘画的托管 API,但使用 PInvoke 相对容易利用。
缓冲绘画 API - 基础知识

Imports
  1. [DllImport("uxtheme")]
  2. static extern IntPtr BufferedPaintInit();
  3. [DllImport("uxtheme")]
  4. static extern IntPtr BufferedPaintUnInit();
  5. [DllImport("uxtheme")]
  6. static extern IntPtr BeginBufferedAnimation(
  7.     IntPtr hwnd,
  8.     IntPtr hdcTarget,
  9.     ref Rectangle rcTarget,
  10.     BP_BUFFERFORMAT dwFormat,
  11.     IntPtr pPaintParams,
  12.     ref BP_ANIMATIONPARAMS pAnimationParams,
  13.     out IntPtr phdcFrom,
  14.     out IntPtr phdcTo
  15. );
  16. [DllImport("uxtheme")]
  17. static extern IntPtr EndBufferedAnimation(IntPtr hbpAnimation, bool fUpdateTarget);
  18. [DllImport("uxtheme")]
  19. [return: MarshalAs(UnmanagedType.Bool)]
  20. static extern bool BufferedPaintRenderAnimation(IntPtr hwnd, IntPtr hdcTarget);
  21. [DllImport("uxtheme")]
  22. static extern IntPtr BufferedPaintStopAllAnimations(IntPtr hwnd);
复制代码
 
用法

 
  1. void Control_Paint(object sender, PaintEventArgs e) {
  2.     IntPtr hdc = e.Graphics.GetHdc();
  3.     if (hdc != IntPtr.Zero) {
  4.         // see if this paint was generated by a soft-fade animation
  5.         if (!Interop.BufferedPaintRenderAnimation(Control.Handle, hdc)) {
  6.             BP_ANIMATIONPARAMS animParams = new BP_ANIMATIONPARAMS();
  7.             animParams.cbSize = Marshal.SizeOf(animParams);
  8.             animParams.style = BP_ANIMATIONSTYLE.BPAS_LINEAR;
  9.             // set duration according to state transition
  10.             animParams.dwDuration = 125;
  11.             // begin the animation
  12.             Rectangle rc = Control.ClientRectangle;
  13.             IntPtr hdcFrom, hdcTo;
  14.             IntPtr hbpAnimation = Interop.BeginBufferedAnimation(
  15.                 Control.Handle,
  16.                 hdc,
  17.                 ref rc,
  18.                 BP_BUFFERFORMAT.BPBF_COMPATIBLEBITMAP,
  19.                 IntPtr.Zero,
  20.                 ref animParams,
  21.                 out hdcFrom,
  22.                 out hdcTo
  23.             );
  24.             if (hbpAnimation != IntPtr.Zero) {
  25.                 if (hdcFrom != IntPtr.Zero) /* paint start frame to hdcFrom */;
  26.                 if (hdcTo != IntPtr.Zero) /* paint end frame to hdcTo */;
  27.                 Interop.EndBufferedAnimation(hbpAnimation, true);
  28.             }
  29.             else {
  30.                 /* paint control normally */
  31.             }
  32.         }
  33.         e.Graphics.ReleaseHdc(hdc);
  34.     }
  35. }
复制代码
 
关于在 Windows 窗体控件上使用缓冲绘制 API 的一些进一步说明:
BufferedPainter – 一个简化缓冲绘画的托管类

缓冲绘画涉及一定数量的样板代码。您需要向控件的创建/处置事件添加代码,使用特定模式覆盖Paint事件,然后提供用于绘制控件的替代方法(到屏幕或位图)。
消除这种样板代码的一种方法是编写一个从Control派生的基类,它提供了这个功能。然而,这有点限制,因为所有使用缓冲绘画的自定义控件都必须从这个类继承。实际上,您可能希望在全新控件中以及在继承现有控件(例如ComboBox)时使用缓冲绘制。出于这个原因,我编写了一个独立的类并附加到任何类型的控件。
BufferedPainter是一个泛型类,它允许使用任何类型来表示控件的视觉状态;这可能是枚举、整数甚至更复杂的类型。只要该类型提供Equals方法(或具有合适的默认实现),它就可以用于跟踪状态转换。一个简单的按钮控件可能具有三种状态;正常,热和推。BufferedPainter保存有关状态更改和状态之间动画持续时间(如果需要)的信息。它存储控件的当前视觉状态,覆盖控件的Paint事件并提供由用户代码处理的PaintVisualState事件。
它还提供了一种机制来简化触发控件视觉状态变化的过程;除了手动设置控件的状态(使用State属性)之外,您还可以添加一个触发器,该触发器会根据条件(例如鼠标悬停在控件上)更改为特定状态。这进一步减少了控制中所需的代码量。条件可以特定于控件范围内的区域,并且锚定可用于在调整控件大小时自动更新区域。
向控件添加缓冲绘制支持非常简单:
  1. // using an enum type 'MyVisualStates' to describe the control's visual state
  2. BufferedPainter<MyVisualStates> painter = new BufferedPainter<MyVisualStates>(/* control instance */);
  3. painter.PaintVisualState += /* event handler which paints the control in a particular state */;
  4. // describe the state transitions we want to animate
  5. painter.AddTransition(MyVisualStates.Normal, MyVisualStates.Hot, 125); // fade in
  6. painter.AddTransition(MyVisualStates.Hot, MyVisualStates.Pushed, 75);
  7. painter.AddTransition(MyVisualStates.Hot, MyVisualStates.Normal, 250); // fade out
  8. // describe what causes the control to change its visual state
  9. painter.AddTrigger(VisualStateTriggerTypes.Hot, MyVisualStates.Hot); // mouse over
  10. painter.AddTrigger(VisualStateTriggerTypes.Pushed, MyVisualStates.Pushed); // mouse down
复制代码
 
BufferedPainting.zip
 
包括示例控件
最后的话

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4