在.NET程序开发中,经常需要将图像、图标和光标等资源嵌入到程序的汇编文件中。这样做的好处包括简化部署(减少需要管理的文件数量)、简化资源消耗(运行时文件不会丢失)等。本文将介绍一个名为“嵌入式图像提取器”的工具,它允许用户方便地查看、保存和复制嵌入在.NET程序中的图像资源。
在深入了解嵌入式图像提取器的工作方式之前,先来回顾一下什么是嵌入式资源。当创建一个程序的汇编文件时,可以在其中存储任意文件,比如BMP图像、XML文件等,这些文件被称为嵌入式资源。将资源嵌入到汇编文件中有几个好处,比如简化部署和资源消耗。
在Visual Studio.NET中,可以通过以下步骤轻松地将图像嵌入到程序的汇编文件中:
.NET框架为程序化检索嵌入式资源提供了支持。本文稍后将探讨这一功能的实现方式。
使用嵌入式图像提取器工具需要四个基本步骤:
提示:步骤1和2可以通过将目标汇编文件拖放到“Embedded Image Grabber.exe”上来合并。
负责从汇编文件中提取图像并在用户界面中显示的主要方法是LoadImagesFromAssembly。
private void LoadImagesFromAssembly(string assemblyPath)
{
    // 尝试加载指定位置的汇编。
    Assembly assembly = this.LoadAssembly(assemblyPath, true);
    if (assembly == null) return;
    this.currentAssembly = assembly;
    // 如果有正在显示的图像,释放它们。
    if (this.bindingSource.DataSource != null)
    foreach (ImageInfo imgInfo in this.bindingSource.DataSource as List)
        imgInfo.Dispose();
    // 绑定到汇编中嵌入的每个图像的列表。
    this.bindingSource.DataSource = this.ExtractImagesFromAssembly(this.currentAssembly);
}
     
如上方法所示,ImageGrabberForm使用BindingSource组件来协调数据绑定。BindingNavigator、DataGridView、PropertyGrid和PictureBox都绑定到绑定源,这使得GUI的同步图像导航功能非常容易实现。
private List ExtractImagesFromAssembly(Assembly assembly)
{
    List imageInfos = new List();
    foreach (string name in assembly.GetManifestResourceNames())
    {
        using (Stream stream = assembly.GetManifestResourceStream(name))
        {
            // 将资源视为图标。
            try
            {
                Icon icon = new Icon(stream);
                imageInfos.Add(new ImageInfo(icon, name));
                continue;
            }
            catch (ArgumentException)
            {
                stream.Position = 0;
            }
            // 将资源视为光标。
            try
            {
                Cursor cursor = new Cursor(stream);
                imageInfos.Add(new ImageInfo(cursor, name));
                continue;
            }
            catch (ArgumentException)
            {
                stream.Position = 0;
            }
            // 将资源视为图像。
            try
            {
                Image image = Image.FromStream(stream);
                // 如果图像是动画GIF,则不要将其添加到集合中,因为Image类无法处理它们,并且在显示图像时会抛出异常。
                FrameDimension frameDim = new FrameDimension(image.FrameDimensionsList[0]);
                bool isAnimatedGif = image.GetFrameCount(frameDim) > 1;
                if (!isAnimatedGif)
                    imageInfos.Add(new ImageInfo(image, name));
                else
                    image.Dispose();
                continue;
            }
            catch (ArgumentException)
            {
                stream.Position = 0;
            }
            // 将资源视为资源文件。
            try
            {
                // 流中的嵌入式资源不是图像,所以通过ResourceReader读取它并从中提取值。
                using (IResourceReader reader = new ResourceReader(stream))
                {
                    foreach (DictionaryEntry entry in reader)
                    {
                        if (entry.Value is Icon)
                        {
                            imageInfos.Add(new ImageInfo(entry.Value, name));
                        }
                        else if (entry.Value is Image)
                        {
                            imageInfos.Add(new ImageInfo(entry.Value, name));
                        }
                        else if (entry.Value is ImageListStreamer)
                        {
                            // 使用ImageListStreamer加载ImageList,并存储它包含的每个图像的引用。
                            using (ImageList imageList = new ImageList())
                            {
                                imageList.ImageStream = entry.Value as ImageListStreamer;
                                foreach (Image image in imageList.Images)
                                    imageInfos.Add(new ImageInfo(image, name));
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
            }
        }
    }
    return imageInfos;
}