删除绑定到控件的图像

3

我是一名编写图像管理WPF应用程序的开发者。我有一个ListBox,其中包含以下ItemsTemplate:

        <Grid x:Name="grid" Width="150" Height="150" Background="{x:Null}">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="27.45"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150"/>
            </Grid.ColumnDefinitions>
            <Border Margin="5,5,5,5.745" Grid.RowSpan="2" Background="#FF828282" BorderBrush="{DynamicResource ListBorder}" CornerRadius="5,5,5,5" BorderThickness="1,1,2,2" x:Name="border">
                <Grid>
                    <Viewbox Margin="0,0,0,21.705">
                        <Image Width="Auto" Height="Auto" x:Name="picture" Source="{Binding Path=FullName}" />
                    </Viewbox>
                    <TextBlock Height="Auto" Text="{Binding Path=Name}" TextWrapping="Wrap" x:Name="PictureText" HorizontalAlignment="Left" Margin="70,0,0,0" VerticalAlignment="Bottom" />
                </Grid>
            </Border>
        </Grid>

请注意,“Image”控件绑定到“FullName”属性,这是表示JPG的绝对路径的字符串。
几个应用程序功能需要我修改JPG文件(移动、重命名或删除)。当我尝试这样做时(目前正在尝试移动文件),我会收到一个IOException:“因为它正被另一个进程使用,所以无法访问该文件。”锁定文件的进程是我的WPF应用程序。
我在网上搜索了一些内容,并发现有几篇文章指出,特别是图像很难释放它们的资源。我已经尝试了以下方法:
1.将ListBox.Source设置为null
2.在尝试移动之前等待10秒。
3.发布GC.Collect()。
4.将操作移动到不同的线程。
还有什么可以尝试的吗?我考虑找到ItemsTemplate中的Image对象的引用并尝试处理Image,但我无法找到引用的方法。
我读到的一个可能的解决方案是创建图像的副本而不是实际图像,但由于绑定是针对文件名而不是实际图像,我不知道是否能使其工作。
如有任何帮助或建议,将不胜感激。
4个回答

7

我的Intuipic应用程序还允许用户删除图片。为了实现这一点,我不得不编写 这个转换器。相关代码:

//create new stream and create bitmap frame
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = new FileStream(path, FileMode.Open, FileAccess.Read);
bitmapImage.DecodePixelWidth = (int) _decodePixelWidth;
bitmapImage.DecodePixelHeight = (int) _decodePixelHeight;
//load the image now so we can immediately dispose of the stream
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();

//clean up the stream to avoid file access exceptions when attempting to delete images
bitmapImage.StreamSource.Dispose();

我尝试了这个(没有使用DecodePixelWidth/Height),但文件仍然被锁定。肯定还有其他问题 - 我得开始寻找其他问题了。FileInfo对象会创建锁定吗? - Joel Cochran
我认为你需要逐一排除可能的原因。 - Kent Boogaart
我已经尽力找到了所有的东西。接下来我将重建这个应用程序,一步一步地检查罪犯。 - Joel Cochran

6

我将Kent的回答标记为答案,如果我使用了bendewey的回答,我也会将其标记为答案,因为我在最终解决方案中都使用了它们。

该文件肯定被锁定,因为只绑定了文件名,因此Image控件打开了实际文件以生成图像。

为了解决这个问题,我创建了一个类似bendewey建议的值转换器,然后我使用了Kent建议的(大部分)代码来返回一个新的BitmapImage:

    [ValueConversion(typeof(string), typeof(BitmapImage))]
public class PathToBitmapImage : IValueConverter
{
    public static BitmapImage ConvertToImage(string path)
    {
        if (!File.Exists(path))
            return null;

        BitmapImage bitmapImage = null;
        try
        {
            bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = new FileStream(path, FileMode.Open, FileAccess.Read);
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.EndInit();
            bitmapImage.StreamSource.Dispose();
        }
        catch (IOException ioex)
        {
        }
        return bitmapImage;
    }

    #region IValueConverter Members

    public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null || !(value is string))
            return null;

        var path = value as string;

        return ConvertToImage(path);
    }

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

    #endregion
}

如上方评论所述,这并没有解决问题。我一直在从事其他项目,最近回到这个项目中,重新振作起来寻找解决方案。
我创建了另一个项目,只是测试了这段代码,当然它能够正常工作。这告诉我,在原始程序中还有更多的问题。
长话短说,图片在三个地方生成,我认为这些地方已经得到了解决:
1)ImageList现在使用Converter进行绑定。 2)主要的Image绑定到ImageList SelectedItem属性。 3)DeleteImage弹出框,使用Converter进行绑定。
结果发现问题出在第二点。通过绑定SelectedItem,我错误地认为我正在绑定到新渲染的Image(基于Converter)。实际上,SelectedItem对象实际上是文件名。这意味着主Image再次通过直接访问文件来构建。
因此,解决方案是将主Image控件绑定到SelectedItem属性,并使用Converter。

2
请查看此处的帖子。 http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dee7cb68-aca3-402b-b159-2de933f933f1/ 示例:
基本上,您需要使用流预加载图像。 我会创建一个PreLoadImageConverter,类似于这样,我没有测试过。
<Grid>
  <Grid.Resources>
    <local:PreLoadImageConverter x:Key="imageLoadingConverter" />
  </Grid.Resources>
  <Image Width="Auto" Height="Auto" x:Name="picture" Source="{Binding Path=FullName, Converter={StaticResource imageLoadingConverter}}" />
</Grid>

PreLoadImageConverter.cs

public class PreLoadImageConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    if (value == null) return null;
    string imagePath = value.ToString();

    ImageSource imageSource;
    using (var stream = new MemoryStream())
    {
      Bitmap bitmap = new Bitmap(imagePath);
      bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);   
      PngBitmapDecoder bitmapDecoder = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
      imageSource = bitmapDecoder.Frames[0];
      imageSource.Freeze();
    }
    return imageSource;
  }

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    throw new Exception("The method or operation is not implemented.");
  }
}

我已经尝试过这个,但仍然无法移动文件。我还在Bitmap周围添加了using(),以为它可能还没有被处理,但结果相同。 - Joel Cochran
这似乎很奇怪,当应用程序未运行时,您能否移动或删除文件?文件是否仍然被某个先前的实例锁定? - bendewey

0

这已经很老了,但是框架已经改变了,因此在 .NET Core 中解决这个问题要容易得多。

据我所知,BitmapImage.UriSource 以前无法绑定。现在可以。只需在 xaml 中明确指定图像源。将 UriSource 绑定并将缓存模式设置为 OnLoad。完成。不需要转换器。

<Image Grid.Row="1">
    <Image.Source>
        <!-- specify the source explicitly and set CacheOption to OnLoad to avoid file locking-->
        <BitmapImage UriSource="{Binding ImagePath}" CacheOption="OnLoad" />
    </Image.Source>
</Image>

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