using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using Vanara.PInvoke; using static Vanara.PInvoke.Gdi32; using static Vanara.PInvoke.UxTheme; namespace Vanara.Drawing { /// Buffered painting helper class. public static class BufferedPaint { private static readonly Dictionary> paintAnimationInstances = new Dictionary>(); /// A method delegate that retrieves a duration, in milliseconds, to use as the time over which buffered painting occurs. /// The type of the state that is used to determine the transition duration. /// The old state value. /// The new state value. /// A duration, in milliseconds, to use as the time over which buffered painting occurs. public delegate int GetDuration(TState oldState, TState newState); /// A method delegate to paint a stateful image. /// The type of the state that is used to determine the image to paint. /// The type of the parameter that is passed into this method. /// The graphics instance on which to paint the image. /// The bounds within which to paint the image. /// The current state to paint. /// The custom data passed into this method. public delegate void PaintAction(Graphics graphics, Rectangle bounds, TState currentState, TParam data); /// Performs a buffered paint operation. /// The type of the state that is used to determine the image to paint. /// The type of the parameter that is passed into this method. /// The target DC on which the buffer is painted. /// Specifies the area of the target DC in which to draw. /// A method delegate that performs the painting of the control at the provided state. /// The current state to use to start drawing the animation. /// User-defined data to pass to the callback. public static void Paint(Graphics graphics, Rectangle bounds, PaintAction paintAction, TState currentState, TParam data) { using (var g = new SafeTempHDC(graphics)) using (var bp = new BufferedPainter(g, bounds)) paintAction(bp.Graphics, bounds, currentState, data); } /// Performs a buffered animation operation. The animation consists of a cross-fade between the contents of two buffers over a specified period of time. /// The type of the state that is used to determine the image to paint. /// The target DC on which the buffer is animated. /// The window in which the animations play. /// Specifies the area of the target DC in which to draw. /// A method delegate that performs the painting of the control at a given state. /// The current state to use to start drawing the animation. /// The final state to use to finish drawing the animation. /// A method delegate that gets the duration of the animation, in milliseconds. public static void PaintAnimation(Graphics graphics, IWin32Window ctrl, Rectangle bounds, PaintAction paintAction, TState currentState, TState newState, GetDuration getDuration) => PaintAnimation(graphics, ctrl, bounds, paintAction, currentState, newState, getDuration, 0); /// Performs a buffered animation operation. The animation consists of a cross-fade between the contents of two buffers over a specified period of time. /// The type of the state that is used to determine the image to paint. /// The type of the parameter that is passed into this method. /// The target DC on which the buffer is animated. /// The window in which the animations play. /// Specifies the area of the target DC in which to draw. /// A method delegate that performs the painting of the control at a given state. /// The current state to use to start drawing the animation. /// The final state to use to finish drawing the animation. /// A method delegate that gets the duration of the animation, in milliseconds. /// User-defined data to pass to the callback. public static void PaintAnimation(Graphics graphics, IWin32Window ctrl, Rectangle bounds, PaintAction paintAction, TState currentState, TState newState, GetDuration getDuration, TParam data) { try { if (System.Environment.OSVersion.Version.Major >= 6) { // If this handle is running with a different state, stop the animations if (paintAnimationInstances.TryGetValue(ctrl.Handle, out var val)) { if (!Equals(val.Item1, currentState) || !Equals(val.Item2, newState)) { BufferedPaintStopAllAnimations(ctrl.Handle); System.Diagnostics.Debug.WriteLine("BufferedPaintStop."); paintAnimationInstances[ctrl.Handle] = new Tuple(currentState, newState); } } else paintAnimationInstances.Add(ctrl.Handle, new Tuple(currentState, newState)); using (var hdc = new SafeTempHDC(graphics)) { if (hdc.IsNull) return; // see if this paint was generated by a soft-fade animation if (BufferedPaintRenderAnimation(ctrl.Handle, hdc)) { paintAnimationInstances.Remove(ctrl.Handle); return; } var animParams = new BP_ANIMATIONPARAMS(BP_ANIMATIONSTYLE.BPAS_LINEAR, getDuration?.Invoke(currentState, newState) ?? 0); using (var h = new BufferedAnimationPainter(ctrl, hdc, bounds, animParams, BP_PAINTPARAMS.NoClip)) { if (!h.IsInvalid) { if (h.SourceGraphics != null) paintAction(h.SourceGraphics, bounds, currentState, data); if (h.DestinationGraphics != null) paintAction(h.DestinationGraphics, bounds, newState, data); } else { // hdc.Dispose(); paintAction(graphics, bounds, newState, data); } } } } else paintAction(graphics, bounds, newState, data); } catch { } System.Diagnostics.Debug.WriteLine($"BufferedPaint state items = {paintAnimationInstances.Count}."); } } /// Use to paint a buffered animation. /// public class BufferedAnimationPainter : IDisposable { private static readonly BufferedPaintBlock block = new BufferedPaintBlock(); private bool disposedValue = false; private SafeHANIMATIONBUFFER hba; /// /// Initializes a new instance of the class and begins a buffered animation operation. The animation consists of a /// cross-fade between the contents of two buffers over a specified period of time. /// /// The window in which the animations play. /// A handle of the target DC on which the buffer is animated. /// Specifies the area of the target DC in which to draw. /// A structure that defines the animation operation parameters. This value can be . /// A class that defines the paint operation parameters. This value can be . /// The format of the buffer. /// Buffered animation could not initialize. public BufferedAnimationPainter(IWin32Window wnd, HDC hdc, Rectangle targetRectangle, BP_ANIMATIONPARAMS? animationParams = null, BP_PAINTPARAMS paintParams = null, BP_BUFFERFORMAT fmt = BP_BUFFERFORMAT.BPBF_TOPDOWNDIB) { RECT rc = targetRectangle; var ap = animationParams ?? BP_ANIMATIONPARAMS.Empty; hba = BeginBufferedAnimation(wnd.Handle, hdc, rc, fmt, paintParams, ap, out var hdcFrom, out var hdcTo); if (hba.IsInvalid) throw new Win32Exception(); if (!hdcFrom.IsNull) SourceGraphics = Graphics.FromHdc((IntPtr)hdcFrom); if (!hdcTo.IsNull) DestinationGraphics = Graphics.FromHdc((IntPtr)hdcTo); } /// Finalizes an instance of the class. ~BufferedAnimationPainter() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(false); } /// Gets the destination graphics where the application should paint the final state of the animation. public virtual Graphics DestinationGraphics { get; } /// Gets a value indicating whether this instance is invalid. /// true if this instance is invalid; otherwise, false. public virtual bool IsInvalid => hba.IsInvalid; /// Gets the source graphics where the application should paint the initial state of the animation. public virtual Graphics SourceGraphics { get; } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. void IDisposable.Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Releases unmanaged and - optionally - managed resources. /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { SourceGraphics?.Dispose(); DestinationGraphics?.Dispose(); hba?.Dispose(); } disposedValue = true; } } } /// Use to perform buffered painting. /// public class BufferedPainter : IDisposable { private static readonly BufferedPaintBlock block = new BufferedPaintBlock(); private bool disposedValue = false; private SafeHPAINTBUFFER hbp; /// Initializes a new instance of the class and begins a buffered paint operation. /// The handle of the target DC on which the buffer will be painted. /// Specifies the area of the target DC in which to paint. /// The paint operation parameters. This value can be . /// The format of the buffer. /// Buffered painting could not initialize. public BufferedPainter(HDC hdc, Rectangle targetRectangle, BP_PAINTPARAMS paintParams = null, BP_BUFFERFORMAT fmt = BP_BUFFERFORMAT.BPBF_TOPDOWNDIB) { RECT target = targetRectangle; hbp = BeginBufferedPaint(hdc, target, fmt, paintParams, out var phdc); if (hbp.IsInvalid) throw new Win32Exception(); if (!phdc.IsNull) Graphics = Graphics.FromHdc((IntPtr)phdc); } /// Finalizes an instance of the class. ~BufferedPainter() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(false); } /// Gets the destination graphics on which all painting is done. public virtual Graphics Graphics { get; } /// Gets a value indicating whether this instance is invalid. /// true if this instance is invalid; otherwise, false. public virtual bool IsInvalid => hbp.IsInvalid; /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. void IDisposable.Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Releases unmanaged and - optionally - managed resources. /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { Graphics?.Dispose(); hbp?.Dispose(); } disposedValue = true; } } } }