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