65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (38投票s)

2004年12月23日

8分钟阅读

viewsIcon

313741

downloadIcon

5850

一篇关于向 imagelist 控件添加 alpha 混合图像并将其用于组件的文章。

Sample Image

引言

随着时间的推移和应用程序的演变,最终用户期望拥有更丰富、更生动的用户界面。一个好的用户界面不仅在视觉上吸引人,而且在功能上也有作用。通过更好的设计和图标的使用来代表任务,不仅能让你的应用程序脱颖而出,还能为用户提供更好的使用体验。这就是我决定发表这篇文章的原因。现在,让我们进入技术细节……

我们都见过微软 (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 及其五个方法允许你从各种来源正确添加图像。这些来源包括 ImageIcon 对象、嵌入在你的项目中的图像资源、任何外部文件图标或文件夹图标,以及任何文件系统扩展图标。这些方法都有示例和说明。用于从不同来源添加图像的各种方法,但主要部分使用的是 Win32 API。当从外部文件或 Image 对象添加图像时,基本操作是使用 gdi32.dll 中的 CreateDIBSection() API 调用创建一个新的 DIB(设备无关位图)对象,该对象会创建一个位图对象并返回内存中位图对象的句柄。然后,将图像以正确的格式复制到这个内存区域。然后使用 ImageList_Add() 调用将位图加载到 ImageList 中,该调用需要一个 ImageList 控件的句柄和一个位图对象的句柄。添加 Icon 对象或文件时,可以使用 Icon 对象的 .Handle 属性正确添加。一旦图像被加载到 ImageList 控件中,你就可以像平常一样将它们用于工具栏、列表视图等,方法是将索引分配给组件。此外,它的所有原生方法都保持并返回正确的值,例如 .Count 属性。

在使用代码之前

首先,你的 C# 应用程序和组件属性必须正确设置,图像才能正确显示。在继续使用该类之前,让我们先介绍这些。你将在下面看到,必须在 .Run 方法之前调用 Application.EnableVisualStyles()Application.DoEvents()。另一个必须设置的关键属性是 ImageListColorDepth 属性,该属性 **必须** 设置为 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 位图像添加到 ImageListprivate 方法。这实际上是解决 alpha 通道损坏问题的关键。请注意,会根据 BITMAPINFO 结构创建一个具有正确格式的新设备无关位图 (DIB) 对象,然后将所需的位图复制到该位置,并使用 Win32 API 添加到我们的 ImageList 中。另一个重载的 Add() 方法用于提取文件图标,这是通过创建并传递一个 SHFileInfo 对象并返回文件图标的句柄来完成的,然后使用默认的 ImageListImages.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# 方面还算新手,可能还有些地方不够完善。另外,任何评论或建议都将不胜感激。谢谢。

© . All rights reserved.