如何从代码隐藏设置图像资源 URI



我正在尝试将PNG图形嵌入到DLL中,并将其作为BitmapImage加载到Image控件中。但是,WPF 不断引发异常,指出找不到资源。

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

  • 创建一个名为ImageResTest的 WPF 项目,其中包含一个空的主窗口(可以将默认命名空间设置为ImageResTest)。主窗口的代码隐藏文件应如下所示:

    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;
    }
    }
    }
    
  • 创建一个名为ImageResTestLib的类库(如上所述,您可以将默认命名空间设置为ImageResTest,因此此处讨论的所有内容都位于同一根命名空间中)。

  • 添加从ImageResTestLibPresentationCorePresentationFrameworkSystem.XamlWindowsBase的引用。
  • 将引用从 ImageResTest 添加到ImageResTestLib
  • ImageResTestLib中,添加文件夹层次结构MyData/SomeStuff/Resources
  • SomeStuff文件夹中,添加以下文件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;
    }
    }
    }
    }
    
  • Resources文件夹中,添加一个名为Img.png的 PNG 文件,并将其生成操作设置为"资源"(例如,此处建议)。

到目前为止,一切顺利 - 启动此应用程序应该创建一个窗口,该窗口实例化MyClass并检索该MyClass实例创建的Image。该图像应填充一个BitmapImage,其数据是从作为资源包含的图形加载的。

不幸的是,资源 URI 似乎有问题。到目前为止,MSDN 上的文档没有帮助。

我已经尝试了以下资源 URI 变体:

  • 上面代码示例中描述的形式 -/AssemblyName;component/Path/Filename- 在这里和这里被建议,但抛出了一个DirectoryNotFoundException,说找不到路径C:ImageResTestLib;componentMyDataSomeStuffResourcesImg.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:UsersmyusernameDocumentsTestDOTNETWPFTestImageResTestbinDebugResourcesImg.png
  • MyData/SomeStuff/Resources/Img.png(相对于项目),也如此处所暗示的那样,其行为与前一个类似。

由于这些都不起作用,我根据ResourceDictionary尝试了以下解决方法:

  • SomeStuff文件夹中添加名为MyClassResources.xaml的 WPF 资源字典。
  • 在该资源字典中,添加具有键imgBitmapImage资源。
  • 更改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()
    {
    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 图形。

我做错了什么,如何加载相应的资源(如果可能,没有额外的资源字典)?


编辑:更多信息:

  • 我使用的是德国视窗 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:UsersusernameDocumentsTestDOTNETWPFTestImgResTestLibMyDataSomeStuffMyClass.cs:Zeile 36.
bei ImageResTest.Window1..ctor() in c:UsersusernameDocumentsTestDOTNETWPFTestImageResTestWindow1.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:UsersusernameDocumentsTestDOTNETWPFTestImageResTestobjDebugApp.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和SharpDevelopment都没有给出任何指示,但标记为Resource的资源实际上有一个逻辑名称(在我的情况下,他们从我暂时将构建操作设置为EmbeddedResource并更改逻辑名称时保留了它)。逻辑名称仍然出现在 MSBuild 文件的<LogicalName>元素中,从我在 ILSpy 中看到的内容来看,这就是在编译程序集中实际用作资源名称的名称。

具有逻辑名称的此类资源的正确(工作)资源 URI 似乎是

/MyAssembly;component/LogicalResourceName

(从而替换资源的目录路径,就像通常的嵌入式资源一样)

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

部分问题在于 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 尝试将该 URI 解析为实际资源,此时BitmapImage将分配为该ImageSource属性。我的第一个示例之所以有效,是因为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,并且没有收到错误。为了清楚起见,这是我对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工作的不同之处,那么你已经拥有的基本方法应该没问题。

最新更新