2023-09-24 17:26:46 -04:00
using System.Collections.Generic ;
2017-11-27 13:11:20 -05:00
using System.Drawing ;
using System.Windows.Forms ;
2018-06-28 20:02:36 -04:00
using Vanara.PInvoke ;
2017-11-27 13:11:20 -05:00
using static Vanara . PInvoke . ComCtl32 ;
2023-03-31 11:47:53 -04:00
namespace Vanara.Extensions ;
/// <summary>Extension methods for <see cref="ImageList"/>.</summary>
public static partial class ImageListExtension
2017-11-27 13:11:20 -05:00
{
2023-09-24 17:26:46 -04:00
private static Dictionary < ImageList , List < int > > imageListOverlays = new ( ) ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Adds the specified image to the ImageList as an overlay, using the specified color to determine transparency.</summary>
/// <param name="imageList">The image list.</param>
/// <param name="value">A Bitmap of the image to add to the list.</param>
/// <param name="transparentColor">The color to use as the transparent color within the Bitmap.</param>
/// <returns>The 1 based index of the overlay.</returns>
/// <exception cref="System.ComponentModel.Win32Exception">The image cannot be added as an overlay.</exception>
public static int AddOverlay ( this ImageList imageList , Image value , Color transparentColor )
{
var idx = imageList . Images . Add ( value , transparentColor ) ;
return SetImageIndexAsOverlay ( imageList , idx ) ;
}
2018-06-08 13:25:08 -04:00
2023-03-31 11:47:53 -04:00
/// <summary>Draws the image indicated by the given index on the specified <see cref="Graphics"/> at the specified location.</summary>
/// <param name="imageList">The image list.</param>
/// <param name="g">The <see cref="Graphics"/> to draw on.</param>
/// <param name="bounds">The bounds in which to draw the image. Set width and height to 0 to draw image at full size.</param>
/// <param name="index">The index of the image in the ImageList to draw.</param>
/// <param name="bgColor">
/// The background color of the image. This parameter can be a <see cref="Color"/> value or <see cref="COLORREF.None"/> so the image is drawn
/// transparently or <see cref="COLORREF.Default"/> so the image is drawn using the background color of the image list.
/// </param>
/// <param name="fgColor">
/// The foreground color of the image. This parameter can be a <see cref="Color"/> value or <see cref="COLORREF.None"/> so the image is blended
/// with the color of the destination device context or <see cref="COLORREF.Default"/> so the image is drawn using the system highlight color
/// as the foreground color.
/// </param>
/// <param name="style">The drawing style.</param>
/// <param name="overlayImageIndex">Optional index of an overlay image.</param>
/// <exception cref="System.ComponentModel.Win32Exception">Unable to draw the image with defined parameters.</exception>
public static void Draw ( this ImageList imageList , Graphics g , Rectangle bounds , int index , COLORREF bgColor , COLORREF fgColor , IMAGELISTDRAWFLAGS style = IMAGELISTDRAWFLAGS . ILD_NORMAL , int overlayImageIndex = 0 )
{
if ( index < 0 | | index > = imageList . Images . Count )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
if ( overlayImageIndex < 0 | | overlayImageIndex > imageList . GetOverlayCount ( ) )
throw new ArgumentOutOfRangeException ( nameof ( overlayImageIndex ) ) ;
2023-09-29 13:58:35 -04:00
using var hg = new SafeTempHDC ( g ) ;
var p = new IMAGELISTDRAWPARAMS ( hg , bounds , index , bgColor , style | ( IMAGELISTDRAWFLAGS ) INDEXTOOVERLAYMASK ( overlayImageIndex ) ) { rgbFg = fgColor } ;
imageList . GetIImageList ( ) . Draw ( p ) ;
2023-03-31 11:47:53 -04:00
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets the current background color for an image list.</summary>
/// <param name="imageList">The image list.</param>
/// <returns>The background color.</returns>
public static Color GetBackgroundColor ( this ImageList imageList ) = > imageList . GetIImageList ( ) . GetBkColor ( ) ;
2018-06-08 13:25:08 -04:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets an <see cref="IImageList"/> object for the <see cref="ImageList"/> instance.</summary>
/// <param name="imageList">The image list.</param>
/// <returns>An <see cref="IImageList"/> object.</returns>
public static IImageList GetIImageList ( this ImageList imageList ) = > new SafeHIMAGELIST ( imageList . Handle , false ) . Interface ;
2018-06-05 13:39:42 -04:00
2023-03-31 11:47:53 -04:00
/// <summary>Creates an <see cref="ImageList"/> from a handle.
/// <note type="warning">This is a super hack involving a lot of reflection against internal structures that can change. Use with caution!</note></summary>
/// <param name="himl">The SafeImageListHandle value.</param>
/// <returns>An <c>ImageList</c> instance hosting the supplied handle.</returns>
/// <exception cref="PlatformNotSupportedException" />
public static ImageList ToImageList ( this HIMAGELIST himl )
{
// Duplicate handle and get IImageList for it
var dhiml = ImageList_Duplicate ( himl ) ;
var iil = dhiml . Interface ;
// Get internal handle class
var nilfi = typeof ( ImageList ) . GetField ( "nativeImageList" , System . Reflection . BindingFlags . Instance | System . Reflection . BindingFlags . NonPublic ) ? ? throw new PlatformNotSupportedException ( ) ;
2023-09-29 13:58:35 -04:00
Type nil = nilfi . FieldType ;
2023-03-31 11:47:53 -04:00
// Create a new instance with the handle param
2023-09-29 13:58:35 -04:00
#pragma warning disable IL2058 // Parameters passed to method cannot be analyzed. Consider using methods 'System.Type.GetType' and `System.Activator.CreateInstance` instead.
var nili = nil . Assembly . CreateInstance ( nil . FullName ! , false , System . Reflection . BindingFlags . Instance | System . Reflection . BindingFlags . NonPublic , null , new object [ ] { dhiml . DangerousGetHandle ( ) } , null , null ) ;
#pragma warning restore IL2058 // Parameters passed to method cannot be analyzed. Consider using methods 'System.Type.GetType' and `System.Activator.CreateInstance` instead.
// Create a new ImageList and initialize with settings from handle
2023-03-31 11:47:53 -04:00
var il = new ImageList ( ) ;
nilfi . SetValue ( il , nili ) ;
var depthfi = typeof ( ImageList ) . GetField ( "colorDepth" , System . Reflection . BindingFlags . Instance | System . Reflection . BindingFlags . NonPublic ) ;
var szfi = typeof ( ImageList ) . GetField ( "imageSize" , System . Reflection . BindingFlags . Instance | System . Reflection . BindingFlags . NonPublic ) ;
if ( depthfi = = null | | szfi = = null ) throw new PlatformNotSupportedException ( ) ;
var add1 = false ;
if ( iil . GetImageCount ( ) = = 0 )
2018-06-08 13:25:08 -04:00
{
2023-03-31 11:47:53 -04:00
add1 = true ;
iil . SetImageCount ( 1 ) ;
2018-06-08 13:25:08 -04:00
}
2023-03-31 11:47:53 -04:00
var bmp = Image . FromHbitmap ( ( IntPtr ) iil . GetImageInfo ( 0 ) . hbmImage ) ;
var depth = ( ColorDepth ) ( ( ( uint ) bmp . PixelFormat & 0x3C00 ) > > 8 ) ;
if ( add1 ) iil . SetImageCount ( 0 ) ;
depthfi . SetValue ( il , depth ) ;
var sz = iil . GetIconSize ( ) ;
szfi . SetValue ( il , sz ) ;
return il ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Creates a new image by combining two existing images.</summary>
/// <param name="il1">The image list that contains the first image.</param>
/// <param name="idx1">The index of the first existing image.</param>
/// <param name="il2">The image list that contains the second image.</param>
/// <param name="idx2">The index of the second existing image.</param>
/// <param name="offset">The offset of the second image relative to the first image.</param>
/// <returns>A merged image.</returns>
public static Icon MergeImage ( this ImageList il1 , int idx1 , ImageList il2 , int idx2 , Point offset = default )
{
2023-09-29 13:58:35 -04:00
IImageList il3 = il1 . GetIImageList ( ) . Merge ( idx1 , il2 . GetIImageList ( ) , idx2 , offset . X , offset . Y , typeof ( IImageList ) . GUID ) ;
return il3 . GetIcon ( 0 , IMAGELISTDRAWFLAGS . ILD_TRANSPARENT | IMAGELISTDRAWFLAGS . ILD_PRESERVEALPHA ) . ToIcon ( ) ! ;
2023-03-31 11:47:53 -04:00
}
2018-06-08 13:25:08 -04:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets the current background color for an image list.</summary>
/// <param name="imageList">The image list.</param>
/// <param name="bkColor">The background color to set.</param>
public static void SetBackgroundColor ( this ImageList imageList , Color bkColor ) = >
imageList . GetIImageList ( ) . SetBkColor ( bkColor , out var _ ) ;
2018-06-08 13:25:08 -04:00
2023-03-31 11:47:53 -04:00
/// <summary>Assigns the image at the specified index as an overlay and returns is overlay index.</summary>
/// <param name="imageList">The image list.</param>
/// <param name="imageIndex">Index of the image within the ImageList.</param>
/// <returns>The 1 based index of the overlay.</returns>
2023-09-29 13:58:35 -04:00
/// <exception cref="ArgumentOutOfRangeException">The imageIndex is not in the current list.</exception>
2023-03-31 11:47:53 -04:00
/// <exception cref="System.ComponentModel.Win32Exception">The image cannot be added as an overlay.</exception>
public static int SetImageIndexAsOverlay ( this ImageList imageList , int imageIndex )
{
if ( imageIndex < 0 | | imageIndex > = imageList . Images . Count )
throw new ArgumentOutOfRangeException ( nameof ( imageIndex ) ) ;
if ( ! imageListOverlays . TryGetValue ( imageList , out var vals ) )
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
imageList . RecreateHandle + = ImageList_RecreateHandle ;
imageListOverlays . Add ( imageList , vals = new List < int > ( 15 ) ) ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
vals . Add ( imageIndex ) ;
var overlayIndex = vals . Count ;
imageList . GetIImageList ( ) . SetOverlayImage ( imageIndex , overlayIndex ) ;
return overlayIndex ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private static int GetOverlayCount ( this ImageList imageList ) = > imageListOverlays . TryGetValue ( imageList , out var vals ) ? vals . Count : 0 ;
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
private static void ImageList_RecreateHandle ( object? sender , EventArgs e )
2023-03-31 11:47:53 -04:00
{
2023-09-29 13:58:35 -04:00
if ( sender is not ImageList il | | ! imageListOverlays . TryGetValue ( il , out var vals ) ) return ;
2023-03-31 11:47:53 -04:00
var iil = il . GetIImageList ( ) ;
for ( var i = 0 ; i < vals . Count ; i + + )
iil . SetOverlayImage ( vals [ i ] , i + 1 ) ;
2017-11-27 13:11:20 -05:00
}
}