How to Set Image Resource URI from Code-Behind
Asked Answered
H

2

14

I am trying to embed a PNG graphic into a DLL and load it into an Image control as a BitmapImage. However, WPF keeps throwing an exception saying that the resource cannot be found.

First, some minimal sample code and the steps to reproduce the problem:

  • 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).

So far, so good - launching this application should create a window which instantiates MyClass and retrieves an Image created by that MyClass instance. That image should have been filled with a BitmapImage whose data was loaded from the graphic included as a resource.

Unfortunately, there seems to be something wrong with the resource URI. The documentation on MSDN has not helped so far.

I have tried the following variants of resource URIs:

  • The form depicted in the code sample above - /AssemblyName;component/Path/Filename - was suggested here and here, but a DirectoryNotFoundException is thrown, saying that a part of the path C:\ImageResTestLib;component\MyData\SomeStuff\Resources\Img.png was not found.
  • pack://application:,,,/MyData/SomeStuff/Resources/Img.png was suggested here, here, here and here, but throws an IOException saying that the resource mydata/somestuff/resources/img.png could not be found.
  • pack://application:,,,/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png was also suggested here, as well as here, but throws a FileNotFoundException saying that ImageResTestLib, Culture=neutral or one of its dependencies was not found.
  • Resources/Img.png (relative from the code file) was implied here and here, but throws a DirectoryNotFoundException saying that C:\Users\myusername\Documents\Test\DOTNET\WPFTest\ImageResTest\bin\Debug\Resources\Img.png was not found.
  • MyData/SomeStuff/Resources/Img.png (relative to the project), also as implied here, behaves analogously to the previous one.

As none of these would work, I tried the following workaround based on a 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;
                }
            }
        }
    }
    

Now, the resource dictionary can be loaded from the indicated URI (when removing the contents of the resource dictionary, loading completes successfully). However, the PNG graphics are still not found when using a path like /ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png.

What am I doing wrong and how can I load the respective resources (if possible, without the extra resource dictionary)?


EDIT: Some more information:

  • I am using a German Windows 7 x64
  • .NET 4.0 Client is set as the target framework
  • Just to make sure, I have tried building and running this both from within Visual Studio 2010 and SharpDevelop 4.3.3; both times resulting in the same exception.

The stacktrace of the FileNotFoundException I am getting based on Ian's code is as follows:

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.

EDIT2:

Adding

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

to the constructor of the main window results in the following output:

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

The call to

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

prints the following:

pack://application:,,,/ImageResTest;component/window1.xaml

EDIT3:

While the accepted answer solves the problem described by this question, the actual reason for why I could not see my graphic in my actual project was quite something different:

While neither VS 2010 nor SharpDevelop give any indication of that, resources marked as Resource actually have a logical name (in my case, they retained it from when I had tentatively set the build action to EmbeddedResource and changed the logical name). The logical name still appears in a <LogicalName> element in the MSBuild file and from what I can see in ILSpy, that is what is actually used as the resource name in the compiled assembly.

The correct (working) resource URI to such a resource with a logical name seems to be

/MyAssembly;component/LogicalResourceName

(thus replacing the directory path to the resource, as usual for EmbeddedResource resources)

While it is not possible to change the logical name in VS or SharpDevelop while the build action is set to Resource, removing the resource and re-adding the file, then setting the build action to Resource, makes the filename-based URIs work again as the logical name will not be in the project file any more. Likewise, removing the <LogicalName> element manually from the MSBuild file should work.

Hangup answered 9/1, 2014 at 14:17 Comment(3)
Your instructions put MyClass in the ImageRestTestLib component, but your source code shows the namespace as ImageRestTest, implying it's in the main ImageRestTest project. Obviously there's nothing stopping you using different namespaces, but I wanted to check in case there was an error in the instructions.Carman
@IanGriffiths: No, that's intentional, so everything here is in the same root namespace. Thank you for the remark; I have added a comment in the description above.Hangup
unbelievable... HOURS wasted trying to get this to work and in the end, deleting the Image and adding it back in, then setting it to resource, "magically" worked... I love how i seem to create a working application in an hour, yet it takes me an entire day to put an image into it...!Expertism
C
20

Part of the problem is that WPF has no context with which to resolve that URL. It's a relative URL, and typically, it would be resolved relative to the base URI of the XAML content in which it's used. If I use exactly the same URL you start with in this code:

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;
}

then it works. That's in the codebehind for MainWindow, obviously.

With one tiny change, moving this line:

Content = img;

to the end, then I get the same DirectoryNotFoundException as you.

WPF tries to resolve that URI to an actual resource at the point at which you assign the BitmapImage as the Source property of that Image. My first example works because the Image is in the visual tree, and so it picks up the base URI of MainWindow.xaml, and resolves that resource URI relative to that base URI.

If you really need to create the Image before it gets associated with a visual tree, you've got various options. You could actually set the base URI on the image:

img.SetValue(BaseUriHelper.BaseUriProperty, baseUri);

However, that's kind of weird. It's easier just to construct an absolute URI, e.g.:

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

Both of these of course presume that you know what the base URI is. You can find that out by asking in your MainWindow constructor:

public MainWindow()
{
    InitializeComponent();

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

In your case, that'll be: pack://application:,,,/ImageResTest;component/mainwindow.xaml

This in turn makes it clear what the resolved URI should be: pack://application:,,,/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png

Interestingly, you say you try that and get an error. Well I'm trying that exact URI, and I'm not getting an error. Just to be clear, here's my modified version of your MyClass constructor:

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;
}

and here's my MainWindow constructor:

public MainWindow()
{
    InitializeComponent();

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

    this.Content = obj.Img;
}

This works for me, having followed your instructions. If I understand you correctly, you're seeing a FileNotFoundException when you do this. This makes me wonder if your instructions have omitted something. E.g., I'd expect to see this error if ImageResTestLib was strongly named. (If you want to refer to a resource in a strongly-named library, you need a fully qualified assembly display name before the ;component part.)

Another option would be to use Application.GetResourceStream, along with the BitmapImage.StreamSource property. But again, this is going to need a working URL, so you're likely going to hit the same problem as you had before. Once you work out what's different in your project that's stopping pack://application:,,,/ImageResTestLib;component/MyData/SomeStuff/Resources/Img.png from working, then the basic approach you already have should be fine.

Carman answered 9/1, 2014 at 15:15 Comment(9)
Up until the base URI, everything I am seeing here matches with what you write. Howevr, with your alternative MyClass constructor (actually, with that constructor code and , I am seeing a FileNotFoundException indicating that ImageResTestLib, Culture=neutral or one of its dependencies could not be found (see my edit of the question). None of the involved assemblies has a strong name. Will keep trying some more things.Hangup
Oh, one moment - I now also tried your very first sample code (the modified MainWindow constructor), and even that results in the same exception.Hangup
Could you try a couple of things? 1: add a Debug.WriteLine(typeof(MyData.SomeStuff.MyClass).Assembly.GetName().FullName); to the MainWindow constructor and show what it prints? Also, what's the BaseUri reported by MainWindow? Finally, could you configure Visual Studio to break when XamlParseException is thrown, and then use the Call Stack to find out what the AssemblyName assemblyRef argument is in the call to Assembly.Load?Carman
I have added the debug output as EDIT2 to my question. As for the exception, I am not sure how to do that: The call stack of the XamlParseException does not contain any calls to Assembly.Load. The inner exception is a FileNotFoundException whose call stack contains a call to Assembly.Load, so I configured VS to break when FileNotFoundException is thrown, as well. It now does break for the FileNotFoundException, and Copy exception detail to clipboard does show the call to Assembly.Load, however the VS Call Stack view does not; it shows a completely different call stack.Hangup
The first three lines in the call stack of the FileNotFoundException as copied to clipboard by VS are calls to RuntimeAssembly._nLoad, RuntimeAssembly.nLoad and RuntimeAssembly.InternalLoadAssemblyName. The first three lines in the Call Stack view are calls to BitmapSource.CompleteDelayedCreation, BitmapSource.PixelWidth.get and, after a native to managed and a managed to native transition, to RuntimeType.CreateInstanceSlow.Hangup
D'oh ... I've finally found out that I misnamed my DLL project ImgResTestLib while I used ImageResTestLib in my resource URIs. After correcting the latter, your solution works in my minimal sample. Does that mean that an absolute URI starting with pack://application:,,,/... should generally work? (I'm inquiring because I cannot find any such naming mistake in my actual project, and there it still doesn't work, though I seem to be unable to reproduce that problem in a minimal sample. Accepting this answer nonetheless becaue you did solve what I showed in the question.)Hangup
I have solved my problem and it was due to something completely different, which I would call a bug in VS and SharpDevelop. If you are interested, please see my EDIT3 for an explanation, where I have outlined what I think is responsible.Hangup
Wow, that's a new one on me. (And the answer to your previous question is, as I'm guessing you know by now, yes, that absolute URI should generally work.)Carman
This worked for me: Uri uri = new Uri("pack://application:,,,Folder/Image.png", UriKind.Absolute);. I saw this in MS docsSixtynine
D
0

using System.Windows.Media;

new ImageSourceConverter().ConvertFromString(ImageAbsolutePath) as ImageSource;

Dumfound answered 21/2 at 8:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.