vbams-accessclipboarddata

How to get the size of the contents of MSForms.DataObject in MS Access VBA


I have a BMP image copied to the clipboard that I will paste into a form object frame control in MS Access (2016).

What I need to know is the memory size that the image takes up on the clipboard (although it would be alright if there was a way to get the size information after it was in the object frame control if necessary). For example, is the image 76,582 bytes or 652 bytes or 942,002 bytes, etc?

Here is the code I use to paste the image from the clipboard into the object frame control:

With Me.imgGrabbedFrame 'Bound Object Frame control
    .Class = "Paintbrush Picture"
    .OLETypeAllowed = acOLEEmbedded
    .Action = acOLEPaste
End With

Is there a way to get the memory taken up by an object on the clipboard?


Solution

  • You can just calculate the length of the value property of the frame. That's including any OLE meta-information.

    LenB(Me.imgGrabbedFrame.Value)
    

    The return value is the length in bytes.

    Calculating it while on the clipboard is possible, but substantially more complicated.

    To do that, first, let us define several functions to work with the clipboard:

    Public Declare PtrSafe Function GetClipboardFormatNameW Lib "User32" (ByVal format As Long, ByVal lpszFormatName As LongPtr, ByVal cchMaxCount As Long) As Long
    Public Declare PtrSafe Function OpenClipboard Lib "User32" (Optional ByVal hWndNewOwner As LongPtr) As Boolean
    Public Declare PtrSafe Function CloseClipboard Lib "User32" () As Boolean
    Public Declare PtrSafe Function EnumClipboardFormats Lib "User32" (ByVal format As Long) As Long
    Public Declare PtrSafe Function CountClipboardFormats Lib "User32" () As Long
    Public Declare PtrSafe Function GlobalSize Lib "Kernel32" (ByVal hMem As LongPtr) As LongPtr
    Public Declare PtrSafe Function GetClipboardData Lib "User32" (ByVal uFormat As Long) As LongPtr
    

    Then, let's define a function to iterate the different formats that are on the clipboard:

    Public Sub ListClipboardFormats()
        Dim l As Long
        Dim format As Long
        Dim b As String
        OpenClipboard
        b = String(255, vbNullChar)
        For l = 1 To CountClipboardFormats
            format = EnumClipboardFormats(format)
            GetClipboardFormatNameW format, StrPtr(b), 255
            If Left(b, 1) = vbNullChar Then
                Debug.Print format
            Else
                Debug.Print b
            End If
            b = String(255, vbNullChar)
        Next
        CloseClipboard
    End Sub
    

    This function will print all available clipboard formats to the immediate window. If it's a built-in format, it will print a number. You can find the list of numbers here. A common number for image data is 8, for CF_DIB, a device-independent bitmap, or 17 for CF_DIBV5, a more modern variant of the same thing.

    If we want to use one of the custom formats, let's define a function to get its number. This function will return 0 in case of an error or a non-existent clipboard format:

    Public Function GetClipboardFormatByName(strName As String) As Long
        Dim format As Long
        Dim b As String
        Dim l As Long
        OpenClipboard
        For l = 1 To CountClipboardFormats
            b = String(255, vbNullChar) 'Initialize string buffer
            format = EnumClipboardFormats(format) 'Get next format
            GetClipboardFormatNameW format, StrPtr(b), 255 'Copy name to buffer
            If Left(b, Len(strName)) = strName Then
                 GetClipboardFormatByName = format
                 Exit Function
            End If
        Next
        CloseClipboard
    End Function
    

    Then, the final function to determine the size of what's on the clipboard if we have its format:

    Public Function GetClipboardLength(ClipboardFormat As Long) As Long
        OpenClipboard
        Dim hClipboardGlobal As LongPtr
        hClipboardGlobal = GetClipboardData(ClipboardFormat)
        If hClipboardGlobal <> 0 Then
            GetClipboardLength = GlobalSize(hClipboardGlobal)
        End If
        CloseClipboard
    End Function
    

    If we know a DIB is on the clipboard, this makes getting it's size as simple as GetClipboardLength(8). Note that this is the size of the DIB, and that might not actually be identical to the size of the data, when pasted, as it's saved, since additional processing might occur.

    A note for size on the clipboard: size on the clipboard might be larger or smaller than the size of what you paste depending on the implementation. Programs can (and often will) put multiple representations of the same thing on the clipboard (text, rich text, image, OLE data object), and can also put it there in such a way it's only copied when accessed. That's why, when closing an Office program after copying a large object, you're often asked if you want to keep that object on the clipboard.