9

我正在使用以下代码将 JPG 放入DataGridView's Image 单元格中。

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

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

所以我尝试inImg.Dispose()在结束之前添加 if,但是程序不再在DataGridView.

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

谢谢

4

5 回答 5

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

或者

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

用法

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

重要的提示

在这里,我创建了FileStream一个MemoryStreamusingUsing语句来确保流被释放。它在我的系统上运行良好,而且似乎也适用于你,尽管MSDN 提到Image.FromStream(stream)方法:

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

这句话的原因在这里解释一下:KB814675 Bitmap and Image constructor dependencies

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

为了保留对源位的访问,GDI+ 锁定任何源文件,并强制应用程序在位图或图像对象的生命周期内维护任何源流的生命周期。

所以知道上面的代码可能会GDIexceptions因为使用Using. 当您从文件中保存图像或在图像创建过程中可能会发生这种情况。从这个线程加载图像而不保持流打开和汉斯帕桑特的评论他们修复了gdiplus.dll的Vista版本中索引像素格式的几个问题。,它只会发生在 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

或者

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

但是最后这些方法有一些缺点,例如不释放流(内存问题),并且它们违反了规则CA2000 Dispose objects before lost scope

知识库文章提供了一些解决方法:

创建非索引图像

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

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

创建索引图像

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

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

这是基于知识库文章和此答案https://stackoverflow.com/a/7972963/2387010的非索引图像创建的实现 您最好的选择是创建图像的像素完美副本-尽管 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

有人指出,重要的是以FileStream读取模式打开(FileAccess.Read)。

的,但是如果您不使用Using语句并且不释放流,或者在多线程上下文中,它更有意义:FileAccess.Write不合适,并且FileAccess.ReadWrite不是必需的,但是使用FileAccess.Read模式打开流不会阻止拥有如果另一个程序(或您在多线程上下文中的IO.Exception程序)以另一种模式打开文件,而不是FileAccess.Read.


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

于 2013-08-15T10:49:42.613 回答
0

@ Chris:使用您的最终代码打开大约 100 个大 (3400x2200) 图像,我收到了一个无效的参数崩溃[img = new bitmap(...],我在打开零大小的图像之前已经看到了这一点,但这里不是这种情况。我添加fs.dispose并成功打开了与第一次测试相同大小的数千张图像,没有问题。我对你对此的评论很感兴趣。

Private Function SafeImageFromFile(FilePath As String) As Image
    Dim img As Bitmap = Nothing
    Using fs As New FileStream(FilePath, 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
        fs.Dispose()
    End Using
    Return img
End Function
于 2015-05-21T12:57:21.820 回答
0

这可以正常工作,通过它(两次)运行 4189 个 3400x2200 图像没有问题,这会将文件流移到函数之外并重新使用它。我正在关闭文件以释放写锁。我在循环中将图片框指向此图像以进行测试。

Private fsIMG As FileStream
Private Function SafeImageFromFile(FilePath As String) As Image
    'Ref:  http://stackoverflow.com/questions/18250848/how-to-prevent-the-image-fromfile-method-to-lock-the-file
    Dim img As Bitmap = Nothing
    fsIMG = New FileStream(FilePath, FileMode.Open, FileAccess.Read)
    Using b As New Bitmap(fsIMG)
        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
    fsIMG.Close()
    Return img
End Function
于 2015-05-21T14:32:55.570 回答
0

在网上搜索了很长时间后,我发现我可以使用此代码而不会出现任何错误。

  Private fsIMG As FileStream
Private Function SafeImageFromFile(FilePath As String) As Image
    'Ref:  http://stackoverflow.com/questions/18250848/how-to-prevent-the-image-fromfile-method-to-lock-the-file
    Dim img As Bitmap = Nothing
    fsIMG = New FileStream(FilePath, FileMode.Open, FileAccess.Read)
    Using b As New Bitmap(fsIMG)
        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
    fsIMG.Close()
    Return img
End Function
于 2016-03-07T14:23:11.247 回答
0

我遇到了同样的情况并使用了这段代码:

' Create memory stream from file
Dim ms As New MemoryStream()
' Open image file
Using fs As New FileStream(.FileName, FileMode.Open, FileAccess.Read)
    ' Save to memory stream
    fs.CopyTo(ms)
End Using

' Create image from the file's copy in memory
Dim img = Image.FromStream(ms)

我没有处理内存流,因为它允许稍后使用与原始文件完全相同的编码保存图像,使用以下代码:

img.Save(someOtherStream, img.RawFormat)
于 2019-02-26T13:26:16.983 回答