Link to home
Start Free TrialLog in
Avatar of taz8020
taz8020Flag for United Kingdom of Great Britain and Northern Ireland

asked on

Load Images in form Like the opendialog does vb.net

Hi I want to create a form and view the image files in a form similar to how it does in openfiledialog. At the moment I have used a listview added the images to an image list and so on.

But 1000 images takes 4mins+ to load images in. If I use the openfiledialog or the windows file browser the 1000 images load in less then 30 secs.

So my question is how does microsoft load these so fast? do they use a list view?
I did notice in my code the images load one after another togeaher with file name, where as in the file explorer the text seems to load then the image after. As you scroll the page all my images are there where as in file explorer its like is loads only the visable images.

As my program will load over 5000 images at a time speed is important. I just want them to add the images they want to use as a visual thing, so as long as I can get the image path to do something with the images it would be fine.
Avatar of SStory
SStory
Flag of United States of America image

Well, the smartest thing to do would be to use some sort of scroll bar and draw the images on the screen yourself and as the user scrolls, and the paint event is fired again, dependent upon the value in the scroll bar, draw different images, but no more than what can be seen.

I'm sure they aren't loading 5,000 images and wasting memory since you can only see so many at at time anyway.

Later when you have that working, you might want to cache some of the images, by preloading the ones that come after the current position and leaving the ones immediately before the current view in memory as well. I'm not sure on that one. You'd have to see how well it worked after doing it.
You can learn plenty about drawing images, etc. here:
http://bobpowell.net/faqmain.htm
SOLUTION
Avatar of SStory
SStory
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of taz8020

ASKER

I really like the idea of this. As the listview works well and is all working. Just a thought but is there a way to detect which items are visable and if they are then load the image?
"So my question is how does microsoft load these so fast?"

Windows creates thumbnails and stores them for later use so that the entire file doesn't have to be processed later:
http://en.wikipedia.org/wiki/Windows_thumbnail_cache
Try this project out.  My form had a Button, ListView and an ImageList.

It has only marginal performance in my opinion, but does load much faster than if you loaded up all the thumbnails at the same time.

I've combined code and concepts from these two articles:
http://msdn.microsoft.com/en-us/library/system.windows.forms.listview.virtualmode.aspx?queryresult=true
http://msdn.microsoft.com/en-us/library/aa289172(VS.71).aspx#sharept_topic2

Here's the code:
Imports System.IO
Imports System.Runtime.InteropServices
Public Class Form1

    Private FileNames As New List(Of FileInfo)
    Private Cache As New Dictionary(Of Integer, ListViewItem)

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        ListView1.View = View.LargeIcon
        ListView1.LargeImageList = ImageList1
        ListView1.VirtualMode = True
        ListView1.VirtualListSize = 0

        ImageList1.ImageSize = New Size(100, 100)
        ImageList1.ColorDepth = ColorDepth.Depth32Bit
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Using folder As New FolderBrowserDialog
            If folder.ShowDialog = Windows.Forms.DialogResult.OK Then
                Cache.Clear()
                ListView1.Items.Clear()
                ImageList1.Images.Clear()

                Dim di As New DirectoryInfo(folder.SelectedPath)
                FileNames.Clear()
                For Each fi As FileInfo In di.GetFiles
                    Select Case fi.Extension.ToUpper
                        Case ".BMP", ".JPG", ".PNG", ".GIF"
                            FileNames.Add(fi)
                    End Select
                Next

                ListView1.VirtualListSize = FileNames.Count
                ListView1.Refresh()
            End If
        End Using
    End Sub

    Private Sub listView1_RetrieveVirtualItem(ByVal sender As Object, ByVal e As RetrieveVirtualItemEventArgs) Handles ListView1.RetrieveVirtualItem
        If Cache.ContainsKey(e.ItemIndex) Then
            e.Item = Cache(e.ItemIndex)
        ElseIf e.ItemIndex < FileNames.Count Then
            Dim FullFileName As String = FileNames(e.ItemIndex).FullName
            ImageList1.Images.Add(Shell.GetThumbnailImage(FullFileName, 100, 32))
            Dim LVI As New ListViewItem(FileNames(e.ItemIndex).Name, ImageList1.Images.Count - 1)
            Cache.Add(e.ItemIndex, LVI)
            e.Item = LVI
        End If
    End Sub

End Class

Public Class Shell

    Public Shared Function GetThumbnailImage(ByVal fileName As String, _
  ByVal longestEdge As Integer, ByVal colorDepth As Integer) As Image

        Dim desktopFolder As IShellFolder = Nothing
        Dim someFolder As IShellFolder = Nothing
        Dim extract As IExtractImage = Nothing
        Dim pidl As IntPtr
        Dim filePidl As IntPtr

        Const MAX_PATH As Integer = 260

        'Manually define the IIDs for IShellFolder and IExtractImage
        Dim IID_IShellFolder = New Guid("000214E6-0000-0000-C000-000000000046")
        Dim IID_IExtractImage = New Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")

        'Divide the file name into a path and file name
        Dim folderName = Path.GetDirectoryName(fileName)
        Dim shortFileName = Path.GetFileName(fileName)

        'Get the desktop IShellFolder
        ShellInterop.SHGetDesktopFolder(desktopFolder)

        'Get the parent folder IShellFolder
        desktopFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, folderName, 0, pidl, 0)
        desktopFolder.BindToObject(pidl, IntPtr.Zero, IID_IShellFolder, someFolder)

        'Get the file's IExtractImage
        someFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, shortFileName, 0, filePidl, 0)
        someFolder.GetUIObjectOf(IntPtr.Zero, 1, filePidl, IID_IExtractImage, 0, extract)

        'Set the size
        Dim size As SIZE_API_STRUCTURE
        size.cx = 500
        size.cy = 500

        Dim flags = IEIFLAG.ORIGSIZE ' Or IEIFLAG.QUALITY
        Dim bmp As IntPtr
        Dim thePath = Marshal.AllocHGlobal(MAX_PATH)

        'Interop will throw an exception if one of these calls fail.
        Try
            extract.GetLocation(thePath, MAX_PATH, 0, size, colorDepth, flags)
            extract.Extract(bmp)
        Catch ex As Exception
        End Try


        'Free the global memory we allocated for the path string
        Marshal.FreeHGlobal(thePath)

        'Free the pidls. The Runtime Callable Wrappers 
        'should automatically release the COM objects
        Marshal.FreeCoTaskMem(pidl)
        Marshal.FreeCoTaskMem(filePidl)

        If Not bmp.Equals(IntPtr.Zero) Then
            GetThumbnailImage = Image.FromHbitmap(bmp)
        Else
            GetThumbnailImage = Nothing
        End If
    End Function

End Class

Public Enum IEIFLAG As Integer
    ASYNC = &H1
    CACHE = &H2
    ASPECT = &H4
    OFFLINE = &H8
    GLEAM = &H10
    SCREEN = &H20
    ORIGSIZE = &H40
    NOSTAMP = &H80
    NOBORDER = &H100
    QUALITY = &H200
End Enum

<StructLayout(LayoutKind.Sequential)> _
Public Structure STRRET_CSTR
    Public uType As Integer
    <FieldOffset(4), MarshalAs(UnmanagedType.LPWStr)> _
    Public pOleStr As String
    <FieldOffset(4)> _
    Public uOffset As Integer
    <FieldOffset(4), MarshalAs(UnmanagedType.ByValArray, SizeConst:=520)> _
    Public strName As Byte()
End Structure

<StructLayout(LayoutKind.Sequential)> _
Public Structure SIZE_API_STRUCTURE
    Public cx As Integer
    Public cy As Integer
End Structure

<ComImportAttribute(), _
 GuidAttribute("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1"), _
 InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
Public Interface IExtractImage

    Sub GetLocation( _
        ByVal pszPathBuffer As IntPtr, _
        ByVal cch As Integer, _
        ByRef pdwPriority As Integer, _
        ByRef prgSize As SIZE_API_STRUCTURE, _
        ByVal dwRecClrDepth As Integer, _
        ByRef pdwFlags As Integer)

    Sub Extract(ByRef phBmpThumbnail As IntPtr)

End Interface

<ComImportAttribute(), _
GuidAttribute("000214E6-0000-0000-C000-000000000046"), _
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
Public Interface IShellFolder

    Sub ParseDisplayName( _
      ByVal hWnd As IntPtr, _
      ByVal pbc As IntPtr, _
      ByVal pszDisplayName As String, _
      ByRef pchEaten As Integer, _
      ByRef ppidl As System.IntPtr, _
      ByRef pdwAttributes As Integer)

    Sub EnumObjects( _
      ByVal hwndOwner As IntPtr, _
      <MarshalAs(UnmanagedType.U4)> ByVal grfFlags As Integer, _
      <Out()> ByRef ppenumIDList As IntPtr)

    Sub BindToObject( _
      ByVal pidl As IntPtr, _
      ByVal pbcReserved As IntPtr, _
      ByRef riid As Guid, _
      ByRef ppvOut As IShellFolder)

    Sub BindToStorage( _
      ByVal pidl As IntPtr, _
      ByVal pbcReserved As IntPtr, _
      ByRef riid As Guid, _
      <Out()> ByVal ppvObj As IntPtr)

    <PreserveSig()> _
    Function CompareIDs( _
    ByVal lParam As IntPtr, _
    ByVal pidl1 As IntPtr, _
    ByVal pidl2 As IntPtr) As Integer

    Sub CreateViewObject( _
      ByVal hwndOwner As IntPtr, _
      ByRef riid As Guid, _
      ByVal ppvOut As Object)

    Sub GetAttributesOf( _
      ByVal cidl As Integer, _
      ByVal apidl As IntPtr, _
      <MarshalAs(UnmanagedType.U4)> ByRef rgfInOut As Integer)

    Sub GetUIObjectOf( _
      ByVal hwndOwner As IntPtr, _
      ByVal cidl As Integer, _
      ByRef apidl As IntPtr, _
      ByRef riid As Guid, _
      <Out()> ByVal prgfInOut As Integer, _
      <Out(), MarshalAs(UnmanagedType.IUnknown)> ByRef ppvOut As Object)

    Sub GetDisplayNameOf( _
      ByVal pidl As IntPtr, _
      <MarshalAs(UnmanagedType.U4)> ByVal uFlags As Integer, _
      ByRef lpName As STRRET_CSTR)

    Sub SetNameOf( _
      ByVal hwndOwner As IntPtr, _
      ByVal pidl As IntPtr, _
      <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszName As String, _
      <MarshalAs(UnmanagedType.U4)> ByVal uFlags As Integer, _
      ByRef ppidlOut As IntPtr)

End Interface

Public Class ShellInterop
    <DllImport("shell32.dll", CharSet:=CharSet.Auto)> _
    Public Shared Function SHGetDesktopFolder( _
    <Out()> ByRef ppshf As IShellFolder) As Integer
    End Function
End Class

Open in new window

I have done the same as the thumbnails when needing to show file icons. Go find the icon--not a simple process--then cache it for each extension....


Well, if you do it my way and just do all of the drawing you can just use math.

Get the Height of your images, plus the space vertically between them, do the math and see how much fits inside the form you are showing it in. You should be able to work that such that based upon the scroll bar value and those values  you show the items you want to.

Think of it like this.  

IMAGE_WIDTH=300
IMAGE_HEIGHT=200
HORIZ_BUFFER=10
VERT_BUFFER=10

Put them inside a Panel object if you want.

NUM_HORIZ_PICTURES=Panel.width / (IMAGE_WIDTH + VERT_BUFFER)
NUM_VERT_PICTURES=Panel.height / (IMAGE_HEIGHT + HORIZ_BUFFER)
Those two calcs tell you the maximum number of pictures you can show on a given page.

Use the scroll bar value to figure out where you are, and draw the appropriate images.
I think I might store references to them in a collection or something. I'd load three times what I can show--some before and some after for a caching effect.

I hope this gives you some idea  how you might tackle the problem.  Think of it as...
you draw all the images based on the calcs and the scrollbar button, then when the vertical scrollbar value changes you do it again, but using different images based on what the scrollbar value is, so you just blank out the panel--erasing all--and then draw the newer images.  You will need to set the panel to Double Buffering to limit flickering.
Avatar of taz8020

ASKER

Hi Idle_Mind, your solution works perfect. All the images are not in proportion correct. where would i fix this?
Avatar of taz8020

ASKER

Plus how you then add or remove an addtional item. For example wanted to have a button to remove selected, and another to add addtional files.

I would have used this
For Each selecteditem As ListViewItem In ListView1.SelectedIndices
     ListView1.Items.Remove(selecteditem)
Next

But does not work in virtualmode?
Fixing aspect ratio is relatively easy.

Adding and Removing items in virtual mode is trickier though.  Are you only adding items to the end of the ListView?...or do you need an actual Insert into the middle?

Working on an example...
Avatar of taz8020

ASKER

Does not matter where they are added from. Was thinking maybe add files to the Filesname() then reorder, then populate view again. Just not sure how to do this.
I guess you could make Filename a sortedlist intead of just a list. But I've heard sorted list degrades after to many items.
Avatar of taz8020

ASKER

Ok thanks, lets sick with the Filenames, I can see how I would add one but how do i get the index of the selected images and remove them from the filenames()? plus keep the image size correct?
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of taz8020

ASKER

Hi Idle_mind I have posted a question which I think you would be able to answer, loads have got back but not getting anywhere.

Just wondered if poss if you could have a look
https://www.experts-exchange.com/questions/27758439/vb-net-How-save-and-Load-all-textbox-data-on-form-to-XML-File.html
In that other question, are you wanting to save only TextBox data?

XML is fine, but a simple text file would do just as well...
Avatar of taz8020

ASKER

On the form there is lots of text boxes, a few combo boxes and a datagrid view. Just thought if I can see how it's is done by looping through text boxes I could work the rest out my self. Need to save it so can be opened on other computers. Any file would be fine. Just thought the XML would be the easiest ?
Hmmm...the DataGridView could probably use one of the built-in methods of the underlying DataSet to serialize itself:
http://msdn.microsoft.com/en-us/library/system.data.dataset.writexml.aspx

I'll post an example for the TextBoxes/ComboBoxes in the other question though.