向 ImageList 控件添加和使用 32 位 alpha 混合图像和图标






4.96/5 (38投票s)
2004年12月23日
8分钟阅读

313741

5850
一篇关于向 imagelist 控件添加 alpha 混合图像并将其用于组件的文章。
引言
随着时间的推移和应用程序的演变,最终用户期望拥有更丰富、更生动的用户界面。一个好的用户界面不仅在视觉上吸引人,而且在功能上也有作用。通过更好的设计和图标的使用来代表任务,不仅能让你的应用程序脱颖而出,还能为用户提供更好的使用体验。这就是我决定发表这篇文章的原因。现在,让我们进入技术细节……
我们都见过微软 (c) 在其产品中使用的那些漂亮的工具栏图标。它们包含阴影、24 位真彩色、透明等。这些图像实际上是 24 位图像,包含 alpha 通道,使其成为 32 位格式。这种格式不仅允许像素透明,还允许像素半透明。这使得图像能够很好地与背景融合,而不会像常规 24 位透明图像那样出现“锯齿”。如果你曾经尝试在应用程序中使用这类图标或图像,你会很快发现这比预期的要困难一些,而且确实有一些障碍需要克服。更不用说框架中存在的一个 bug,它会阻止你正确显示这类图像。在本文中,我将尝试解释克服这些障碍需要什么,以及如何使用我的 ImageList
类来修复这个问题。从现在开始,我将把图标和图像都称为图像,并将 24 位 alpha 混合图像称为 32 位图像。
问题
我认为上面提到的 bug 存在于 ImageList
组件本身。如果你尝试使用 ImageList
控件的 Images.Add()
方法添加 32 位图像,或者尝试使用 Visual Studio IDE 添加图像,你会发现 alpha 通道会根据图像文件本身的原始格式而损坏或完全丢失。例如,你可以创建一个新的 Bitmap
对象,并使用如下的 Bitmap
构造函数加载一个 32 位图像(如 .png 文件):Bitmap bm = new Bitmap( file );
,然后使用 ImageList
.Add 方法将位图对象添加到 ImageList
。即使位图的 .IsAlphaFormat
属性为 true
,在添加到 ImageList
控件的过程中,图像也会损坏,并且如果你尝试在组件上使用这些图像,你会发现它们显示不正确。我相信问题存在于 .Add()
方法从位图结构中提取 alpha 通道并将其复制到 ImageList
的方式。我得出这个结论的原因是,通过为 .Add()
方法提供一个图像句柄似乎可以正常工作。你会看到我使用这种方法处理图标(.ico 文件),因为 Icon
类有一个 .FromHandle
方法,它返回一个 Windows 图标。
现在,如果你尝试将这些添加的图像用于你的组件,你会发现由于 alpha 通道已损坏,图像中用于 alpha 混合的区域会显示为纯黑色。在添加带有 alpha 通道的 PNG 文件的情况下,你会发现 alpha 混合确实发生了,但在用于 alpha 混合的区域会存在细微的颜色错误。你可以在上面提供的图像中看到一个很好的例子,看看左边放大的 IE 和 MSN 图标。对于这些 32x32 像素的图标来说,这可能并不显眼,但当你有 16x16 的图标时,颜色错误会非常明显。总而言之,ImageList
控件本身可以毫无问题地存储 32 位 alpha 混合图像,但用于添加这些图像的方法导致了损坏的发生。
修复方案
我提出一个简单的类,其中包含一些静态方法,用于将 32 位图像添加到你的 ImageList
中。该类 Imagelist
及其五个方法允许你从各种来源正确添加图像。这些来源包括 Image
或 Icon
对象、嵌入在你的项目中的图像资源、任何外部文件图标或文件夹图标,以及任何文件系统扩展图标。这些方法都有示例和说明。用于从不同来源添加图像的各种方法,但主要部分使用的是 Win32 API。当从外部文件或 Image
对象添加图像时,基本操作是使用 gdi32.dll 中的 CreateDIBSection()
API 调用创建一个新的 DIB(设备无关位图)对象,该对象会创建一个位图对象并返回内存中位图对象的句柄。然后,将图像以正确的格式复制到这个内存区域。然后使用 ImageList_Add()
调用将位图加载到 ImageList
中,该调用需要一个 ImageList
控件的句柄和一个位图对象的句柄。添加 Icon
对象或文件时,可以使用 Icon
对象的 .Handle
属性正确添加。一旦图像被加载到 ImageList
控件中,你就可以像平常一样将它们用于工具栏、列表视图等,方法是将索引分配给组件。此外,它的所有原生方法都保持并返回正确的值,例如 .Count
属性。
在使用代码之前
首先,你的 C# 应用程序和组件属性必须正确设置,图像才能正确显示。在继续使用该类之前,让我们先介绍这些。你将在下面看到,必须在 .Run
方法之前调用 Application.EnableVisualStyles()
和 Application.DoEvents()
。另一个必须设置的关键属性是 ImageList
的 ColorDepth
属性,该属性 **必须** 设置为 ColorDepth.Depth32Bit
。当然,你可以使用 Visual Studio IDE 设置此属性。另请注意,alpha 混合图像仅在 Windows XP+ 和 .NET Framework 1.1+ 中受支持。此代码对早期版本的 Windows 无效。
//
// Enabling Visual Styles
//
static void Main()
{
// These two calls MUST be made before the
// .Run method to enable 32bit icons to be used.
Application.EnableVisualStyles();
Application.DoEvents();
// The color depth of the ImageList MUST be set
// to 32bit before images can be added to the control.
myImagelist.ColorDepth = ColorDepth.Depth32Bit;
Application.Run(new Form1());
}
ImageList 类的公共方法
public sealed class Imagelist
{
public static void AddFromImage( Image sourceImage,
ImageList destinationImagelist ){..}
public static void AddFromImage( Icon sourceIcon,
ImageList destinationImagelist ){..}
public static void AddFromFile( string fileName,
ImageList destinationImagelist ){..}
public static void AddIconOfFile( string fileName, IconSize iconSize,
bool selectedState, bool openState,
bool linkOverlay, ImageList destinationImagelist ){..}
public static void AddIconOfFile( string fileName,
ImageList destinationImagelist ){..}
public static void AddIconOfFileExt( string fileExtension,
IconSize iconSize, bool selectedState, bool openState,
bool linkOverlay , ImageList destinationImagelist ){..}
public static void AddIconOfFileExt( string fileExtension,
ImageList destinationImagelist ){..}
public static void AddEmbeddedResource( string resourceName,
ImageList destinationImagelist){..}
}
现在这些调用和属性都已设置好,我们可以继续使用静态类方法了……
添加 Image 或 Icon 对象
//
// This example adds a 32bit alphablended image
// to myImagelist using the AddFromImage() method.
// This overloaded method accepts either
// an Image object or Icon object as its source.
//
Narbware.Imagelist.AddFromImage( Image.FromFile("image.png"),
myImagelist );
Bitmap myBitmap = new Bitmap( "image.png" );
Narbware.Imagelist.AddFromImage( myBitmap, myImagelist );
Icon myIcon = new Icon("icon.ico");
Narbware.Imagelist.AddFromImage( myIcon, myImagelist );
从嵌入式资源添加图像
此方法的重要性在于,你可以使用它从嵌入式资源预加载图像到 ImageList
。这意味着在 ImageList
中需要预加载的图像时,不需要外部图像文件。这是因为你无法使用 IDE 预加载图像,因为 IDE 实际上使用了 ImageList.Add()
方法。添加嵌入式资源时的一个重要步骤是,将图像资源添加到项目后,必须将其“生成操作”属性设置为“嵌入的资源”。完成后,下面的代码会将该资源添加到你的 ImageList
中。
//
// This example adds a 32bit alphablended image from
// an embedded resource to myImagelist
// using the AddEmbeddedResource() method.
//
Narbware.Imagelist.AddEmbeddedResource( "myApplicationName.image.png",
myImagelist ); // where image.png resides in the projects root
从外部文件添加图像
当你需要从项目目录外的文件添加图像时,此方法可以节省一些时间。因为你无需创建 Image
,将文件加载到 Image
,然后将其添加到 ImageList
。相反,你只需提供要添加的图像的路径。
//
// This example adds a 32bit alphablended image from an external
// file to myImagelist using the AddFromFile() method.
//
Narbware.Imagelist.AddFromFile( "c:\\blah\image.png", myImagelist );
Narbware.Imagelist.AddFromFile( "c:\\icons\icon.ico", myImagelist );
提取并添加文件或文件夹图标
此方法提取文件或文件夹图标并将其添加到 ImageList
。这是一个强大的方法,当前框架不支持,但在处理 shell 的任何应用程序中都很有用。此方法有两个重载,一个简单重载,默认情况下会将大尺寸图标添加到 ImageList
,另一个重载允许你格式化要提取的图标。例如,添加图标的选中状态,或添加带有链接覆盖的图标,指定要提取的图标大小等……
//
// This example extracts and adds 32bit alphablended icons
// from external files and folders
// to myImagelist using the AddIconOfFile() method.
//
// This short overload extracts and adds
// a largs icon (32x32) to and imagelist
Narbware.Imagelist.AddIconOfFile( "c:\\file.mpg",
myImagelist )
// adds the files icons, in this case the systems mpg icon
// This longer method gives you more
// options on the type of icon to extract
Narbware.Imagelist.AddIconOfFile( "c:\\file.mpg",
Narbware.IconSize.Small, false, false,
false, myImagelist );
// This adds a folder icon. It can also extract
// special folder icons such as My Music, My Pictures, etc...
Narbware.Imagelist.AddIconOfFile( "c:\mySpecialFolder",
Narbware.IconSize.Large, false, false,
false, myImagelist );
提取并添加文件类型图标(扩展名)
此方法与上面的 AddIconOfFile()
方法类似,只不过你需要向该方法提供要从系统中提取图标的扩展名。上面方法的所有规则都适用于此方法。示例见下文……
//
// This example extracts and adds 32bit alphablended file
// extension icons to myImagelist using the AddIconOfFileExt() method.
//
// adds a large sized icon associated with jpeg files.
Narbware.Imagelist.AddIconOfFileExt( "jpg", myImagelist );
Narbware.Imagelist.AddIconOfFileExt( "zip", Narbware.IconSize.Small,
false, false, false, myImagelist );
// adds a small sized zip file icon to the imagelist.
关注点
下面是用于将 32 位图像添加到 ImageList
的 private
方法。这实际上是解决 alpha 通道损坏问题的关键。请注意,会根据 BITMAPINFO
结构创建一个具有正确格式的新设备无关位图 (DIB) 对象,然后将所需的位图复制到该位置,并使用 Win32 API 添加到我们的 ImageList
中。另一个重载的 Add()
方法用于提取文件图标,这是通过创建并传递一个 SHFileInfo
对象并返回文件图标的句柄来完成的,然后使用默认的 ImageList
的 Images.Add();
方法将其添加。
private static void Add( Bitmap bm, ImageList il )
{
IntPtr hBitmap, ppvBits;
BITMAPINFO bmi = new BITMAPINFO();
if ( bm.Size != il.ImageSize )
{
// resize the image to dimensions of imagelist before adding
bm = new Bitmap( bm, il.ImageSize.Width, il.ImageSize.Height );
}
bmi.biSize = 40; // Needed for RtlMoveMemory()
bmi.biBitCount = 32; // Number of bits
bmi.biPlanes = 1; // Number of planes
bmi.biWidth = bm.Width; // Width of our new bitmap
bmi.biHeight = bm.Height; // Height of our new bitmap
bm.RotateFlip( RotateFlipType.RotateNoneFlipY );
// Required due to the way bitmap is copied and read
hBitmap = CreateDIBSection( new IntPtr(0), bmi, 0,
out ppvBits, new IntPtr(0), 0 );
//create our new bitmap
BitmapData bitmapData = bm.LockBits( new Rectangle( 0, 0,
bm.Width, bm.Height ), ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb );
RtlMoveMemory( ppvBits, bitmapData.Scan0,
bm.Height * bitmapData.Stride );
// copies the bitmap
bm.UnlockBits( bitmapData );
ImageList_Add( il.Handle, hBitmap, new IntPtr(0) );
// adds the new bitmap to the imagelist control
}
结论
通过使用 ImageList
类及其从各种来源添加图像的方法,你现在就可以在你的 C# 应用程序中使用 32 位 alpha 混合图像了。图像添加完成后,你可以继续像往常一样使用 ImageList
控件。如果你认为本文中有不正确或误导性的信息,请随时指出。我 C# 方面还算新手,可能还有些地方不够完善。另外,任何评论或建议都将不胜感激。谢谢。