17

如何为资源文件中的图像构建包 URI?

我有一个名为 的程序集MyAssembly.Resources.dll,它有一个名为Images的文件夹,然后有一个名为Assets.resx的资源文件。此资源文件包含我的图像(称为MyImage.png)。我的代码行是:

uri = new Uri("pack://application:,,,/MyAssembly.Resources,Culture=neutral,PublicKeyToken=null;component/Images/Assets/MyImage.png");

但是,当我尝试将此 URI 提供给新BitmapImage的构造函数时,我得到一个带有消息的IOException

找不到资源“images/assets/myimage.png”。

请注意,我在同一个程序集中还有其他松散的图像,我可以使用包 URI 很好地检索这些图像,这些图像的构建操作设置为 Resource但它们没有嵌入 resx 文件中。我应该在路径中包含 resx 文件的名称吗?

(我希望在 resx 文件中嵌入图像,以便我可以利用 UI 文化设置来检索正确的图像(图像包含文本))。

4

4 回答 4

17

我认为不可能使用“pack”协议方案。该协议与规范化的开放包装约定规范相关(http://tools.ietf.org/id/draft-shur-pack-uri-scheme-05.txt用于指针)。因此,pack uri 指向应用程序包的资源(或 OPC 术语中的部分),而不是 .NET 嵌入式资源。

但是,您可以定义自己的方案,例如“resx”并在 WPF 组件 uris 中使用它。可以使用WebRequest.RegisterPrefix定义用于此类用途的新 Uri 方案。

这是一个基于名为“WpfApplication1”的小型 Wpf 应用程序项目的示例。此应用程序定义了一个 Resource1.resx 文件(可能还有其他本地化的相应 Resource1 文件,例如法语的 Resource1.fr-FR.resx)。这些 ResX 文件中的每一个都定义了一个名为“img”的图像资源(请注意,此名称与资源所基于的图像文件名不同)。

这是 MainWindow.xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Image Source="resx:///WpfApplication1.Resource1/img" />
</Window>

uri 格式是这样的:

resx://assembly name/resource set name/resource name

并且程序集名称是可选的,所以

resx:///resource set name/resource name

也是有效的并指向主程序集中的资源(我的示例使用这个)

这是支持它的代码,在 App.xaml.cs 或其他地方,您需要注册新方案:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        ResXWebRequestFactory.Register();
        base.OnStartup(e);
    }
}

及方案实施:

public sealed class ResXWebRequestFactory : IWebRequestCreate
{
    public const string Scheme = "resx";
    private static ResXWebRequestFactory _factory = new ResXWebRequestFactory();

    private ResXWebRequestFactory()
    {
    }

    // call this before anything else
    public static void Register()
    {
        WebRequest.RegisterPrefix(Scheme, _factory);
    }

    WebRequest IWebRequestCreate.Create(Uri uri)
    {
        return new ResXWebRequest(uri);
    }

    private class ResXWebRequest : WebRequest
    {
        public ResXWebRequest(Uri uri)
        {
            Uri = uri;
        }

        public Uri Uri { get; set; }

        public override WebResponse GetResponse()
        {
            return new ResXWebResponse(Uri);
        }
    }

    private class ResXWebResponse : WebResponse
    {
        public ResXWebResponse(Uri uri)
        {
            Uri = uri;
        }

        public Uri Uri { get; set; }

        public override Stream GetResponseStream()
        {
            Assembly asm;
            if (string.IsNullOrEmpty(Uri.Host))
            {
                asm = Assembly.GetEntryAssembly();
            }
            else
            {
                asm = Assembly.Load(Uri.Host);
            }

            int filePos = Uri.LocalPath.LastIndexOf('/');
            string baseName = Uri.LocalPath.Substring(1, filePos - 1);
            string name = Uri.LocalPath.Substring(filePos + 1);

            ResourceManager rm = new ResourceManager(baseName, asm);
            object obj = rm.GetObject(name);

            Stream stream = obj as Stream;
            if (stream != null)
                return stream;

            Bitmap bmp = obj as Bitmap; // System.Drawing.Bitmap
            if (bmp != null)
            {
                stream = new MemoryStream();
                bmp.Save(stream, bmp.RawFormat);
                bmp.Dispose();
                stream.Position = 0;
                return stream;
            }

            // TODO: add other formats
            return null;
        }
    }
}
于 2013-05-09T09:54:13.423 回答
6

有两种方法可以将资源“嵌入”到程序集中。Windows 窗体使用Embedded Resource生成操作。WPF 期望程序集中包含的资源使用Resource生成操作进行标记。

在 Visual Studio 中使用 Resx 编辑器添加图像时,它会将其标记为嵌入式资源。此外,它将其存储为 type System.Drawing.Bitmap。WPF 期望一个System.Windows.Media.ImageSource类型。

如果您有一个反汇编程序(如 ILSpy),您可以查看对文件设置不同构建操作的影响。

示例 ImagesLib 项目

这是一个包含两个图像的项目的屏幕截图。从名称中可以明显看出,cat_embedded.jpg正在使用Embedded ResourceBuild 操作,并且cat_resource.jpg正在使用ResourceBuild 操作。

在此处输入图像描述

这就是它们在 ILSpy 中的样子。

在此处输入图像描述

看看 cat_resource.jpg 文件是如何在 ImageLib.g.resources 部分中的?那是 WPF 寻找资源的地方。文件的路径是资源名称 ( images/cat_resource.jpg) 的一部分。因此,当您使用如下路径时:

var uri = new Uri("pack://application:,,,/ImageLib;component/Images/cat_resource.jpg");

您在 word 之后指定匹配路径;component

另一个 jpg 文件位于程序集中的不同位置,并在名称中使用句点 ( ImageLib.Images.cat_embedded.jpg)。

您可以尝试对该字符串进行多种排列来尝试获取 cat_embedded.jpg 图像,但 WPF 不会找到它。

RESX 编辑器

这是另一个项目,它有两张图片,一张标记为资源,一张由 resx 编辑器添加。

在此处输入图像描述

这是反汇编的屏幕截图。

在此处输入图像描述

如您所见,resx 图像使用与前面嵌入图像示例相同的 URI 位置。在您的情况下,您将无法使用 Pack URI 从 resx 文件中获取图像。

本土化

根据您在问题中所说的,您要完成的是图像的本地化,对吗?

你看过这篇 MSDN 文章吗?

WPF 全球化和本地化概述

于 2013-05-09T08:03:12.620 回答
5

正如沃尔特正确指出的那样,您从 resx 文件中得到的是一个System.Drawing.Bitmap. 所以这需要转换为一个System.Windows.Media.ImageSource或子类型。

我不确定这是否会浪费您的时间,因为它不使用 URI,但这是我从另一个库中的 resx 文件获取图像的方法。我使用了一个简单的代理,因为 resx 设计器文件只公开了一个内部构造函数(即使该类是公共的),然后定义一个将提供 ImageSource 的 ValueConverter。

在此处输入图像描述

<Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:WpfApplication1"
            xmlns:resx="clr-namespace:MyAssembly.Resources;assembly=MyAssembly.Resources"
            Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <resx:AssetsProxy x:Key="Assets" />
        <resx:BitmapToImageSourceConverter x:Key="BitmapConverter" />
    </Window.Resources>

    <Image Source="{Binding myimage, Source={StaticResource Assets}, Converter={StaticResource BitmapConverter}}" />
</Window>

资产代理:

namespace MyAssembly.Resources
{
    public class AssetsProxy : Images.Assets
    {
        public AssetsProxy() : base() { }
    }
}

位图到 ImageSource 的转换:

using System;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace MyAssembly.Resources
{
    /// <summary>
    /// Converts a BitmapImage, as provided by a resx resource, into an ImageSource/BitmapImage
    /// </summary>
    public class BitmapToImageSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            BitmapImage bitmapImage = null;
            if (value is System.Drawing.Image)
            {
                bitmapImage = ((System.Drawing.Image)value).ToBitmapImage();
            }
            return bitmapImage;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public static class BitmapExtensions
    {
        /// <summary>
        /// Converts the System.Drawing.Image to a System.Windows.Media.Imaging.BitmapImage
        /// </summary>
        public static BitmapImage ToBitmapImage(this System.Drawing.Image bitmap)
        {
            BitmapImage bitmapImage = null;
            if (bitmap != null)
            {
                using (MemoryStream memory = new MemoryStream())
                {
                    bitmapImage = new BitmapImage();
                    bitmap.Save(memory, ImageFormat.Png);
                    memory.Position = 0;
                    bitmapImage.BeginInit();
                    bitmapImage.StreamSource = memory;
                    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                    bitmapImage.EndInit();
                }
            }
            return bitmapImage;
        }
    }
}
于 2013-05-10T09:37:42.543 回答
2

我在这篇博文中描述了一个在 WPF 中使用 resx 图像的组件:http ://wpfglue.wordpress.com/2012/05/31/localization-revisited/ 。您将在http://wpfglue.wordpress.com/category/localization/下找到更多关于在 WPF 中使用 resx 资源的帖子

在这些帖子中,我不使用包 uri,而是使用标记扩展。

于 2013-05-09T14:27:18.540 回答