tags:

views:

879

answers:

4

To receive the bounty, please provide an answer with working code. Thanks.

I have a stdole.StdPicture Object of the Type vbPicTypeIcon. I need to convert it to Type vbPicTypeBitmap. Due to project contraints, I need to be able to do this using Win32 or VBA. I am trying to load a file's icon to a command bar button. Here is what I have so far. It produces a lovely black square:) I am really new to graphics land so pardon me if it's a basic question.

Option Explicit

Private Const vbPicTypeBitmap As Long = 1
Private Const vbPicTypeIcon As Long = 3

Private Const SHGFI_ICON As Long = &H100&
Private Const SHGFI_SMALLICON As Long = &H1&

Private Type PICTDESC
    cbSize As Long
    pictType As Long
    hIcon As Long
    hPal As Long
End Type

Private Type typSHFILEINFO
  hIcon As Long
  iIcon As Long
  dwAttributes As Long
  szDisplayName As String * 260
  szTypeName As String * 80
End Type

Private Declare Function DestroyIcon Lib "user32" (ByVal hIcon As Long) As Long
Private Declare Function SHGetFileInfoA Lib "shell32.dll" (ByVal pszPath As String, ByVal dwFileAttributes As Long, psfi As typSHFILEINFO, ByVal cbSizeFileInfo As Long, ByVal uFlags As Long) As Long
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (lpPictDesc As PICTDESC, riid As Any, ByVal fOwn As Long, ipic As stdole.IPictureDisp) As Long

Public Sub Test()
    Dim btn As Office.CommandBarButton
    Dim lngRslt As Long
    Dim lngAppInstc As Long
    Dim strFilePath As String
    Dim tFI As typSHFILEINFO
    Dim pic As stdole.IPictureDisp
    Set btn = TestEnv.GetTestButton
    lngAppInstc = Excel.Application.Hinstance
    strFilePath = TestEnv.GetTestFile
    If LenB(strFilePath) = 0& Then
        Err.Raise 70&
    End If
    SHGetFileInfoA strFilePath, 0&, tFI, LenB(tFI), SHGFI_ICON Or SHGFI_SMALLICON
    Set pic = IconToPicture(tFI.hIcon)
    btn.Picture = pic
Exit_Proc:
    On Error Resume Next
    If tFI.hIcon Then
        lngRslt = DestroyIcon(tFI.hIcon)
    End If
    Exit Sub
Err_Hnd:
    MsgBox Err.Description, vbCritical Or vbMsgBoxHelpButton, Err.Number, Err.HelpFile, Err.HelpContext
    Resume Exit_Proc
    Resume
End Sub

Private Function IconToPicture(ByVal hIcon As Long) As stdole.IPictureDisp
    'Modified from code by Francesco Balena on DevX
    Dim pic As PICTDESC
    Dim guid(0 To 3) As Long
    Dim pRtnVal As stdole.IPictureDisp
    pic.cbSize = LenB(pic)
    'pic.pictType = vbPicTypeBitmap
    pic.pictType = vbPicTypeIcon
    pic.hIcon = hIcon
    ' this is the IPicture GUID {7BF80980-BF32-101A-8BBB-00AA00300CAB}
    ' we use an array of Long to initialize it faster
    guid(0) = &H7BF80980
    guid(1) = &H101ABF32
    guid(2) = &HAA00BB8B
    guid(3) = &HAB0C3000
    ' create the picture,
    ' return an object reference right into the function result
    OleCreatePictureIndirect pic, guid(0), True, pRtnVal
    Set IconToPicture = pRtnVal
End Function
+1  A: 

Give this post at vbAccelerator.com a shot.

Edit: The closest thing I found for VBA is this post on officeblogs.net. The code takes an icon instead of an icon handle though.

C-Pound Guru
Hi C-Pound. Perhaps I should have mentioned that I need this solution to work in VBA as well. This is what created the problem. Unfortunatly VBA cannot access the Picture object. This is why I was specifically trying to use stdole.IPictureDisp. Is it possible to make the code you referred to produce the correct object?
Oorang
Sorry about the VB-only link. I added an edit with something that may work for VBA.
C-Pound Guru
Hi CPG, Again, we are all around the problem but not quite there. This is VB.Net code. I could code it and compile it, but then I am distributing an additional file (which as mentioned, I am trying to avoid). Also it brings back a StdPicture of the Icon type. After a deep dive I realized why the assignment to the picture property wasn't working is because the StdPicture object needs to be of the Bitmap type. So as far as I can tell the problem boils down to trying to figure out how to convert said Icon. I ended up using SHGetFileInfoA to get to the right 16X16 Icon Handle then <more>
Oorang
<continued> ... OleCreatePictureIndirect to get the StdPicture Object. But it's of the wrong type. And that is where I am stuck. If you try to apply the StdPicture to the CommandBarButton you just have a black square.
Oorang
+1  A: 

Okay, I have cleaned up the code. The ExtractAssociatedIcon method is returning a 64x64 icon so for the example I have just hard coded that size. The picturebox has neen removed and the image is assigned to the form's picture property to avoid confusion.

Example: copy the code to a new form and run

Option Explicit

Private Declare Function ExtractAssociatedIcon Lib "shell32.dll" Alias "ExtractAssociatedIconA" (ByVal hInst As Long, ByVal lpIconPath As String, lpiIcon As Long) As Long
Private Declare Function DestroyIcon Lib "user32" (ByVal hIcon As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Declare Function FillRect Lib "user32" (ByVal hdc As Long, lpRect As RECT, ByVal hBrush As Long) As Long
Private Declare Function DrawIconEx Lib "user32" (ByVal hdc As Long, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (ByRef PicDesc As PICTDESC_BMP, ByRef RefIID As GUID, ByVal fPictureOwnsHandle As Long, ByRef IPic As IPicture) As Long

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

Private Type PICTDESC_BMP
    Size As Long
    Type As Long
    hBmp As Long
    hPal As Long
    Reserved As Long
End Type

Const DI_MASK = &H1
Const DI_IMAGE = &H2
Const DI_NORMAL = DI_MASK Or DI_IMAGE

Private Type RECT
        Left As Long
        Top As Long
        Right As Long
        Bottom As Long
End Type

Private Sub Form_Load()

   Call GetIcon("C:\Program Files\Internet Explorer\iexplore.exe")

End Sub

Private Sub GetIcon(ByVal sFileName As String)
   Dim hIcon As Long
   Dim hAssocIcon As Long
   Dim sAssocFile As String * 260
   Dim sCommand As String
   Dim lDC As Long
   Dim lBmp As Long
   Dim R As RECT
   Dim OldBMP As Long

   Me.AutoRedraw = True
   hIcon = ExtractAssociatedIcon(App.hInstance, sFileName, hAssocIcon)
   If hIcon <> 0 Then 'no icons found - use icon generic icon resource
      'Create a device context, compatible with the screen
      lDC = CreateCompatibleDC(GetDC(0&))
      'Create a bitmap, compatible with the screen
      lBmp = CreateCompatibleBitmap(GetDC(0&), 64, 64)
      'Select the bitmap into the device context
      OldBMP = SelectObject(lDC, lBmp)
      ' Set the rectangles' values
      R.Left = 0
      R.Top = 0
      R.Right = 64
      R.Bottom = 64
      ' Fill the rect with white
      FillRect lDC, R, 0
      ' Draw the icon
      Call DrawIconEx(lDC, 0, 0, hIcon, 64, 64, 0, 0, DI_NORMAL)
      Me.Picture = PictureFromBitmap(lBmp, 0&)
      DestroyIcon (hIcon)
   End If
   Call SelectObject(lDC, OldBMP)
   Call DeleteObject(lDC)

End Sub

Private Function PictureFromBitmap(ByVal hBmp As Long, ByVal hPal As Long) As StdPicture
    Dim IPictureIID As GUID
    Dim IPic As IPicture
    Dim tagPic As PICTDESC_BMP
    Dim lpGUID As Long

    ' Fill in the IPicture GUID
    ' {7BF80980-BF32-101A-8BBB-00AA00300CAB}
    With IPictureIID
        .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

    ' Set the properties on the picture object
    With tagPic
        .Size = Len(tagPic)
        .Type = vbPicTypeBitmap
        .hBmp = hBmp
        .hPal = hPal
    End With

    ' Create a picture that will delete it's bitmap when it is finished with it
    Call OleCreatePictureIndirect(tagPic, IPictureIID, 1, IPic)

    ' Return the picture to the caller
    Set PictureFromBitmap = IPic
End Function
Beaner
Hi, thanks for the response. This is headed down the road I want, but instead of loading the Icon into a Picture control, I am trying to load it into Office.CommandBarButton.Picture which is an IPictureDisp object.
Oorang
I was just suggesting you use this method, to a hidden Picturebox to load you icon, then use "Clipboard.Clear", Clipboard.SetData(Picture1.Picture, vbCFBitmap), and CommandBarControl.PasteFace to put the icon onto you button.
Beaner
Hi Beaner, one of the contraints of the problem is that it must also work in VBA. Sadly VBA can not access the Picture object. This is why I am trying to coarce an Icon Handle to stdole.IPictureDisp specifically.
Oorang
A: 

LoadPicture It returns an object that supports IPictureDisp. It may not be vbPicTypeBitmap though. Not sure if you can call GdipCreateBitmapFromFile in VBA.

Sheng Jiang 蒋晟
Oorang
try GdipCreateBitmapFromHICON/GdipSaveImageToFile
Sheng Jiang 蒋晟
A: 

Search Google Groups for a thread entitled, Convert StdPicture from Icon to Bitmap.

UPDATE

No, I can't get it to work either.

But I was getting a terrible sense of deja vu as I was trying it... then remembered I definitely did this a couple of years ago i.e. adding icons with masks to Excel CommandBarButtons at runtime, not knowing which version of Excel it was being opened in. Sadly I can't find the code (not in source control so didn't make it to release? I'm almost sure I got it working).

I think I borrowed heavily from these articles:

How To Create a Transparent Picture For Office CommandBar Buttons

How To Set the Mask and Picture Properties for Office XP CommandBars

And because Excel has no clipboard, I seem to recall borrowing from Stephen Bullen's PastePicture.zip.

Hope this doesn't send you off on a wild goose chase :)

onedaywhen
This looks promising, but I was unable to parley it into a working example. Although I did come up with a way to make Excel crash out to the desktop pretty consistently:) Could you please provide a working example?
Oorang
I definitely want to steer clear of the clipboard. It's terrible practice to hijack it. Another approach I considered was trying to just save the icon to a temp folder and then use load picture. But it would still have to be saved as a bitmap to load right. However as that would be yet another research problem, I thought I would save that for plan B.
Oorang