none
Generate a thumbnail preview image each time I select a filename in a listbox RRS feed

  • Question

  • Hello, I am creating a list of part numbers in a listbox from a Bill Of Materials from an Excel Workbook. I open excel, get the values I need, then display those part numbers in a listbox. All part numbers have a AutoCAD .dwg file on the network and I'm trying to get it to where each time the user selects an item in the listbox it shows a thumbnail image in a picture box on the form. Then when they find the part they want to modify they can click the "Open Part" button and it will open the AutoCAD drawing. I thought that I would have to use multi-threading but I haven't used multi-threading before and not quite sure of how to go about this. Any help would be greatly appreciated.
    Thursday, April 24, 2014 2:06 PM

Answers

  • I found some code on theswamp.org that does what I need.

    http://www.theswamp.org/index.php?topic=30985.15

    Kudos to Huiz on there for posting to that thread. It works great, now I'm going to research how to resize the picture so it fills the picture box I'm using. Here is the code he posted:

    Imports System.Drawing
    Imports System.IO
    
    ' 2011 Copyright (C) jgr=&jgr, via http://www.theswamp.org
    ' 2012 (me): Added code to read PNG Thumbnails from DWG (2013 file format)
    Friend NotInheritable Class ThumbnailReader
      Private Sub New()
      End Sub
      Friend Shared Function GetBitmap(fileName As String) As Bitmap
        Using fs As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
          Using br As New BinaryReader(fs)
            fs.Seek(&HD, SeekOrigin.Begin)
            fs.Seek(&H14 + br.ReadInt32(), SeekOrigin.Begin)
            Dim bytCnt As Byte = br.ReadByte()
            If bytCnt <= 1 Then
              Return Nothing
            End If
            Dim imageHeaderStart As Integer
            Dim imageHeaderSize As Integer
            Dim imageCode As Byte
            For i As Short = 1 To bytCnt
              imageCode = br.ReadByte()
              imageHeaderStart = br.ReadInt32()
              imageHeaderSize = br.ReadInt32()
              If imageCode = 2 Then ' BMP Preview (2012 file format)
                ' BITMAPINFOHEADER (40 bytes)
                br.ReadBytes(&HE)
                'biSize, biWidth, biHeight, biPlanes
                Dim biBitCount As UShort = br.ReadUInt16()
                br.ReadBytes(4)
                'biCompression
                Dim biSizeImage As UInteger = br.ReadUInt32()
                'br.ReadBytes(0x10); //biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportant
                '-----------------------------------------------------
                fs.Seek(imageHeaderStart, SeekOrigin.Begin)
                Dim bitmapBuffer As Byte() = br.ReadBytes(imageHeaderSize)
                Dim colorTableSize As UInteger = CUInt(Math.Truncate(If((biBitCount < 9), 4 * Math.Pow(2, biBitCount), 0)))
                Using ms As New MemoryStream()
                  Using bw As New BinaryWriter(ms)
                    bw.Write(CUShort(&H4D42))
                    bw.Write(54UI + colorTableSize + biSizeImage)
                    bw.Write(New UShort())
                    bw.Write(New UShort())
                    bw.Write(54UI + colorTableSize)
                    bw.Write(bitmapBuffer)
                    Return New Bitmap(ms)
                  End Using
                End Using
              ElseIf imageCode = 6 Then ' PNG Preview (2013 file format)
                fs.Seek(imageHeaderStart, SeekOrigin.Begin)
                Using ms As New MemoryStream
                  fs.CopyTo(ms, imageHeaderStart)
                  Dim img = Image.FromStream(ms)
                  Return img
                End Using
              ElseIf imageCode = 3 Then
                Return Nothing
              End If
            Next
          End Using
        End Using
        Return Nothing
      End Function
    End Class

    Then I just do this at my button click:

    PreviewPictureBox.Image =

    ThumbnailReader.GetBitmap(AcadFilename)

    Thanks for the help.

    • Marked as answer by cncah Thursday, April 24, 2014 7:17 PM
    Thursday, April 24, 2014 7:17 PM

All replies

  • How are you going to make the thumbnail from an Acad drawing? Do you already have a thumbnail bitmap of the drawing?

    Thursday, April 24, 2014 2:12 PM
  • How are you going to make the thumbnail from an Acad drawing? Do you already have a thumbnail bitmap of the drawing?

    Tom,

    AutoCAD generates its own vector-based preview:

    That won't do any good here, but I feel sure that's sort of what he has in mind.


    Please call me Frank :)

    Thursday, April 24, 2014 3:58 PM
  • How are you going to make the thumbnail from an Acad drawing? Do you already have a thumbnail bitmap of the drawing?

    Tom,

    AutoCAD generates its own vector-based preview:

    That won't do any good here, but I feel sure that's sort of what he has in mind.


    Please call me Frank :)

    Yes. I mean I assume the OP is making a stand alone vb.net application. So there needs to be a method of generating the thumbnail image bitmap from the acad .dwg. I think that would require a 3rd party control, as far as I know .net does not support .dwg but I would love it if it did. Or maybe he has the bitmap already made, perhaps in the excel sheet.

    Thursday, April 24, 2014 5:30 PM
  • Yes. I mean I assume the OP is making a stand alone vb.net application. So there needs to be a method of generating the thumbnail image bitmap from the acad .dwg. I think that would require a 3rd party control, as far as I know .net does not support .dwg but I would love it if it did. Or maybe he has the bitmap already made, perhaps in the excel sheet.

    AutoDesk has an API, but that's only applicable to versions starting in 2010 (I think - it might be 2011). I've seen third-party stuff for earlier versions like mine but they are very much NOT cheap!

    One thought would be to have someone plot out to PDF and show the PDF instead of an image, but that's a lot of work someone would have to do to get those PDF files. Then if you *really* wanted it as an image, the PDF can be rasterized ... again, a lot of manual effort.


    Please call me Frank :)

    Thursday, April 24, 2014 5:35 PM
  • Yes. I mean I assume the OP is making a stand alone vb.net application. So there needs to be a method of generating the thumbnail image bitmap from the acad .dwg. I think that would require a 3rd party control, as far as I know .net does not support .dwg but I would love it if it did. Or maybe he has the bitmap already made, perhaps in the excel sheet.

    AutoDesk has an API, but that's only applicable to versions starting in 2010 (I think - it might be 2011). I've seen third-party stuff for earlier versions like mine but they are very much NOT cheap!

    One thought would be to have someone plot out to PDF and show the PDF instead of an image, but that's a lot of work someone would have to do to get those PDF files. Then if you *really* wanted it as an image, the PDF can be rasterized ... again, a lot of manual effort.


    Please call me Frank :)


    Yup. So the question is either about how do you make the image from .dwg or how do you show it in a vb app or both. Maybe the OP has a plan? Not sure where the multithreading comes in?
    Thursday, April 24, 2014 5:43 PM
  • I found this link to a different thread but not sure how to implement the code to mine. I've given up on the idea of multi-threading the bitmap preview portion of the program and figured I would just have a button on the form labeled "Preview Button". I tried using the code that was marked as the answer but not sure how to pass the autocad.dwg file name or the name of my picture box to this code. Any help?

    http://social.msdn.microsoft.com/Forums/en-US/f4b057f0-b516-4f50-b2e3-8f435c0ee5a5/dwg-thumbnail-in-picturebox-vbnet-2010?forum=Vsexpressvb

    Thursday, April 24, 2014 6:44 PM
  • CNC,

    Looking at the second post in that thread, it appears that Mike Feng put a class together. It's not formatted very well (I mean this forum, not his code!), but try to pull that out and put it in a test project and see if you can manage to make it work for you.

    It very well might be what you want!


    Please call me Frank :)

    Thursday, April 24, 2014 7:00 PM
  • I found some code on theswamp.org that does what I need.

    http://www.theswamp.org/index.php?topic=30985.15

    Kudos to Huiz on there for posting to that thread. It works great, now I'm going to research how to resize the picture so it fills the picture box I'm using. Here is the code he posted:

    Imports System.Drawing
    Imports System.IO
    
    ' 2011 Copyright (C) jgr=&jgr, via http://www.theswamp.org
    ' 2012 (me): Added code to read PNG Thumbnails from DWG (2013 file format)
    Friend NotInheritable Class ThumbnailReader
      Private Sub New()
      End Sub
      Friend Shared Function GetBitmap(fileName As String) As Bitmap
        Using fs As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
          Using br As New BinaryReader(fs)
            fs.Seek(&HD, SeekOrigin.Begin)
            fs.Seek(&H14 + br.ReadInt32(), SeekOrigin.Begin)
            Dim bytCnt As Byte = br.ReadByte()
            If bytCnt <= 1 Then
              Return Nothing
            End If
            Dim imageHeaderStart As Integer
            Dim imageHeaderSize As Integer
            Dim imageCode As Byte
            For i As Short = 1 To bytCnt
              imageCode = br.ReadByte()
              imageHeaderStart = br.ReadInt32()
              imageHeaderSize = br.ReadInt32()
              If imageCode = 2 Then ' BMP Preview (2012 file format)
                ' BITMAPINFOHEADER (40 bytes)
                br.ReadBytes(&HE)
                'biSize, biWidth, biHeight, biPlanes
                Dim biBitCount As UShort = br.ReadUInt16()
                br.ReadBytes(4)
                'biCompression
                Dim biSizeImage As UInteger = br.ReadUInt32()
                'br.ReadBytes(0x10); //biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportant
                '-----------------------------------------------------
                fs.Seek(imageHeaderStart, SeekOrigin.Begin)
                Dim bitmapBuffer As Byte() = br.ReadBytes(imageHeaderSize)
                Dim colorTableSize As UInteger = CUInt(Math.Truncate(If((biBitCount < 9), 4 * Math.Pow(2, biBitCount), 0)))
                Using ms As New MemoryStream()
                  Using bw As New BinaryWriter(ms)
                    bw.Write(CUShort(&H4D42))
                    bw.Write(54UI + colorTableSize + biSizeImage)
                    bw.Write(New UShort())
                    bw.Write(New UShort())
                    bw.Write(54UI + colorTableSize)
                    bw.Write(bitmapBuffer)
                    Return New Bitmap(ms)
                  End Using
                End Using
              ElseIf imageCode = 6 Then ' PNG Preview (2013 file format)
                fs.Seek(imageHeaderStart, SeekOrigin.Begin)
                Using ms As New MemoryStream
                  fs.CopyTo(ms, imageHeaderStart)
                  Dim img = Image.FromStream(ms)
                  Return img
                End Using
              ElseIf imageCode = 3 Then
                Return Nothing
              End If
            Next
          End Using
        End Using
        Return Nothing
      End Function
    End Class

    Then I just do this at my button click:

    PreviewPictureBox.Image =

    ThumbnailReader.GetBitmap(AcadFilename)

    Thanks for the help.

    • Marked as answer by cncah Thursday, April 24, 2014 7:17 PM
    Thursday, April 24, 2014 7:17 PM
  • CNC,

    Good deal then!

    At some point in the future I'l likewise borrow that (with credit to the author) and put something together to "batch" folders of .dwg files into a class I'll build for it.

    Good find! :)


    Please call me Frank :)

    Thursday, April 24, 2014 7:21 PM
  • The only thing I wonder is there a way to have the preview automatically generated every time I click on a file in the listbox?
    Thursday, April 24, 2014 8:00 PM
  • The only thing I wonder is there a way to have the preview automatically generated every time I click on a file in the listbox?

    I haven't (and right now don't have time to) study what you found, but you'll need to call the shared function in that class each time you want to "get the image" from some particular filepath.

    It returns a bitmap so you can then set the PictureBox's .Image to that return value IF it returned a bitmap (test that it's not null first)


    Please call me Frank :)

    Thursday, April 24, 2014 8:05 PM
  • CNC,

    I ran a little test project with Mike Feng's code.

    The first thought I had was "DAMN that's fast!" - and I tried it on a number of .dwg files, including some pretty large ones (3D solids, and you know how big those files get), and it didn't matter. Whatever I threw at it, it took ... I don't know, but REAL fast.

    I then noticed that he's doing some work (which I don't understand, honestly) and it occurred to me that he's actually getting the embedded preview of the AutoCAD file itself - so it's not "creating a new image from scratch", it's getting that specific ACAD information.

    Now it makes sense why it's so fast - the "image" is already in the .dwg file itself. Unfortunately this also means that there is no change on the size of it (well it could be downscaled), but it works pretty well.

    I'll try to experiment with it more this weekend -- if you're interested in me doing that, that is?

    If so, tell me more about your program overall. What data do you want to have in it? I presume you want a way to save it somehow, or do you want it to always be 'on the fly'?


    Please call me Frank :)

    Thursday, April 24, 2014 9:09 PM
  • Frank,

    I haven't tried Mike Feng's code since I got the other to work as I needed it to. I was able to write a sub that runs the bitmap generator whenever the selected index is changed in the listbox so I don't need to use a button to generate the preview anymore.

    Private Sub ExcelBOMListBox_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ExcelBOMListBox.SelectedIndexChanged
    
            NoPreviewLabel.Visible = False
            PreviewPictureBox.Image = Nothing
    
            Dim CurrentPart As String = ExcelBOMListBox.SelectedItem.ToString()
    
            If CurrentPart <> sPart Then
                PartNumberLabel.Text = CurrentPart
                Dim AcadFilename As String = ""
                For Each File In acadFiles
                    If File.Contains(CurrentPart) Then
                        AcadFilename = File
                    End If
                Next
    
                Try
    
                    Dim BitPart As Bitmap = ThumbnailReader.GetBitmap(AcadFilename)
    
                    If BitPart IsNot Nothing Then
                        PreviewPictureBox.Image = BitPart
                    Else
                        NoPreviewLabel.Text = CurrentPart + vbNewLine + " Has Not Been" + vbNewLine + "Programmed"
                        NoPreviewLabel.Visible = True
                    End If
    
    
                Catch PictureError As Exception
                    Debug.WriteLine(PictureError)
                End Try
               
            End If
        End Sub
     

    So my form ends up looking like this when I have a "programmed" part selected.

    And looks like this if it has not been programmed.

    So this is working fine as of now. About the only thing I wish I knew how to do was have a textbox(it would have to be rather large) to display the part number, then the layer list for that part, without having to open the part. I was thinking that if I absolutely had to open the part to be able to get the layer table, then maybe that is something that I could multi-thread and have another instance of AutoCAD running in the background. Opening each part in the listbox, getting the layer table, then displaying those layer names to the textbox while I'm going through programming the parts. The only kicker is that as I'm processing the parts the layer table in those parts will change and I would have to have a button to regenerate the layer list in the textbox. I guess I could wait until I was done then run the layer name process. Then I wouldn't have to worry about the multi-threading. I know how to use AutoLISP to get the layer table, and I've found sample code on how to get the layer table if you are writing a vb program to run inside of AutoCAD. But do you know by chance on how to get it if the program is a what the AUGI and Autodesk people call an "Out Of Process" program? Anytime I try to submit a question to one of their forums they always say to just write it as a dll to run inside of AutoCAD. I guess because there is more documentation on that then if you were to write your program using COM references. Anyways, thanks for the all of the help, and I hope someone can use any of this someday in their own project.

    Friday, April 25, 2014 11:10 AM
  • CNC,

    No, I don't know how to get the layer names, sorry that I can't help.


    Please call me Frank :)

    Friday, April 25, 2014 11:40 AM