PDA

View Full Version : [SOLVED:] A Class Module to store Images from a folder for later reuse in Workbook Excel VBA



sifar786
02-15-2020, 02:01 AM
I am importing csv data and images from a folder into a temporary Excel workbook, which is used for readying the data for reporting purposes and resizing the data-related images. I then plan to import this data and corresponding images into my current workbook.


With ADO, i am able to import the csv data from the temporary workbook into my current workbook, BUT NOT THEIR CORRESPONDING IMAGES to generate different reports.


I came to know that a Custom Class Module can store objects for later use. My question is, is there a way to store the pictures directly from the folder into a Class Module by the picture name (instead of inserting them into the temporary workbook), for later use for generating reports in current workbook?


e.g. a Class Module to hold the pictures till the current workbook is open (maintain active session).


Is this possible?

Dave
02-15-2020, 06:11 AM
Seems similar to your previous query except that U now want to use a temporary wb to store the images. You can load images into a collection for later use however the collection will only exist as long as the temporary wb is open. HTH. Dave

sifar786
02-15-2020, 07:19 AM
Hi @Dave,

If i plan to do away with the temp.xlsx workbook and just load the csvs and images into a Class Module in my Current workbook, so that they can remain in memory till the current workbook is open, how feasible is this to do?

Dave
02-15-2020, 08:02 AM
This is confusing reading both threads on the same topic. I'm not sure why U need a temporary wb?
As I mentioned, U can load images into a collection that will persist as long as the wb is open.
To illustrate, create a userform and add an image control and 3 command buttons. Add the following
code to the userform....

Private Sub CommandButton1_Click()
'adjust file path to suit
UserForm1.Image1.Picture = LoadPicture("C:\TestFolder\SomePic.gif")
End Sub

Private Sub CommandButton2_Click()
Set PicCollect = New Collection
PicCollect.Add UserForm1.Image1.Picture
End Sub
Private Sub CommandButton3_Click()
With UserForm1.Image1
.Picture = PicCollect(1)
End With
End Sub
Add a module and insert this line of code...

Public PicCollect As Collection
To trial change the picture path to a valid picture file path. Show the userform.
Select command1 to load the picture in the image. Select command2 to load the picture to the collection.
Unload the userform. Show the userform. Select command3 to load the image from the collection.
U could loop through all of your pics and load them into the collection and retrieve them as needed.
HTH. Dave

sifar786
02-15-2020, 08:43 AM
Sorry for the confusion @Dave. Just working on a weekend trying to close this issue has made me hanky-panky!

You mean, do i need to add a Userform inorder to load each image to an image control to store them in my current workbook (if i go with your suggestion of doing away with the temporary workbook)?

Is there a way to avoid the userform but create an image control instance for each image, store it in a collection and then use it as needed while current workbook is open?

Dave
02-15-2020, 09:30 AM
You will need some method of creating the images to store in the collection. The pic file
address is just a string. You probably could use a chart or a frame control to load the
image into. U only need to show the userform/chart/frame once and load the collection.
Sticking with the previous example, change command2 to the following code
(Adjust your folder path of images and file extension to suit)....

Private Sub CommandButton2_Click()
Dim FSO As Object, FolDir As Object, FileNm As Object
Set PicCollect = New Collection
Set FSO = CreateObject("scripting.filesystemobject")
'change folder to suit
Set FolDir = FSO.GetFolder("C:\TestFolder")
For Each FileNm In FolDir.Files
If FileNm.Name Like "*.gif" Then 'change file extension to suit
UserForm1.Image1.Picture = LoadPicture("C:\TestFolder\" & FileNm.Name)
PicCollect.Add UserForm1.Image1.Picture
End If
Next FileNm
Set FSO = Nothing
Set FolDir = Nothing
End Sub
Add a spin control to the userform. Add this code to the userform...

Dim spincnt As Integer
Private Sub SpinButton1_SpinDown()
spincnt = spincnt - 1
If spincnt < 1 Then
spincnt = 1
End If
With UserForm1.Image1
.Picture = PicCollect(spincnt)
End With
End Sub

Private Sub SpinButton1_SpinUp()
spincnt = spincnt + 1
If spincnt > PicCollect.Count Then
spincnt = PicCollect.Count
End If
With UserForm1.Image1
.Picture = PicCollect(spincnt)
End With
End Sub
To operate, select command2. The spin button will then loop through the pics.
Unload the userform. Show the userform. The spin button will continue to show
the pics in the collection. Not sure if this helps but the pics are converted to memory. Dave

sifar786
02-15-2020, 09:52 AM
Thanks for the example @Dave. Though it doesn't help with my situation, it does answer one question that i had in mind, i.e. Pictures cannot be imported and stored directly to a collection or dictionary without loading them to some OLE control without which they may not be imported to workbook later. Also, it means Ole controls loaded with pictures cannot be saved to a Dictionary or Collection without a Userform to hold them.

Appreciate your help here.

Dave
02-15-2020, 10:56 AM
You are welcome. Yes U need some container to first generate the images to store them in the collection after that they are available from the collection to do whatever U want just like any picture file (ie. U don't need the userform to access them). Dave

sifar786
02-15-2020, 11:18 AM
You mean if the container is the Image Control, can it be stored in a Dictionary/Collection without the need of a userform?

Dave
02-15-2020, 01:24 PM
No. U initially need some container to generate the images. After U generate the images and the images are stored in a collection, U no longer need the container for anything. Dave

Dave
02-16-2020, 04:18 PM
It's Sunday and I'm bored. Thought maybe I'd blow off some web space to further illustrate
that it is possible to load pictures to memory for later use. It seems like that is your
objective (without the use of a userform). All you need is a folder location with pics for
this to work. It adds the pic to a sheet as a shape, then copies the shape, loads it to the
collection and then deletes the pic/shape. The pics are loaded in the collection in whatever order
the files have within the folder. To access the pics from the collection, reference their
location within the collection eg. UserForm1.Image1.Picture = PicCollect(1)
Note: the code has some API stuff which requires a 32 bit office installation
Module code..(Thanks mostly to Jaafar for the creating pic from the clipboard code)

Public PicCollect As Collection
'Declare a UDT to store a GUID for the IPicture OLE Interface
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type

'Declare a UDT to store the bitmap information
Private Type uPicDesc
Size As Long
Type As Long
hPic As Long
hPal As Long
End Type

'Does the clipboard contain a bitmap/metafile?
Private Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Integer) As Long
'Open the clipboard to read
Public Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
'Get a pointer to the bitmap/metafile
Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long
'Close the clipboard
Public Declare Function CloseClipboard Lib "user32" () As Long
Public Declare Function EmptyClipboard Lib "user32" () As Long
'Convert the handle into an OLE IPicture interface.
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (PicDesc As uPicDesc, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
'Create our own copy of the metafile, so it doesn't get wiped out by subsequent clipboard updates.
Private Declare Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" (ByVal hemfSrc As Long, ByVal lpszFile As String) As Long
'Create our own copy of the bitmap, so it doesn't get wiped out by subsequent clipboard updates.
Private Declare Function CopyImage Lib "user32" (ByVal handle As Long, ByVal un1 As Long, ByVal n1 As Long, ByVal N2 As Long, ByVal un2 As Long) As Long

'The API format types we're interested in
Public Const CF_BITMAP = 2
Private Const CF_PALETTE = 9
Public Const CF_ENHMETAFILE = 14
Private Const IMAGE_BITMAP = 0
Private Const LR_COPYRETURNORG = &H4

'OLE Picture types
Private Const PICTYPE_BITMAP = 1
Private Const PICTYPE_ENHMETAFILE = 4
'========================================================================== ============================
Public Function PastePicture(Optional lXlPicType As Long = xlPicture) As IPicture
Dim h As Long, hPicAvail As Long, hPtr As Long, hPal As Long, lPicType As Long, hCopy As Long

'Convert the type of picture requested from the xl constant to the API constant
lPicType = IIf(lXlPicType = xlBitmap, CF_BITMAP, CF_ENHMETAFILE)
'Check if the clipboard contains the required format
hPicAvail = IsClipboardFormatAvailable(lPicType)

If hPicAvail <> 0 Then
h = OpenClipboard(0&) 'Get access to the clipboard
If h > 0 Then
hPtr = GetClipboardData(lPicType) 'Get a handle to the image data
'Create our own copy of the image on the clipboard, in the appropriate format.
If lPicType = CF_BITMAP Then
hCopy = CopyImage(hPtr, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG)
Else
hCopy = CopyEnhMetaFile(hPtr, vbNullString)
End If

'clear then close clipboard
EmptyClipboard
h = CloseClipboard 'Release the clipboard to other programs

'If we got a handle to the image, convert it into a Picture object and return it
If hPtr <> 0 Then Set PastePicture = CreatePicture(hCopy, 0, lPicType)
End If
End If
End Function
Private Function CreatePicture(ByVal hPic As Long, ByVal hPal As Long, ByVal lPicType) As IPicture
Dim r As Long, uPicInfo As uPicDesc, IID_IDispatch As GUID, IPic As IPicture

' Create the Interface GUID (for the IPicture interface)
With IID_IDispatch
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With

' Fill uPicInfo with necessary parts.
With uPicInfo
.Size = Len(uPicInfo) ' Length of structure.
.Type = IIf(lPicType = CF_BITMAP, PICTYPE_BITMAP, PICTYPE_ENHMETAFILE) ' Type of Picture
.hPic = hPic ' Handle to image.
.hPal = IIf(lPicType = CF_BITMAP, hPal, 0) ' Handle to palette (if bitmap).
End With

' Create the Picture object.
r = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, IPic)
' If an error occured, show the description
If r <> 0 Then Debug.Print "Create Picture Error" ' & fnOLEError(r) 'Requires a reference to the "OLE Automation" type library

Set CreatePicture = IPic ' Return the new Picture object.
End Function
Public Function PicCollection(ShtName As String, Flder As String, Filext As String) As Boolean
Dim FSO As Object, FolDir As Object, FileNm As Object, Shp As Shape, Ws As Worksheet
'Call PicCollection("sheet1", "C:\Pftrial", "gif")
'Loads pics into collection
On Error GoTo ErFix
Set PicCollect = New Collection
Set FSO = CreateObject("scripting.filesystemobject")
Set FolDir = FSO.GetFolder(Flder)
Set Ws = ThisWorkbook.Worksheets(ShtName)
Application.ScreenUpdating = False
'loop files
For Each FileNm In FolDir.Files
If FileNm.Name Like "*." & Filext Then
Set Shp = Ws.Shapes.AddPicture(Flder & "\" & FileNm.Name, _
False, True, 100, 100, 1, 1)
'Use picture'Shp height and width.
Shp.ScaleHeight 1, msoCTrue
Shp.ScaleWidth 1, msoCTrue
Shp.CopyPicture
PicCollect.Add PastePicture(CF_ENHMETAFILE)
Shp.Delete
End If
Next FileNm
Application.ScreenUpdating = True
Set FSO = Nothing
Set FolDir = Nothing
PicCollection = True
Exit Function
ErFix:
On Error GoTo 0
Application.ScreenUpdating = True
Set FSO = Nothing
Set FolDir = Nothing
PicCollection = False
End Function
To operate...

'adjust sheetname, foldername/path and file extension to suit
If Not PicCollection("sheetname", "C:\foldername", "gif") Then
MsgBox "Picture collection NOT created!"
End If
That's about it for this thread. Dave

sifar786
02-19-2020, 06:21 AM
@Dave, i tried the above code but it didnt work on my 64 bit Excel, 64 bit Windows 10 system. :-(

I finally found a 64 bit code on this same site :

http://www.vbaexpress.com/forum/showthread.php?25275-Saving-Clipboard-data-as-picture/page2


which works sometime and sometimes gives an Error on "CloseClipboard" :


"1004. Application Defined or Object Defined Error."

- I am not sure but i think it may be happening because of Clipboard not closing quickly. So added a Do...DoEvents Loop Until H=0. It works most of the time, but still gives same error after loading a couple of Pictures from folder. How to tackle this issue?

- Once the Dictionary gets populated, i tried running a different Procedure to check if Dictionary was still populated. It maintained its contents until an error occurred. In which case, i have to run the "picollection" procedure again.

Question: How do i insert the picture from the Dictionary or Collection to my Thisworkbook Sheet?

Here's the full code i am using. Maybe you will be able to quickly identify the root cause and suggest an appropriate solution :




Option Explicit


Public PicCollect As Object 'Collection

'************************************************************************** *'*
'* AUTHOR & DATE: STEPHEN BULLEN, Office Automation Ltd
'* 15 November 1998


'*
'* DESCRIPTION: Creates a standard Picture object from whatever is on the clipboard.
'* This object can then be assigned to (for example) and Image control
'* on a userform. The PastePicture function takes an optional argument of
'* the picture type - xlBitmap or xlPicture.
'*
'* The code requires a reference to the "OLE Automation" type library
'*
'* The code in this module has been derived from a number of sources
'* discovered on MSDN.
'*
'* To use it:
'* Set Image1.Picture = PastePicture(xlPicture)
'* to paste a picture of whatever is on the clipboard into a standard image control.
'*
'* PROCEDURES:
'* PastePicture The entry point for the routine
'* CreatePicture Private function to convert a bitmap or metafile handle to an OLE reference
'* fnOLEError Get the error text for an OLE error code
'************************************************************************** *
'Option Private Module
Option Compare Text


''' User-Defined Types for API Calls


'Declare a UDT to store a GUID for the IPicture OLE Interface
Private Type guid
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type



'''Windows API Function Declarations
#If VBA7 Then


Private Type uPicDesc
Size As Long
Type As Long
hPic As LongPtr
hPal As LongPtr
End Type


'Does the clipboard contain a bitmap/metafile?
Private Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Integer) As Long


'Open the clipboard to read
Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long


'Get a pointer to the bitmap/metafile
Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As LongPtr


'Close the clipboard
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long


'Convert the handle into an OLE IPicture interface.
Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleaut32.dll" (PicDesc As uPicDesc, RefIID As guid, ByVal fPictureOwnsHandle As LongPtr, IPic As IPicture) As Long

'Create our own copy of the metafile, so it doesn't get wiped out by subsequent clipboard updates.
Declare PtrSafe Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" (ByVal hemfSrc As LongPtr, ByVal lpszFile As String) As LongPtr

'Create our own copy of the bitmap, so it doesn't get wiped out by subsequent clipboard updates.
Declare PtrSafe Function CopyImage Lib "user32" (ByVal Handle As LongPtr, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As LongPtr


#Else

'Declare a UDT to store the bitmap information
Private Type uPicDesc
Size As Long
Type As Long
hPic As Long
hPal As Long
End Type


'Does the clipboard contain a bitmap/metafile?
Private Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Integer) As Long

'Open the clipboard to read
Private Declare Function OpenClipboard Lib "user32" (ByVal hWnd As Long) As Long

'Get a pointer to the bitmap/metafile
Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long

'Close the clipboard
Private Declare Function CloseClipboard Lib "user32" () As Long

'Convert the handle into an OLE IPicture interface.
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (PicDesc As uPicDesc, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long

'Create our own copy of the metafile, so it doesn't get wiped out by subsequent clipboard updates.
Declare Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" (ByVal hemfSrc As Long, ByVal lpszFile As String) As Long

'Create our own copy of the bitmap, so it doesn't get wiped out by subsequent clipboard updates.
Declare Function CopyImage Lib "user32" (ByVal Handle As Long, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long


#End If


'The API format types we're interested in
Const CF_BITMAP = 2
Const CF_PALETTE = 9
Const CF_ENHMETAFILE = 14
Const IMAGE_BITMAP = 0
Const LR_COPYRETURNORG = &H4
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '''''''''''''''''''''''
''' Subroutine: PastePicture
'''
''' Purpose: Get a Picture object showing whatever's on the clipboard.
'''
''' Arguments: lXlPicType - The type of picture to create. Can be one of:
''' xlPicture to create a metafile (default)
''' xlBitmap to create a bitmap
'''
''' Date Developer Action
''' ------------------------------------------------------c--------------------
''' 30 Oct 98 Stephen Bullen Created
''' 15 Nov 98 Stephen Bullen Updated to create our own copies of the clipboard images


Function PastePicture(Optional lXlPicType As Long = xlPicture) As IPicture


#If VBA7 Or Win64 Then
PastePictureVBA7 (lXlPicType)
#Else
PastePictureWin32 (lXlPicType)
#End If


End Function


Function PastePictureVBA7(Optional lXlPicType As Long = xlPicture) As IPicture
'Some pointers
Dim H As Long, hPicAvail As Long, hPtr As LongPtr, hPal As LongPtr, lPicType As Long, hCopy As LongPtr

'Convert the type of picture requested from the xl constant to the API constant
lPicType = IIf(lXlPicType = xlBitmap, CF_BITMAP, CF_ENHMETAFILE)

'Check if the clipboard contains the required format
hPicAvail = IsClipboardFormatAvailable(lPicType)

If hPicAvail <> 0 Then
'Get access to the clipboard
H = OpenClipboard(0&)

If H > 0 Then
'Get a handle to the image data
hPtr = GetClipboardData(lPicType)

'Create our own copy of the image on the clipboard, in the appropriate format.
If lPicType = CF_BITMAP Then
hCopy = CopyImage(hPtr, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG)
Else
hCopy = CopyEnhMetaFile(hPtr, vbNullString)
End If

'Release the clipboard to other programs.*******HERE I TRIED DOWN CODE BY ADDING DOEVENTS.*******
Do
H = CloseClipboard
EmptyClipboard
DoEvents
Loop Until H = 0

'If we got a handle to the image, convert it into a Picture object and return it
If hPtr <> 0^ Then Set PastePictureVBA7 = CreatePictureVBA7(hCopy, 0, lPicType)
End If
End If
End Function




Function PastePictureWin32(Optional lXlPicType As Long = xlPicture) As IPicture




'Some pointers
Dim H As Long, hPicAvail As Long, hPtr As Long, hPal As Long, lPicType As Long, hCopy As Long

'Convert the type of picture requested from the xl constant to the API constant
lPicType = IIf(lXlPicType = xlBitmap, CF_BITMAP, CF_ENHMETAFILE)

'Check if the clipboard contains the required format
hPicAvail = IsClipboardFormatAvailable(lPicType)

If hPicAvail <> 0 Then
'Get access to the clipboard
H = OpenClipboard(0&)

If H > 0 Then
'Get a handle to the image data
hPtr = GetClipboardData(lPicType)

'Create our own copy of the image on the clipboard, in the appropriate format.
If lPicType = CF_BITMAP Then
hCopy = CopyImage(hPtr, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG)
Else
hCopy = CopyEnhMetaFile(hPtr, vbNullString)
End If

'Release the clipboard to other programs. *******HERE I TRIED DOWN CODE BY ADDING DOEVENTS.*******
Do
H = CloseClipboard
EmptyClipboard
DoEvents
Loop Until H = 0

'If we got a handle to the image, convert it into a Picture object and return it
If hPtr <> 0 Then Set PastePictureWin32 = CreatePicture(hCopy, 0, lPicType)
End If
End If


End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '''''''''''''''''''''''
''' Subroutine: CreatePicture
'''
''' Purpose: Converts a image (and palette) handle into a Picture object.
'''
''' Requires a reference to the "OLE Automation" type library
'''
''' Arguments: None
'''
''' Date Developer Action
''' --------------------------------------------------------------------------
''' 30 Oct 98 Stephen Bullen Created
'''
Private Function CreatePictureVBA7(ByVal hPic As LongPtr, ByVal hPal As LongPtr, ByVal lPicType) As IPicture


' IPicture requires a reference to "OLE Automation"
Dim r As Long, uPicInfo As uPicDesc, IID_IDispatch As guid, IPic As IPicture


'OLE Picture types
Const PICTYPE_BITMAP = 1
Const PICTYPE_ENHMETAFILE = 4


' Create the Interface GUID (for the IPicture interface)
With IID_IDispatch
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With


' Fill uPicInfo with necessary parts.
With uPicInfo
.Size = Len(uPicInfo) ' Length of structure.
.Type = IIf(lPicType = CF_BITMAP, PICTYPE_BITMAP, PICTYPE_ENHMETAFILE) ' Type of Picture
.hPic = hPic ' Handle to image.
.hPal = IIf(lPicType = CF_BITMAP, hPal, 0) ' Handle to palette (if bitmap).
End With

' Create the Picture object.
r = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, 1, IPic)


' If an error occured, show the description
If r <> 0 Then Debug.Print "Create Picture: " & fnOLEError(r)


' Return the new Picture object.
Set CreatePictureVBA7 = IPic

End Function




Private Function CreatePicture(ByVal hPic As Long, ByVal hPal As Long, ByVal lPicType) As IPicture


' IPicture requires a reference to "OLE Automation"
Dim r As Long, uPicInfo As uPicDesc, IID_IDispatch As guid, IPic As IPicture

'OLE Picture types
Const PICTYPE_BITMAP = 1
Const PICTYPE_ENHMETAFILE = 4

' Create the Interface GUID (for the IPicture interface)
With IID_IDispatch
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With

' Fill uPicInfo with necessary parts.
With uPicInfo
.Size = Len(uPicInfo) ' Length of structure.
.Type = IIf(lPicType = CF_BITMAP, PICTYPE_BITMAP, PICTYPE_ENHMETAFILE) ' Type of Picture
.hPic = hPic ' Handle to image.
.hPal = IIf(lPicType = CF_BITMAP, hPal, 0) ' Handle to palette (if bitmap).
End With

' Create the Picture object.
r = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, IPic)

' If an error occured, show the description
If r <> 0 Then Debug.Print "Create Picture: " & fnOLEError(r)

' Return the new Picture object.
Set CreatePicture = IPic
End Function




''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '''''''''''''''''''''''
''' Subroutine: fnOLEError
'''
''' Purpose: Gets the message text for standard OLE errors
'''
''' Arguments: None
'''
''' Date Developer Action
''' --------------------------------------------------------------------------
''' 30 Oct 98 Stephen Bullen Created
Private Function fnOLEError(lErrNum As Long) As String


'OLECreatePictureIndirect return values
Const E_ABORT = &H80004004
Const E_ACCESSDENIED = &H80070005
Const E_FAIL = &H80004005
Const E_HANDLE = &H80070006
Const E_INVALIDARG = &H80070057
Const E_NOINTERFACE = &H80004002
Const E_NOTIMPL = &H80004001
Const E_OUTOFMEMORY = &H8007000E
Const E_POINTER = &H80004003
Const E_UNEXPECTED = &H8000FFFF
Const S_OK = &H0


Select Case lErrNum
Case E_ABORT
fnOLEError = " Aborted"
Case E_ACCESSDENIED
fnOLEError = " Access Denied"
Case E_FAIL
fnOLEError = " General Failure"
Case E_HANDLE
fnOLEError = " Bad/Missing Handle"
Case E_INVALIDARG
fnOLEError = " Invalid Argument"
Case E_NOINTERFACE
fnOLEError = " No Interface"
Case E_NOTIMPL
fnOLEError = " Not Implemented"
Case E_OUTOFMEMORY
fnOLEError = " Out of Memory"
Case E_POINTER
fnOLEError = " Invalid Pointer"
Case E_UNEXPECTED
fnOLEError = " Unknown Error"
Case S_OK
fnOLEError = " Success!"
End Select
End Function


Public Function PicCollection(ShtName As String, Flder As String, Filext As String) As Boolean


Dim FSO As Object, FolDir As Object, FileNm As Object, Shp As Shape, Ws As Worksheet

'Call PicCollection("sheet1", "C:\Pftrial", "gif")

'Loads pics into collection
On Error GoTo ErFix
' Set PicCollect = New Collection
Set PicCollect = CreateObject("Scripting.Dictionary")
Set FSO = CreateObject("scripting.filesystemobject")
Set FolDir = FSO.GetFolder(Flder)
Set Ws = ThisWorkbook.Worksheets(ShtName)
With Ws
For Each Shp In .Shapes
If Shp.Type = msoPicture Then
Shp.Delete
End If
Next Shp
End With

Application.ScreenUpdating = False
'loop files
For Each FileNm In FolDir.Files
If FileNm.Name Like "*." & Filext Then
Set Shp = Ws.Shapes.AddPicture(Flder & "\" & FileNm.Name, False, True, 50, 50, 1, 1)

'Use picture'Shp height and width.
Shp.ScaleHeight 1, msoCTrue
Shp.ScaleWidth 1, msoCTrue
Shp.CopyPicture 'xlScreen, xlBitmap

If Not PicCollect.Exists(FileNm.Name) Then
PicCollect.Add key:=FileNm.Name, Item:=PastePicture
Shp.Delete
End If
End If
Next FileNm

Application.ScreenUpdating = True
Set FSO = Nothing
Set FolDir = Nothing
PicCollection = True
Exit Function


ErFix:
MsgBox Err.Number & "_" & Err.Description
On Error GoTo 0
Application.ScreenUpdating = True
Set FSO = Nothing
Set FolDir = Nothing
PicCollection = False

End Function



'To operate...'adjust sheetname, foldername/path and file extension to suit
Sub Doit()
Dim strFolder$
strFolder = "C:\MyPics\"
If Not PicCollection("PicSht", strFolder, "png") Then MsgBox "Picture collection NOT created!"
End Sub


Sub TestPicsDictionary()

Dim wk As Worksheet, key, i&
Set wk = ThisWorkbook.Sheets("PicSht")

i = 0
With wk
For Each key In PicCollect.Keys
MsgBox key
.Shapes.AddPicture PicCollect(key), msoFalse, msoTrue, 50 + i, 50, PicCollect(key).Width, PicCollect(key).Height ' GET ERROR HERE ALSO AND PicCollect loses images.
i = i + 50
Next key
End With
End Sub


Also, when it works,a single IPicture does not show any properties populated!

26031


P.S: It seems "fnOLEError" is never called!

Dave
02-19-2020, 11:30 AM
As I mentioned the code was for 32 bit office installations. The "fnOLEError" is never called as I
removed that part of the code and in error I kept that line...whoops! Anyways, U can trial my kick at converting
the declarations to 64 bit. The rest of the code should be the same. I think you will need an image control
on a worksheet to diplay the images ie. Worksheets("sheet1").Image1.Picture = PicCollect(1)

Public PicCollect As Collection
'Declare a UDT to store a GUID for the IPicture OLE Interface
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type

#If VBA7 Then
Type uPicDesc
Size As Long
Type As Long
hPic As LongPtr
hPal As LongPtr
End Type
Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr
Declare PtrSafe Function OleCreatePictureIndirect Lib "OleAut32.dll" (PicDesc As uPicDesc, _
RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
Declare PtrSafe Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" ( _
ByVal hemfSrc As LongPtr, ByVal lpszFile As String) As LongPtr
Private Declare PtrSafe Function CopyImage Lib "user32" (ByVal Handle As Long, _
ByVal un1 As Long, ByVal n1 As Long, ByVal N2 As Long, ByVal un2 As Long) As LongPtr
Dim hPtr As LongPtr
#Else
Type uPicDesc
Size As Long
Type As Long
hPic As Long
hPal As Long
End Type
Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Declare Function EmptyClipboard Lib "user32" () As Long
Declare Function CloseClipboard Lib "user32" () As Long
Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Integer) As Long
Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long
Declare Function OleCreatePictureIndirect Lib "OleAut32.dll" (PicDesc As uPicDesc, _
RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
Declare Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" _
(ByVal hemfSrc As Long, ByVal lpszFile As String) As Long
Declare Function CopyImage Lib "user32" (ByVal Handle As Long, _
ByVal un1 As Long, ByVal n1 As Long, ByVal N2 As Long, ByVal un2 As Long) As Long
Dim hPtr As Long
#End If

'The API format types we're interested in
Public Const CF_BITMAP = 2
Private Const CF_PALETTE = 9
Public Const CF_ENHMETAFILE = 14
Private Const IMAGE_BITMAP = 0
Private Const LR_COPYRETURNORG = &H4

'OLE Picture types
Private Const PICTYPE_BITMAP = 1
Private Const PICTYPE_ENHMETAFILE = 4
To fix the fnOLEError mistake, you can change this...

If r <> 0 Then
MsgBox "Create Picture Error"
End If
HTH. Dave

sifar786
02-19-2020, 12:15 PM
Hi @Dave,

I checked your declarations and added the missing one i.e. "EmptyClipboard".

Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long

Also changed WFormat in both the below declarations from "Integer" to "Long".


Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long

Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr


and also changed the below from LongPtr to Long:

fPictureOwnsHandle As Long
in


Declare PtrSafe Function OleCreatePictureIndirect Lib "OleAut32.dll" (PicDesc As uPicDesc, _ RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long


The code runs and i can see from the watch window that the dictionary items are populated, but show as "Nothing".

26034


i.e. the IPic in OleCreatePictureIndirect is not getting created correctly. Am i missing something?

26035

How do i add an Image control to a Worksheet? Can you post a working example file demonstrating image control on worksheet?

Dave
02-20-2020, 12:05 AM
Get rid of your TestPicsDictionary testing code...the pics are in a collection already so no need for dictionary code. Go to the control toolbox (developer view) and insert/select the active X image control, select your sheet and size the control. Select an Active X spinbutton control then select your sheet. Paste this code to your sheet code. Use the operating code previously posted to load the collection. The spinbutton will loop U through all your pics in the image control. Dave'**change sheet name to suit
Dim spincnt As IntegerPrivate Sub SpinButton1_SpinDown()spincnt = spincnt - 1If spincnt < 1 Thenspincnt = 1End IfWith Sheets("Sheet1").Image1.Picture = PicCollect(spincnt)End WithEnd SubPrivate Sub SpinButton1_SpinUp()spincnt = spincnt + 1If spincnt > PicCollect.Count Thenspincnt = PicCollect.CountEnd IfWith Sheets("Sheet1").Image1.Picture = PicCollect(spincnt)End WithEnd Sub

Dave
02-20-2020, 12:12 AM
Hmmmmm…..
Dim spincnt As IntegerPrivate Sub SpinButton1_SpinDown()spincnt = spincnt - 1If spincnt < 1 Thenspincnt = 1End IfWith Sheets("Sheet1").Image1.Picture = PicCollect(spincnt)End WithEnd SubPrivate Sub SpinButton1_SpinUp()spincnt = spincnt + 1If spincnt > PicCollect.Count Thenspincnt = PicCollect.CountEnd IfWith Sheets("Sheet1").Image1.Picture = PicCollect(spincnt)End WithEnd Sub

sifar786
02-20-2020, 02:40 AM
Yes Dave, the picture objects seem to be loaded in the Dictionary (or Collection) but do not seem to hold Picture information. Thats what i was mentioning previously.

So after loading the images once from the folder into Dictionary, when i use the SpinUp or SpinDown it results in an error as shown below:

26039


Error on
.Image1.Picture = PicCollect(SpinCnt)Then :
26041

sifar786
02-20-2020, 03:05 AM
I tried even your Collection Code (just renamed PicCollection to PiczCollection and PicCollect to PiczCollect). Same issue!

26042

So i manually selected the Image control and then tried adding a PNG picture to the Image Control. Unfortunately, it does not show File Filters for PNG. So i selected "All Files" and then chose a PNG file. The Image control shot up a message saying "INVALID PICTURE". :-(

EDIT:
Later i also tried with JPG but still no images loading in Image Control! Somehow IPicture is not getting created. I am not sure what setting it is missing, but i have copied your exact code!

Dave
02-20-2020, 07:26 AM
I'll try this again...

Dim spincnt As Integer
Private Sub SpinButton1_SpinDown()
spincnt = spincnt - 1
If spincnt < 1 Then
spincnt = 1
End If
With Sheets("Sheet1").Image1
.Picture = PicCollect(spincnt)
End With
End Sub
Private Sub SpinButton1_SpinUp()
spincnt = spincnt + 1
If spincnt > PicCollect.Count Then
spincnt = PicCollect.Count
End If
With Sheets("Sheet1").Image1
.Picture = PicCollect(spincnt)
End With
End Sub
I think U missed the Spincnt variable declaration at the top of the module. Apologies, the site wouldn't let me post the code correctly or edit it last pm. The code I posted works with my trialled .gif files on my 32 bit install. Dave

Dave
02-20-2020, 08:06 AM
Tested with gif, bmp, jpg and png files...they all work. Dave

sifar786
02-20-2020, 08:26 AM
No Worries Dave. i had put the Declaration on the top of the module. :-)

Here is a sample file to demonstrate my problem. I have used your code and followed the steps you suggested.

If you put a watch on the PicCollect Collection or on IPic in the API Module then you will notice that none of the IPicture properties are populating. They are instead showing "Nothing".

Dave
02-20-2020, 09:15 AM
Trial this. Dave

sifar786
02-20-2020, 09:42 AM
I didn't change any of your code, but clicked on the CommandButton. Got the following error:

26045

I am on Windows 10 Enterprise, Excel 2016 Pro. Was my file working for you, as it contained both Win32 as well as Win64 API code?

Dave
02-20-2020, 10:04 AM
Tested perfect... the 64 bit code for GetClipBoardData must not be correct. I have no 64 bit to test on. Dave

sifar786
02-20-2020, 10:29 AM
hmmm...how do i resolve this issue? Is there some documentation for 64 bit that you can suggest that may shed some light on this issue?

BTW does Data1 need to be a longPtr?



Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type


or any of the other Types or Enums that go into defining the IPicture attributes?

Dave
02-21-2020, 10:41 AM
I googled some more. U can trial this 64bit stuff....

#If VBA7 Then
Type uPicDesc
Size As Long
Type As Long
hPic As LongPtr
hPal As LongPtr
End Type
Declare PtrSafe Function CloseClipboard Lib "user32" () As LongPtr
Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
Declare PtrSafe Function EmptyClipboard Lib "user32" () As LongPtr
Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As LongPtr) As LongPtr
Declare PtrSafe Function OleCreatePictureIndirect Lib "OleAut32.dll" (PicDesc As uPicDesc, _
RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
Declare PtrSafe Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" ( _
ByVal hemfSrc As LongPtr, ByVal lpszFile As String) As LongPtr
Declare PtrSafe Function CopyImage Lib "user32" ( _
ByVal Handle As LongPtr, ByVal un1 As Long, ByVal n1 As Long, _
ByVal N2 As Long, ByVal un2 As Long) As LongPtr
Dim hPtr As LongPtr

sifar786
02-22-2020, 01:18 AM
Thanks @Dave.

What i found out was that the IPic was returning the picture as seen from this test :

26059

26060
But I think the Collection or Dictionary is not returning it to the Image1 in the proper format.

How does one store an IPicture interface to a Collection or Dictionary?

Dave
02-22-2020, 07:46 AM
"How does one store an IPicture interface to a Collection" ….. that is what this code does. Sorry sifar786 but I have no 64 bit instal to test code. As stated it works with gif, bmp, jpg and png files on my 32 bit install. I'm guessing that the VBA 7 declarations are still not correct. Good luck. Dave
ps. Collections are not the same as a Dictionary and I have no idea if a Dictionary can contain images/objects.
On second look, why are you not using the code I provided? U are not loading the pics in a collection, U are trying to create a picture from within the createpicture routine???

PicCollect.Add PastePicture(CF_ENHMETAFILE) Is how the pics are loaded in the collection. So...

sheets("picsht").image1.Picture =PastePicture(CF_ENHMETAFILE) would work for a copied picture if U use the code correctly.

sifar786
02-22-2020, 08:09 AM
The earlier sample file i attached used your Collection approach only. Dictionaries can store any datatypes including Objects (not sure of Picture types if they are not considered as objects...).

I am also using the same declarations that you provided, but still the same issue. The only way it works, is as shown in my earlier reply, i am able
to load the pic directly i.e.


Thisworkbook.sheets("PicSht").Image1.Picture=IPic

but not by assigning from a Collection

PicCollect.Add PastePicture(CF_ENHMETAFILE)

sifar786
02-22-2020, 12:52 PM
Hi @Dave,

I finally was able to make your file work for 64 bit address space using the latest declarations you gave me. :-)

I have not changed any major part of the code, except the following:

-
Added a Do...DoEvents....Loop Until H=0 This is necessary, else the Clipboard doesn't Clear/Close correctly e.g., if you run the button once and then click the Up or Down arrows of the Spinbutton and then click the button again, it shoots up an Error!

- Put
On Error Resume Next....On Error Goto 0 on the SpinButton click procedures...otherwise if button is not clicked and spinbutton Up/Down arrows are clicked, it shoots up an Error!



I saw that you had commented 32 bit WinAPI code. When i uncommented it and tried to use something like below to redirect the call either to 64 bit function or 32 bit functions, strange enough, i was not able to show the images in the Image1 control :


Public Function PastePicture(Optional lXlPicType As Long = xlPicture) As IPicture
#If VBA7 Or Win64 Then
PastePictureVBA7 (lXlPicType)
#Else
PastePictureWin32 (lXlPicType)
#End If
End Function




One Last help : Could you help me by adding the correct 32 bit WinAPI code, as people with 32 bit systems will also be using it?

Here's the attached file.

Dave
02-22-2020, 02:11 PM
Well it's been quite a journey. I'm glad that U have got it working and hope that it addresses your needs. Not sure why U need to adjust the pastepicture and I really don't understand it? The error on spinbutton will occur if the collection isn't loaded. Not sure why U need a delay to clear the clipboard but I'll post some code that U can trial. The file U sent still works for all image types on my 32 bit install. Have a nice day. Dave

'clear then close clipboard. The Do...Loop ensure proper clearing & closing of clipboard
' Do
EmptyClipboard
'DoEvents
Dim t As Double
t = Timer
Do Until Timer - t > 1
DoEvents
Loop
H = CloseClipboard 'Release the clipboard to other programs
' DoEvents
' Loop Until H = 0]

poielsd
03-04-2020, 09:22 AM
Seems similar to your previous query except that U now want to use a temporary wb to store the images. You can load images into a collection for later use however the collection will only exist as long as the temporary wb is open. HTH. Dave