如何防止Image.FromFile()方法锁定文件

sha*_*rgy 9 vb.net locking datagridview image save

我正在使用以下代码将JPG放入一个DataGridView图像单元格中.

If strFileName.ToLower.EndsWith(".jpg") Then
     Dim inImg As Image = Image.FromFile(strFileName)
     DataGridView4.Rows.Add()
     DataGridView4.Rows(DataGridView4.Rows().Count - 1).Cells(0).Value = inImg
End If
Run Code Online (Sandbox Code Playgroud)

问题是我需要从程序中保存这个文件,但我收到的消息是该文件正在被另一个程序使用.

所以我试图inImg.Dispose()在结束之前添加if,但之后程序不再显示图像了DataGridView.

如何在DataGridView不锁定图像的情况下添加图像?

谢谢

Chr*_*ris 20

当您使用该Image.FromFile(strFileName)方法创建时Image,该方法将锁定该文件,直到您释放该文件Image.确切原因解释如下.这就是为什么你不能用这种方法多次访问同一个图像文件.

你可以改为:

以下是可能实现SafeImageFromFile不锁定图像文件的自定义方法:

Public Shared Function SafeImageFromFile(path As String) As Image
    Using fs As New FileStream(path, FileMode.Open, FileAccess.Read)
        Dim img = Image.FromStream(fs)
        Return img
    End using
End Function
Run Code Online (Sandbox Code Playgroud)

要么

Public Shared Function SafeImageFromFile(path As String) As Image
    Dim bytes = File.ReadAllBytes(path)
    Using ms As New MemoryStream(bytes)
        Dim img = Image.FromStream(ms)
        Return img
    End Using
End Function
Run Code Online (Sandbox Code Playgroud)

用法

If strFileName.ToLower.EndsWith(".jpg") Then
    Dim inImg As Image = SafeImageFromFile(strFileName)
    Dim index as integer = DataGridView4.Rows.Add()
    DataGridView4.Rows(index).Cells(0).Value = inImg
End If
Run Code Online (Sandbox Code Playgroud)

重要的提示

在这里,我创建FileStreamMemoryStream使用一个Using语句来确保流被释放.它在我的系统上工作正常,它似乎也适用于你,虽然 MSDN说有关Image.FromStream(stream)方法:

您必须在图像的生命周期内保持流打开.

这句话的原因在这里解释:KB814675 Bitmap和Image构造函数依赖项

GDI +以及System.Drawing命名空间可以推迟原始图像位的解码,直到图像需要这些位. 另外,即使在图像被解码之后,GDI +也可以确定丢弃用于大位图的存储器并且稍后重新解码更有效.因此,GDI +必须能够在Bitmap或Image对象的生命周期内访问图像的源位.

为了保持对源位的访问,GDI +锁定任何源文件,并强制应用程序在Bitmap或Image对象的生命周期内维持任何源流的生命周期.

因此,知道上面的代码可能会GDIexceptions因为使用释放流而生成Using.从文件或图像创建过程中保存图像时可能会发生这种情况.从这个线程加载来自流的图像而不保持流打开和Hans Passant的评论他们修复了Vista版本的gdiplus.dll中的索引像素格式的几个问题.,它只会在XP上发生.

为避免这种情况,您需要保持流打开.方法是:

Public Shared Function SafeImageFromFile(path As String) As Image
    Dim fs As New FileStream(path, FileMode.Open, FileAccess.Read)
    Dim img = Image.FromStream(fs)
    Return img
End Function
Run Code Online (Sandbox Code Playgroud)

要么

Public Shared Function SafeImageFromFile(path As String) As Image
    Dim bytes = File.ReadAllBytes(path)
    Dim ms = New MemoryStream(bytes)
    Dim img = Image.FromStream(ms)
    Return img
End Function
Run Code Online (Sandbox Code Playgroud)

但是那些最后的方法有一些缺点,比如不释放流(内存问题),并且在丢失范围之前它们违反规则CA2000 Dispose对象.

知识库文章给出了一些解决方法:

创建非索引图像

这种方法要求新图像采用非索引像素格式(每像素超过8位),即使原始图像采用索引格式.此解决方法使用Graphics.DrawImage()方法将图像复制到新的Bitmap对象:

  1. 从流,内存或文件构造原始位图.
  2. 创建一个相同大小的新位图,像素格式超过每像素8位(BPP).
  3. 使用Graphics.FromImage()方法获取第二个Bitmap的Graphics对象.
  4. 使用Graphics.DrawImage()将第一个Bitmap绘制到第二个Bitmap上.
  5. 使用Graphics.Dispose()来处理Graphics.
  6. 使用Bitmap.Dispose()来处理第一个Bitmap.

创建索引图像

此解决方法以索引格式创建Bitmap对象:

  1. 从流,内存或文件构造原始位图.
  2. 创建一个与第一个Bitmap具有相同大小和像素格式的新Bitmap.
  3. 使用Bitmap.LockBits()方法以其原生像素格式锁定两个Bitmap对象的整个图像.
  4. 使用Marshal.Copy函数或其他内存复制功能将图像位从第一个位图复制到第二个位图.
  5. 使用Bitmap.UnlockBits()方法解锁两个Bitmap对象.使用Bitmap.Dispose()来处理第一个Bitmap.

这是一个非索引图像创建的实现,基于知识库文章和这个答案/sf/answers/558107441/ 你最好的选择是创建一个像素完美的图像副本 - 虽然YMMV(与某些类型的图像可能有多个帧,或者您也可能需要复制调色板数据.)但对于大多数图像,这有效:

Private Shared Function SafeImageFromFile(path As String) As Bitmap
    Dim img As Bitmap = Nothing
    Using fs As New FileStream(path, FileMode.Open, FileAccess.Read)
        Using b As New Bitmap(fs)
            img = New Bitmap(b.Width, b.Height, b.PixelFormat)
            Using g As Graphics = Graphics.FromImage(img)
                g.DrawImage(b, Point.Empty)
                g.Flush()
            End Using
        End Using
    End Using
    Return img
End Function
Run Code Online (Sandbox Code Playgroud)

有人表示重要的是以FileStream读模式打开(FileAccess.Read).

是的,但是如果你不使用Using语句就会更加敏感,所以你不释放流,或者在多线程上下文中:FileAccess.Write不合适,并且FileAccess.ReadWrite不是必需的,但是用FileAccess.Read模式打开流不会阻止一个IO.Exception如果另一个程序(或在多线程上下文你的)已经打开与另一模式比文件FileAccess.Read.


如果您希望能够显示图像并同时能够将数据保存到文件中,由于您没有使用这些方法锁定文件,您应该能够保存图像(删除/覆盖以前的图像)文件)使用该Image.Save方法.

  • 更有意义!非常感谢克里斯!答案是完美的! (2认同)
  • 大声笑..抱歉没有仔细阅读!谢谢! (2认同)