在运行时创建CroppedBitmap - 不会从资源加载

时间:2009-06-28 23:17:59

标签: wpf exception resources bitmapimage

我正在尝试编写将从资源加载图像的代码,然后裁剪它。当我在XAML中完成所有或部分操作时,此代码可以正常工作。我想从all-XAML切换到all-code,所以我可以使用不同的Uris重复使用多个这样的地方。

但是当我尝试在代码中做同样的事情时,我得到一个DirectoryNotFoundException,因为它突然开始尝试在磁盘上查找文件夹,而不是从资源加载图像。

  • 如果我在XAML中加载BitmapImage,然后在XAML中创建CroppedBitmap,一切正常。
  • 如果我在XAML中加载BitmapImage,然后编写代码来从中创建CroppedBitmap,一切正常。
  • 如果我在代码中加载BitmapImage,没有从中创建CroppedBitmap,一切正常。
  • 但是如果我在代码中加载BitmapImage并且在代码中创建一个CroppedBitmap,它会尝试从文件系统而不是资源加载,并且我得到一个DirectoryNotFoundException。

代码示例如下。我确定我做的事情很愚蠢,但我现在已经完成了三次(一次在我的真实应用程序中,一次在测试应用程序中,一次在写这个问题时),我得到了同样的东西结果三次。

对于以下所有代码示例,我在项目中创建了一个Images文件夹,并添加了一个名为“elf.png”的现有图像,其属性设置为默认值(Build Action =“Resource”;复制到输出目录=“不要复制”)。


案例1:XAML中的BitmapImage和CroppedBitmap。

<Window x:Class="WpfApplication8.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <BitmapImage x:Key="fullImage" UriSource="Images/elf.png"/>
        <CroppedBitmap x:Key="croppedImage" Source="{StaticResource fullImage}"
                       SourceRect="0 0 240 320"/>
    </Window.Resources>
    <Image Source="{StaticResource croppedImage}"/>
</Window>

这显示了位图的裁剪部分,如预期的那样。


案例2:XAML中的BitmapImage;代码隐藏中的CroppedBitmap。

XAML:

<Window x:Class="WpfApplication8.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <BitmapImage x:Key="fullImage" UriSource="Images/elf.png"/>
    </Window.Resources>
    <Image Name="image"/>
</Window>

代码隐藏中的构造函数:

public Window1()
{
    InitializeComponent();
    var fullImage = (BitmapImage) FindResource("fullImage");
    var croppedImage =
        new CroppedBitmap(fullImage, new Int32Rect(0, 0, 240, 320));
    image.Source = croppedImage;
}

这也显示了位图的裁剪部分,如预期的那样。


案例3:代码中的BitmapImage;没有CroppedBitmap。

public Window1()
{
    InitializeComponent();
    var uri = new Uri("Images/elf.png", UriKind.RelativeOrAbsolute);
    var fullImage = new BitmapImage(uri);
    image.Source = fullImage;
}

这显示了整个位图。这不是我想要的,但确实告诉我,我知道如何编写C#代码来创建正确的Uri并从资源加载BitmapImage。


案例4:代码中的BitmapImage和CroppedBitmap。

    public Window1()
    {
        InitializeComponent();
        var uri = new Uri("Images/elf.png", UriKind.RelativeOrAbsolute);
        var fullImage = new BitmapImage(uri);
        var croppedImage =
            new CroppedBitmap(fullImage, new Int32Rect(0, 0, 240, 320));
        image.Source = croppedImage;
    }

据我所知,这只是像以前一样拼凑出来的。它使用我知道将从资源加载BitmapImage的代码,我知道的代码将从加载的BitmapImage中裁剪一个部分。但不知何故,当两者放在一起时,它会忘记资源存在,并尝试从磁盘加载。我得到以下异常:

  • XamlParseException:“无法创建程序集'WpfApplication8,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null'中定义的'Window1'实例。调用目标已抛出异常。标记文件中出错' Window1.xaml'第1行位置13.“
    • 内部异常: TargetInvocationException:“调用目标已抛出异常。”
      • 内部异常: DirectoryNotFoundException:“无法找到路径'C:\ svn \ WpfApplication8 \ WpfApplication8 \ bin \ Debug \ Images \ elf.png'的一部分。”

内部 - 内部异常堆栈跟踪显示实例化CroppedBitmap的行抛出原始异常(DirectoryNotFoundException)。我不知道为什么那条线会试图从磁盘读取,或者为什么它不能正常工作时,我可以告诉等效的XAML正常工作。

因为我知道XAML正在使用无参数构造函数,所以我也尝试了以下版本,它应该更接近XAML的实际作用:

public Window1()
{
    InitializeComponent();
    var uri = new Uri("Images/elf.png", UriKind.RelativeOrAbsolute);
    var fullImage = new BitmapImage();
    fullImage.BeginInit();
    fullImage.UriSource = uri;
    fullImage.EndInit();
    var croppedImage = new CroppedBitmap();
    croppedImage.BeginInit();
    croppedImage.Source = fullImage;
    croppedImage.SourceRect = new Int32Rect(0, 0, 240, 320);
    croppedImage.EndInit();
    image.Source = croppedImage;
}

同样的例外,这次来自croppedImage.EndInit();行。

有关如何获取全代码版本以正确加载资源并裁剪图像的任何想法?在不同的XAML版本中发生了什么?

1 个答案:

答案 0 :(得分:4)

魔术竟然出现在BitmapImage的 BaseUri 属性中。 BaseUri显然是UriSource相对的“当前目录”。

当从XAML加载我的BitmapImage时,BaseUri被神奇地设置为“pack:// application:,,, / WpfApplication8; component / window1.xaml”。当我修改代码片段#4以在创建CroppedBitmap之前将fullImage.BaseUri显式设置为相同的值时,一切正常。

为什么它适用于XAML (以及来自Just-BitmapImage-without-CroppedBitmap)

那么这个神奇的BaseUri价值来自哪里?

BaseUri是IUriContext界面的一部分。 IUriContext.BaseUri在WPF程序集中的两个位置设置,在我的各种示例之间,我设法点击两个。难怪我很困惑。

  • BamlRecordReader.ElementInitialize。 BAML加载程序会在加载实现IUriContext的元素时自动设置BaseUri。这解释了我的示例#1和#2的工作原理:它们是从编译的BAML资源加载的。
  • Image.UpdateBaseUri (每当更改Source属性时调用)。这将检查Source是否实现IUriContext,如果是,则设置其BaseUri。这解释了为什么我的示例#3工作:将BitmapImage推入GUI迫使它获得正确的搜索路径。

当BaseUri设置为magic pack:// URI时,它只查找EXE资源中的图像。如果没有(当所有内容都是在代码中创建而没有被推入GUI时),它只能在磁盘上查找。

修复

如上所述,我可以硬编码BaseUri。但BaseUriHelper类提供了更好的解决方法:

fullImage.BaseUri = BaseUriHelper.GetBaseUri(this);

这会将fullImage设置为与窗口(this)具有相同的BaseUri。如果在创建CroppedBitmap之前完成此操作,则一切正常。