Adding worksheet picture to a comment

craig.penny

Well-known Member
Joined
May 8, 2009
Messages
656
Hello all you Excel nerds! :)

I'm trying to insert a picture into a cell comment. I've found plenty of threads but they are all for situations where the picture is not already within the worksheet or workbook. This is what I've been trying with all different things in place of the "???????" but I haven't figured it out.

Code:
Sub NamePic()
      Dim Pic As Object
      Dim Nm As String: Nm = "Picture 2"
      Dim s As Worksheet: Set s = ActiveSheet
      
      On Error Resume Next
      
      s.Cells(1, 2).Select
      Set Pic = s.Pictures(Nm)
      
      s.Cells(1, 5).AddComment
      s.Cells(1, 5).Comment.Shape.Height = Pic.Height
      s.Cells(1, 5).Comment.Shape.Width = Pic.Width
      s.Cells(1, 5).Comment.Shape.Fill.UserPicture   ????????
      
End Sub

"Picture 2" is definitely the right name but now what?

If you read this and you don't know the answer but just have an idea I'd love to hear it!

:) Thanks in advance! :)
 

Excel Facts

Excel Wisdom
Using a mouse in Excel is the work equivalent of wearing a lanyard when you first get to college
You only need 64 bit declarations if you are running 64bit office (2010).
 
Upvote 0
Ok - I've done some research but there seems to be no way to copy a comment picture to a shape.

the only twisted solution I could come up with is to brievely display the comment,take a snapshot of the area it occupies on the screen ,place the resulting BITMAP object on the clipboard and from there save it to disk.

This is brute programming but it works rather well.


workbook demo.

Proceeding:

Add a comment with a picture to cell A1. Add an oval shape somewhere on the worksheet ( "Oval 1" ) , place the code below in a Standard module and run the TEST macro :

Code:
Option Explicit

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

Private Type BITMAP
    bmType As Long
    bmWidth As Long
    bmHeight As Long
    bmWidthBytes As Long
    bmPlanes As Integer
    bmBitsPixel As Integer
    bmBits As Long
End Type

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Type RECT
    left As Long
    top As Long
    right As Long
    bottom As Long
End Type

Private Declare Function GetDeviceCaps Lib "gdi32" ( _
ByVal hdc As Long, ByVal nIndex As Long) As Long

Private Declare Function CreateDCAsNull Lib "gdi32" _
Alias "CreateDCA" (ByVal lpDriverName As String, _
lpDeviceName As Any, _
lpOutput As Any, _
lpInitData As Any _
) As Long

Private Declare Function GetDC Lib "user32.dll" _
(ByVal hwnd As Long) As Long

Private Declare Function ReleaseDC Lib "user32.dll" _
(ByVal hwnd As Long, ByVal hdc As Long) As Long

Private Declare Function DeleteDC Lib "gdi32" _
(ByVal hdc As Long) As Long

Private Declare Function SelectObject Lib "gdi32" _
(ByVal hdc As Long, ByVal hObj 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 BitBlt Lib "gdi32" _
(ByVal hDestDC As Long, _
ByVal x As Long, ByVal y As Long, _
ByVal nWidth As Long, ByVal nHeight As Long, _
ByVal hSrcDC As Long, _
ByVal xSrc As Long, ByVal ySrc As Long, _
ByVal dwRop As Long) As Long
 
Private Declare Function OpenClipboard Lib "user32" _
(ByVal hwnd As Long) As Long
 
Private Declare Function GetClipboardData Lib "user32" _
(ByVal wFormat As Integer) As Long
 
Private Declare Function CloseClipboard Lib "user32" _
() As Long

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

Private Const CF_BITMAP = 2
Private Const CF_PALETTE = 9
Private Const PICTYPE_BITMAP = 1
Private Const LOGPIXELSX = 88
Private Const LOGPIXELSY = 90
Private Const PointsPerInch = 72
Private Const SRCCOPY As Long = &HCC0020


Sub TEST()

    'copy the comment picture to the shape.
    Call CommentToShape(Range("a1").Comment, ActiveSheet.Shapes("Oval 1"))

End Sub



Private Sub CommentToShape(ByVal Comment As Comment, ByVal Shape As Shape)

    Dim IID_IDispatch As GUID
    Dim uPicinfo As uPicDesc
    Dim IPic As IPicture
    Dim hPtr As Long
    Dim hBmp As Long
    Dim sTempBMPFile As String

    'Define the temp bmp file location.
    sTempBMPFile = ThisWorkbook.Path & "\Temp.BMP"
    
    'retrieve the BMP from the memory dc.
    hBmp = BitmapFromDC(Comment)

    'Retrieve the handle to the BMP Image from the clpb.
    OpenClipboard 0
    hPtr = GetClipboardData(CF_BITMAP)
    CloseClipboard
 
    'Create the interface GUID for the picture
    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 = PICTYPE_BITMAP ' Type of Picture
        .hPic = hBmp ' Handle to image.
        .hPal = 0 ' Handle to palette (if bitmap).
    End With
 
   'Create the Picture Object
   Call OleCreatePictureIndirect(uPicinfo, IID_IDispatch, True, IPic)
 
    'Save the pic to disk.
    stdole.SavePicture IPic, sTempBMPFile
    
    'Set the shape picture.
    Shape.Fill.UserPicture sTempBMPFile
    
    'Delete the temp bmp file.
    Kill sTempBMPFile

End Sub


Private Function BitmapFromDC(ByVal Comment As Comment) As Long

   Dim tCommentRect As RECT
   Dim tBM As BITMAP
   Dim oSelection As Range
   Dim hMemoryDC As Long
   Dim hBmpCopy As Long
   Dim lhBmpCopyOld As Long
   Dim hScreenDC As Long
   Dim hAppDC As Long
   Dim lCol As Long
   Dim lRow As Long
   Dim lZoom As Long
   Dim lCommentDisplaySetting As Long
   
    'Store the worksheet initial state.
    With Application
        hAppDC = GetDC(.hwnd)
        lCommentDisplaySetting = .DisplayCommentIndicator
        lZoom = .ActiveWindow.Zoom
        lCol = .ActiveWindow.ScrollColumn
        lRow = .ActiveWindow.ScrollRow
        .DisplayCommentIndicator = xlCommentAndIndicator
        Set oSelection = Selection
        .Goto Comment.Parent, True
    End With

    'Get the comment boundaries.
    tCommentRect = GetRangeRect(Comment.Shape.OLEFormat.Object)

    'Get the screen dc.
   hScreenDC = CreateDCAsNull("DISPLAY", ByVal 0&, ByVal 0&, ByVal 0&)
   
   'Create a compatible memory dc.
   hMemoryDC = CreateCompatibleDC(hScreenDC)
   
   'Copy the app dc onto the memory dc.
    With tCommentRect
        hBmpCopy = CreateCompatibleBitmap(hScreenDC, _
        .right - .left, .bottom - .top)
        lhBmpCopyOld = SelectObject(hMemoryDC, hBmpCopy)
        BitBlt hMemoryDC, 0, 0, .right - .left _
        , .bottom - .top, hAppDC, .left, .top + 2, SRCCOPY
    End With

    'Restore the worksheet initial state.
    With Application
      .DisplayCommentIndicator = lCommentDisplaySetting
      .ActiveWindow.Zoom = lZoom
      .ActiveWindow.ScrollColumn = lCol
      .ActiveWindow.ScrollRow = lRow
      .Goto oSelection
    End With

    'CleanUp.
    SelectObject hMemoryDC, lhBmpCopyOld
    DeleteDC hScreenDC
    DeleteDC hMemoryDC

    'Return function.
    BitmapFromDC = hBmpCopy

End Function


Private Function GetRangeRect(ByVal rng As Object) As RECT
 
    Dim tPt1 As POINTAPI
    Dim tPt2 As POINTAPI
    Dim OWnd  As Window
    Dim lWBHwnd As Long
 
    On Error Resume Next
 
    Set OWnd = rng.Parent.Parent.Windows(1)
 
    With rng
        GetRangeRect.left = _
        PTtoPX(.left * OWnd.Zoom / 100, 0) _
        + OWnd.PointsToScreenPixelsX(0)
        GetRangeRect.top = _
        PTtoPX(.top * OWnd.Zoom / 100, 1) _
        + OWnd.PointsToScreenPixelsY(0)
        GetRangeRect.right = _
        PTtoPX(.Width * OWnd.Zoom / 100, 0) _
        + GetRangeRect.left
        GetRangeRect.bottom = _
        PTtoPX(.Height * OWnd.Zoom / 100, 1) _
        + GetRangeRect.top
    End With
 
End Function

Private Function ScreenDPI(bVert As Boolean) As Long
 
   Static lDPI(1), lDC
   
   If lDPI(0) = 0 Then
        lDC = GetDC(0)
        lDPI(0) = GetDeviceCaps(lDC, LOGPIXELSX)
        lDPI(1) = GetDeviceCaps(lDC, LOGPIXELSY)
        lDC = ReleaseDC(0, lDC)
    End If
    ScreenDPI = lDPI(Abs(bVert))
 
End Function
 
Private Function PTtoPX _
(Points As Single, bVert As Boolean) As Long
 
    PTtoPX = Points * ScreenDPI(bVert) / PointsPerInch
 
End Function
 
Upvote 0
Rory, Jaafar,

Any thoughts on Craig's problem where the code will work on one machine but not another? And the behavior he describes in post #17 of this thread:

The problem only happens when I'm on a worksheet, change a value, and press TAB or ENTER and then I get "User-defined type not defined". If I do the same thing but click into another cell it's fine. I've tried commenting out large chunks of code but I can't seem to make it stop.
 
Upvote 0
Without knowing how the code is being called, I couldn't say.
 
Upvote 0
There's a good chance I'm just not understanding everything that's going on in this code and, after some time, I may figure out why I'm having the problem. It's part of workbook that has quite a bit of Event-triggered code so it may take me awhile to track it down.

It is a little frustrating that the error box with "User-defined type not defined" comes up but doesn't focus on the code window, highlighting the problem. It doesn't stop any UserForms from working either which just adds to my confusion...

Once again, thanks all very much! I sincerely appreciate your time!
 
Upvote 0
Well I'm utterly perplexed. After spending hours commenting out code and testing it I finally found the culprit and it doesn't seem to have anything to do with the code provided in this thread. Once I commented this out

Code:
'Sub TableOfContents()
'  Navigate.Show vbModeless
'End Sub

Everything works fine! It was in a standard module and this was in the ThisWorkbook module

Code:
Public Sub Workbook_Open()
  Set App = Application
  Set AppDate = Application
  Application.OnKey "+^{q}", "TableOfContents"
  Navigate.Show vbModeless
  Call AutoDate("WBOpen")
End Sub

It's so strange (to me at least) that I get the "User defined type not defined" because of those first 3 lines and that it would only happen when I changed something and pressed either Enter or Tab. I can live with it. Once more, thanks very much!:)
 
Upvote 0
Try solution without API:
Rich (BB code):

' ZVI:2011_07_24 http://www.mrexcel.com/forum/showthread.php?t=562721
Sub Pic2Comment()

  Const TmpWb$ = "_TempWb.xls"
  Dim Pic As Picture, PicName As String, p As String
  
  Set Pic = ActiveSheet.Pictures(1) ' <-- Set index or name of the picture here
  p = ThisWorkbook.Path

  Application.ScreenUpdating = False
  With Workbooks.Add
    PicName = p & "\_" & Replace(Pic.Name, " ", "_") & ".jpg"
    Pic.Copy
    With .Sheets(1).ChartObjects.Add(0, 0, Pic.Width, Pic.Height).Chart
      .Paste
      .Export Filename:=PicName, FilterName:="JPG"
    End With
    .Close False
  End With

  With ActiveSheet.Cells(1, 1)
    If Not .Comment Is Nothing Then .Comment.Delete
    .AddComment
    With .Comment.Shape
      .Height = Pic.Height
      .Width = Pic.Width
      .Fill.UserPicture PicName
    End With
  End With
  Kill PicName
  Application.ScreenUpdating = True
  
End Sub
Regards,
 
Last edited:
Upvote 0
Please delete this line of the code above: Const TmpWb$ = "_TempWb.xls"
 
Upvote 0

Forum statistics

Threads
1,224,600
Messages
6,179,836
Members
452,947
Latest member
Gerry_F

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top