【问题标题】:Delete an image bound to a control删除绑定到控件的图像
【发布时间】:2020-09-10 01:57:10
【问题描述】:

我正在编写一个图像管理器 WPF 应用程序。我有一个带有以下 ItemsTemplate 的 ListBox:

        <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,但我不知道如何获取该引用。

我读到的一个可能的解决方案是创建图像的副本而不是实际图像,但由于绑定是文件名而不是实际图像,我不知道我是否可以做到这一点。

任何帮助或建议将不胜感激。

【问题讨论】:

    标签: c# wpf file-io


    【解决方案1】:

    我的Intuipic 应用程序也允许用户删除图像。我不得不写this converter 来实现它。相关代码:

    //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 对象会创建锁吗?
    • 我认为你必须一一排除潜在原因。
    • 我用尽了所有我能找到的东西。接下来我将重建应用程序,逐步检查罪魁祸首。
    【解决方案2】:

    我将 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
    }
    

    然而,正如上面 cmets 所建议的,这并没有解决问题。我一直在做其他项目,最近又回到了这个项目,重新振作起来寻找解决方案。

    我创建了另一个项目,只测试了这段代码,当然它有效。这告诉我原来的程序有更多的错误。

    长话短说,图像是在三个地方生成的,我认为这已经解决了:

    1) ImageList,现在使用转换器绑定。 2) 绑定到 ImageList SelectedItem 属性的主图像。 3) DeleteImage 弹窗,使用 Converter 绑定。

    原来问题出在#2。通过绑定到 SelectedItem,我错误地认为我正在绑定到新渲染的图像(基于转换器)。实际上,SelectedItem 对象实际上就是文件名。这意味着再次通过直接访问文件来构建主图像。

    因此解决方案是将主 Image 控件绑定到 SelectedItem 属性并使用 Converter。

    【讨论】:

      【解决方案3】:

      在这里查看这篇文章。

      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(),认为它可能尚未被处理,但结果相同。
      • 这似乎很奇怪,您可以在应用未运行时移动或删除文件吗?该文件是否可能仍被以前的某个实例锁定?
      【解决方案4】:

      这已经很老了,但是框架已经改变,解决这个问题要容易得多,至少在 .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>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-20
        • 2015-01-10
        • 1970-01-01
        • 2012-08-07
        • 1970-01-01
        • 2013-01-11
        相关资源
        最近更新 更多