如何从代码后台设置图像资源URI

14

我正在尝试将一个PNG图形嵌入到DLL中,并将其作为BitmapImage加载到Image控件中。然而,WPF一直抛出异常,称找不到资源。

首先,以下是一些最小化的示例代码和重现问题的步骤:

  • Create a WPF project named ImageResTest with an empty main window (you can set the default namespace to ImageResTest). The code-behind file of the main window should look like this:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace ImageResTest
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
    
                var obj = new MyData.SomeStuff.MyClass();
    
                this.Content = obj.Img;
            }
        }
    }
    
  • Create a class library named ImageResTestLib (you can set the default namespace to ImageResTest, as above, so everything discussed here is in the same root namespace).

  • Add references from ImageResTestLib to PresentationCore, PresentationFramework, System.Xaml and WindowsBase.
  • Add a reference from ImageResTest to ImageResTestLib.
  • Inside ImageResTestLib, add the folder hierarchy MyData/SomeStuff/Resources.
  • In the SomeStuff folder, add the following file MyClass.cs:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    
    namespace ImageResTest.MyData.SomeStuff
    {
        public class MyClass
        {
            public MyClass()
            {
                img = new Image();
                {
                    var bmp = new BitmapImage();
                    bmp.BeginInit();
                    bmp.UriSource = new Uri(@"/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png", UriKind.RelativeOrAbsolute);
                    bmp.EndInit();
    
                    img.Source = bmp;
                    img.Width = bmp.PixelWidth;
                }
            }
    
            private Image img;
    
            public Image Img {
                get {
                    return img;
                }
            }
        }
    }
    
  • In the Resources folder, add a PNG file named Img.png and set its build action to Resource (as suggested, for example, here).

所迄今为止,一切都很好——启动此应用程序应该创建一个窗口,该窗口实例化MyClass并检索由该MyClass实例创建的Image。该图像应填充了一个BitmapImage,其数据从作为资源包含的图形中加载。
不幸的是,似乎资源URI存在问题。至今MSDN文档也没有提供帮助。
我尝试了以下几种变体的资源URI:
上面示例代码中所描述的表单 - /AssemblyName;component/Path/Filename - 建议在这里这里使用,但会抛出DirectoryNotFoundException异常,表示未找到路径的一部分C:\ImageResTestLib;component\MyData\SomeStuff\Resources\Img.png
建议在这里这里这里这里使用pack://application:,,,/MyData/SomeStuff/Resources/Img.png,但会抛出IOException异常,表示无法找到资源mydata/somestuff/resources/img.png
也建议在这里这里使用pack://application:,,,/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png,但会抛出FileNotFoundException异常,表示未找到ImageResTestLib, Culture=neutral或其依赖项之一。 Resources/Img.png(相对于代码文件)在这里这里中被暗示使用,但会抛出DirectoryNotFoundException异常,表示未找到C:\Users\myusername\Documents\Test\DOTNET\WPFTest\ImageResTest\bin\Debug\Resources\Img.pngMyData/SomeStuff/Resources/Img.png(相对于项目)也被暗示在这里中使用,行为类似于前一个。

由于这些方法都无法起作用,我尝试了以下基于ResourceDictionary的解决方法:

  • Add a WPF resource dictionary named MyClassResources.xaml in the SomeStuff folder.
  • In that resource dictioanry, add a BitmapImage resource with the key img.
  • Change the contents of MyClass.cs like this:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    
    namespace ImageResTest.MyData.SomeStuff
    {
        public class MyClass
        {
            public MyClass()
            {
                ResourceDictionary dict = new ResourceDictionary();
                dict.Source = new Uri("/ImgResTestLib;component/MyData/SomeStuff/MyClassResources.xaml", UriKind.RelativeOrAbsolute);
    
                img = new Image();
                {
                    var bmp = (BitmapImage)dict["img"];
    
                    img.Source = bmp;
                    img.Width = bmp.PixelWidth;
                }
            }
    
            private Image img;
    
            public Image Img {
                get {
                    return img;
                }
            }
        }
    }
    
现在,资源字典可以从指定的URI加载(当删除资源字典的内容时,加载成功完成)。然而,当使用像/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png这样的路径时,仍然找不到PNG图形。
我做错了什么?如何加载相应的资源(如果可能,不使用额外的资源字典)?

编辑:更多信息:

  • 我使用的是德语Windows 7 x64
  • .NET 4.0客户端被设置为目标框架
  • 为了确保,我尝试了在Visual Studio 2010和SharpDevelop 4.3.3中构建和运行这个程序,两次都导致相同的异常。

基于Ian的代码,我得到的FileNotFoundException的堆栈跟踪如下:

System.Windows.Markup.XamlParseException: Zeilennummer "3" und Zeilenposition "2" von "Durch den Aufruf des Konstruktors für Typ "ImageResTest.Window1", der den angegebenen Bindungseinschränkungen entspricht, wurde eine Ausnahme ausgelöst.". ---> System.IO.FileNotFoundException: Die Datei oder Assembly "ImageResTestLib, Culture=neutral" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.
   bei System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   bei System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   bei System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   bei System.Reflection.Assembly.Load(AssemblyName assemblyRef)
   bei System.Windows.Navigation.BaseUriHelper.GetLoadedAssembly(String assemblyName, String assemblyVersion, String assemblyKey)
   bei MS.Internal.AppModel.ResourceContainer.GetResourceManagerWrapper(Uri uri, String& partName, Boolean& isContentFile)
   bei MS.Internal.AppModel.ResourceContainer.GetPartCore(Uri uri)
   bei System.IO.Packaging.Package.GetPartHelper(Uri partUri)
   bei System.IO.Packaging.Package.GetPart(Uri partUri)
   bei System.IO.Packaging.PackWebResponse.CachedResponse.GetResponseStream()
   bei System.IO.Packaging.PackWebResponse.GetResponseStream()
   bei System.IO.Packaging.PackWebResponse.get_ContentType()
   bei System.Windows.Media.Imaging.BitmapDecoder.SetupDecoderFromUriOrStream(Uri uri, Stream stream, BitmapCacheOption cacheOption, Guid& clsId, Boolean& isOriginalWritable, Stream& uriStream, UnmanagedMemoryStream& unmanagedMemoryStream, SafeFileHandle& safeFilehandle)
   bei System.Windows.Media.Imaging.BitmapDecoder.CreateFromUriOrStream(Uri baseUri, Uri uri, Stream stream, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption, RequestCachePolicy uriCachePolicy, Boolean insertInDecoderCache)
   bei System.Windows.Media.Imaging.BitmapImage.FinalizeCreation()
   bei System.Windows.Media.Imaging.BitmapImage.EndInit()
   bei ImageResTest.MyData.SomeStuff.MyClass..ctor(Uri baseUri) in C:\Users\username\Documents\Test\DOTNET\WPFTest\ImgResTestLib\MyData\SomeStuff\MyClass.cs:Zeile 36.
   bei ImageResTest.Window1..ctor() in c:\Users\username\Documents\Test\DOTNET\WPFTest\ImageResTest\Window1.xaml.cs:Zeile 17.
   --- End of inner exception stack trace ---
   bei System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
   bei System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri)
   bei System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
   bei System.Windows.Application.LoadBamlStreamWithSyncInfo(Stream stream, ParserContext pc)
   bei System.Windows.Application.LoadComponent(Uri resourceLocator, Boolean bSkipJournaledProperties)
   bei System.Windows.Application.DoStartup()
   bei System.Windows.Application.<.ctor>b__1(Object unused)
   bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   bei MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   bei System.Windows.Threading.DispatcherOperation.InvokeImpl()
   bei System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   bei System.Windows.Threading.DispatcherOperation.Invoke()
   bei System.Windows.Threading.Dispatcher.ProcessQueue()
   bei System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   bei MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   bei MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   bei MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   bei System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   bei MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   bei MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   bei System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   bei System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   bei System.Windows.Threading.Dispatcher.Run()
   bei System.Windows.Application.RunDispatcher(Object ignore)
   bei System.Windows.Application.RunInternal(Window window)
   bei System.Windows.Application.Run(Window window)
   bei System.Windows.Application.Run()
   bei ImageResTest.App.Main() in c:\Users\username\Documents\Test\DOTNET\WPFTest\ImageResTest\obj\Debug\App.g.cs:Zeile 0.

编辑2:

添加

Debug.WriteLine(typeof(MyData.SomeStuff.MyClass).Assembly.GetName().FullName);

将代码传递给主窗口的构造函数会产生以下输出:

ImgResTestLib, Version=1.0.5123.16826, Culture=neutral, PublicKeyToken=null

调用

Debug.WriteLine(BaseUriHelper.GetBaseUri(this).ToString());

打印以下内容:
pack://application:,,,/ImageResTest;component/window1.xaml

编辑3:

虽然被接受的答案解决了这个问题所描述的问题,但我在实际项目中无法看到图形的实际原因却完全不同:

虽然VS 2010和SharpDevelop都没有给出任何提示,但标记为Resource的资源实际上有一个逻辑名称(在我的情况下,它们保留了我在试探性地将构建操作设置为EmbeddedResource并更改逻辑名称时的名称)。逻辑名称仍然出现在MSBuild文件中的<LogicalName>元素中,从我在ILSpy中看到的内容来看,那就是编译后程序集中实际使用的资源名称。

这样一个带有逻辑名称的资源的正确(有效)资源URI似乎是:

/MyAssembly;component/LogicalResourceName

(因此,像嵌入式资源一样替换资源的目录路径)

虽然在构建操作设置为Resource时无法在VS或SharpDevelop中更改逻辑名称,但是删除资源并重新添加文件,然后将构建操作设置为Resource,使基于文件名的URI再次起作用,因为逻辑名称不再在项目文件中。同样,手动从MSBuild文件中删除<LogicalName>元素也应该可以起作用。


你的指示将 MyClass 放在 ImageRestTestLib 组件中,但是你的源代码显示命名空间为 ImageRestTest,这意味着它在主 ImageRestTest 项目中。显然,没有什么阻止你使用不同的命名空间,但我想检查一下是否有指示上的错误。 - Ian Griffiths
@IanGriffiths:不,这是有意为之的,这样所有内容都在同一个根命名空间中。感谢您的评论;我已经在上面的描述中添加了一条注释。 - O. R. Mapper
2
难以置信...浪费了数小时的时间尝试让它正常工作,最后只是删除了图像并重新添加,然后将其设置为资源,“神奇地”就可以工作了... 我喜欢自己似乎可以在一个小时内创建一个可用的应用程序,但却需要整整一天的时间才能将一张图片放入其中...! - Rob
1个回答

20

问题的一部分在于WPF没有上下文用于解析该URL。它是相对URL,通常情况下,它将相对于其所使用的XAML内容的基本URI进行解析。如果我在这段代码中使用与您开始使用的完全相同的URL:

public MainWindow()
{
    InitializeComponent();

    var img = new Image();
    Content = img;
    var bmp = new BitmapImage();
    bmp.BeginInit();
    bmp.UriSource = new Uri(@"/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png", UriKind.RelativeOrAbsolute);
    bmp.EndInit();

    img.Source = bmp;
    img.Width = bmp.PixelWidth;
}

然后它就正常工作了。很明显,这是MainWindow的代码后端。

只需进行微小更改,将这一行移动:

Content = img;

最终,我遇到了与您相同的DirectoryNotFoundException异常。

WPF会在您将BitmapImage分配为ImageSource属性时,尝试将该URI解析为实际资源。我的第一个示例能够工作是因为Image位于可视树中,因此它会获取MainWindow.xaml的基本URI,并相对于该基本URI解析该资源URI。

如果您确实需要在将其与可视树相关联之前创建Image,则有多种选项可供选择。您可以实际上设置图像的基本URI:

img.SetValue(BaseUriHelper.BaseUriProperty, baseUri);

不过,这有点奇怪。更简单的方法是构造一个绝对URI,例如:

bmp.UriSource = new Uri(
    baseUri,
    @"/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png");

当然,这两种情况都假定您知道基本URI是什么。 您可以在MainWindow构造函数中询问以查找它:

public MainWindow()
{
    InitializeComponent();

    var baseUri = BaseUriHelper.GetBaseUri(this);
    ...

在您的情况下,应该是:pack://application:,,,/ImageResTest;component/mainwindow.xaml 这将进一步明确解析后的URI应该是:pack://application:,,,/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png 有趣的是,您说您尝试了上述URI并出现了错误。然而,我正在尝试这个精确的URI,但我没有遇到错误。为了更清楚,请看一下我修改过的MyClass构造函数:
public MyClass(Uri baseUri)
{
    img = new Image();
    var bmp = new BitmapImage();
    bmp.BeginInit();
    bmp.UriSource = new Uri(baseUri, @"/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png");
    bmp.EndInit();

    img.Source = bmp;
    img.Width = bmp.PixelWidth;
}

这是我的MainWindow构造函数:

public MainWindow()
{
    InitializeComponent();

    var obj = new MyData.SomeStuff.MyClass(BaseUriHelper.GetBaseUri(this));

    this.Content = obj.Img;
}

在遵循你的指示后,这对我起作用了。如果我理解正确的话,当你执行此操作时,你会看到一个FileNotFoundException。这让我想知道你的指示是否漏掉了一些内容。例如,如果ImageResTestLib被强命名,我会期望看到这个错误(如果你想引用强命名库中的资源,在;component之前需要一个完全限定的程序集显示名称)。
另一个选项是使用Application.GetResourceStream,以及BitmapImage.StreamSource属性。但再次提醒,这将需要一个可用的URL,所以你可能会遇到之前遇到的同样问题。一旦你弄清楚了在你的项目中有什么不同,导致pack://application:,,,/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png不能正常工作,那么你已经拥有的基本方法应该就可以了。

我已经在我的问题中添加了调试输出作为EDIT2。至于异常,我不确定如何处理:XamlParseException的调用堆栈不包含任何对Assembly.Load的调用。内部异常是一个FileNotFoundException,其调用堆栈包含对Assembly.Load的调用,因此我配置了VS在抛出FileNotFoundException时中断。现在它确实会中断FileNotFoundException,并且将异常详细信息复制到剪贴板确实显示了对Assembly.Load的调用,但是VS的调用堆栈视图没有显示;它显示完全不同的调用堆栈。 - O. R. Mapper
通过VS复制到剪贴板的FileNotFoundException的调用堆栈的前三行是对RuntimeAssembly._nLoadRuntimeAssembly.nLoadRuntimeAssembly.InternalLoadAssemblyName的调用。在调用堆栈视图中的前三行是对BitmapSource.CompleteDelayedCreationBitmapSource.PixelWidth.get的调用,以及在进行本机到托管和托管到本机的转换后,对RuntimeType.CreateInstanceSlow的调用。 - O. R. Mapper
哎呀...我终于发现我错误地将我的DLL项目命名为“ImgResTestLib”,而在我的资源URI中使用了“ImageResTestLib”。在更正后,您的解决方案在我的最小示例中运行。这是否意味着以pack://application:,,,/...开头的绝对URI通常会起作用?(我正在查询,因为我在我的实际项目中找不到任何这样的命名错误,尽管我似乎无法在最小示例中重现该问题。尽管如此,我仍然接受这个答案,因为您解决了我在问题中展示的内容。) - O. R. Mapper
1
我已经解决了我的问题,并且它是由于完全不同的原因,我称之为VS和SharpDevelop中的一个错误。如果您有兴趣,请查看我的EDIT3以获取说明,其中我概述了我认为负责的内容。 - O. R. Mapper
1
哇,这对我来说是新鲜事。 (而且你之前的问题的答案是,我猜你现在已经知道了,是的,那个绝对URI通常应该有效。) - Ian Griffiths
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接