taz8020
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.
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.
You can learn plenty about drawing images, etc. here:
http://bobpowell.net/faqmain.htm
http://bobpowell.net/faqmain.htm
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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
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:
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
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.w idth / (IMAGE_WIDTH + VERT_BUFFER)
NUM_VERT_PICTURES=Panel.he ight / (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.
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.w
NUM_VERT_PICTURES=Panel.he
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.
ASKER
Hi Idle_Mind, your solution works perfect. All the images are not in proportion correct. where would i fix this?
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(sel ecteditem)
Next
But does not work in virtualmode?
I would have used this
For Each selecteditem As ListViewItem In ListView1.SelectedIndices
ListView1.Items.Remove(sel
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...
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...
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.
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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
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...
XML is fine, but a simple text file would do just as well...
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.
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.
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.