Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,615
Office Version
  1. 2016
Platform
  1. Windows
Hi dear members.

I thought I would post this little project here . Basically, as per the thread title, the code creates beveled round buttons at runtime.

The buttons can be added to the userform or to a frame control.

Behind the scenes, the code creates temporary round shapes in a hidden worksheet (makes use of the enhanced shapes engine that is built-in excel > 2007) , add to them the requested formatting, copies them to the clipboard and then paste them to a parent frame container as a stdpicture.

I have seen similar approaches for adding shapes to userforms before but the resulting shapes were liveless: always flat, not clickable and had no tab support.

I have wrapped the code in two classes CRoundButton and CRoundButtons for easy use. The classes expose intuitive Properties,Methods and a Click event + Tab functionality.

One limitation is that you cannot add the buttons in separate parent containers. In other words, if you decide to add the buttons to the userform, you cannot add some of them to a frame and vice versa. This is due to an annoying bug in the MSForms control .

Workbook Example:
Round3D_Buttons.xlsm


Here is a preview :




1- CRoundButton Class code:
VBA Code:
Option Explicit

Private Enum eBevelTopType
    BevelConvex = MsoBevelType.msoBevelConvex
    BevelHardEdge = MsoBevelType.msoBevelHardEdge
End Enum

Private Type BUTTON_PROPERTIES
    Name               As String
    Parent             As Object
    TabIndex           As Long
    Left               As Single
    Top                As Single
    Width              As Single
    Height             As Single
    Caption            As String
    BackColor          As Long
    FontColor          As Long
    FontBold           As Boolean
    FontName           As String
    FontSize           As Long
End Type

Private Type RGB
    R                  As Byte
    G                  As Byte
    b                  As Byte
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 Type GUID
    Data1               As Long
    Data2               As Integer
    Data3               As Integer
    Data4(0 To 7)       As Byte
End Type

Private Type uPicDesc
    Size                As Long
    Type                As Long
    #If Win64 Then
        hPic            As LongLong
        hPal            As LongLong
    #Else
        hPic            As Long
        hPal            As Long
    #End If
End Type

Private Type BITMAPINFOHEADER
    biSize              As Long
    biWidth             As Long
    biHeight            As Long
    biPlanes            As Integer
    biBitCount          As Integer
    biCompression       As Long
    biSizeImage         As Long
    biXPelsPerMeter     As Long
    biYPelsPerMeter     As Long
    biClrUsed           As Long
    biClrImportant      As Long
End Type


#If VBA7 Then
    Private 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
    Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr
    Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
    Private Declare PtrSafe Function GlobalSize Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
    Private Declare PtrSafe Function GlobalUnlock Lib "kernel32" (ByVal hMem As LongPtr) As Long
    Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleaut32" (PicDesc As Any, RefIID As Any, ByVal fPictureOwnsHandle As Long, iPic As IPicture) As Long
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As LongPtr, ByVal lpszName As String, ByVal lType As Long, ByVal cxWidth As Long, ByVal cyHeight As Long, ByVal fuLoad As Long) As Long
    Private Declare PtrSafe Function GetTempFileName Lib "kernel32" Alias "GetTempFileNameA" (ByVal lpszPath As String, ByVal lpPrefixString As String, ByVal wUnique As Long, ByVal lpTempFileName As String) As Long
    Private Declare PtrSafe Function ApiGetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As LongPtr, ByVal nCount As Long, lpObject As Any) As Long
    Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
    Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUf As LongPtr) As Long
    Private Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hwnd As LongPtr, lpRect As RECT) As Long
    Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDc As LongPtr, ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hDc As LongPtr) As Long
    Private Declare PtrSafe Function TranslateColor Lib "oleAut32.dll" Alias "OleTranslateColor" (ByVal Clr As OLE_COLOR, ByVal palet As LongPtr, col As Long) As Long
    Private Declare PtrSafe Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As LongPtr
    Private Declare PtrSafe Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As LongPtr
    Private Declare PtrSafe Function CombineRgn Lib "gdi32" (ByVal hDestRgn As LongPtr, ByVal hSrcRgn1 As LongPtr, ByVal hSrcRgn2 As LongPtr, ByVal nCombineMode As Long) As Long
    Private Declare PtrSafe Function SetWindowRgn Lib "user32" (ByVal hwnd As LongPtr, ByVal hRgn As LongPtr, ByVal bRedraw As Long) As Long
    Private Declare PtrSafe Function ScreenToClient Lib "user32" (ByVal hwnd As LongPtr, lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function SetRect Lib "user32" (lpRect As RECT, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
    Private Declare PtrSafe Function GetPixel Lib "gdi32" (ByVal hDc As LongPtr, ByVal x As Long, ByVal Y As Long) As Long
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hDc As LongPtr) As LongPtr
    Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hDc As LongPtr) As Long
    Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hDc As LongPtr, ByVal hObject As LongPtr) As LongPtr
    Private Declare PtrSafe Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As LongPtr
    Private Declare PtrSafe Function Arc Lib "gdi32" (ByVal hDc As LongPtr, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal X3 As Long, ByVal Y3 As Long, ByVal X4 As Long, ByVal Y4 As Long) As Long
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As LongPtr) As Long
#Else
    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
    Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function CloseClipboard Lib "user32" () As Long
    Private Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function EmptyClipboard Lib "user32" () As Long
    Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
    Private Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
    Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
    Private Declare Function OleCreatePictureIndirect Lib "oleaut32" (PicDesc As Any, RefIID As Any, ByVal fPictureOwnsHandle As Long, iPic As IPicture) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpszName As String, ByVal lType As Long, ByVal cxWidth As Long, ByVal cyHeight As Long, ByVal fuLoad As Long) As Long
    Private Declare Function GetTempFileName Lib "kernel32" Alias "GetTempFileNameA" (ByVal lpszPath As String, ByVal lpPrefixString As String, ByVal wUnique As Long, ByVal lpTempFileName As String) As Long
    Private Declare Function ApiGetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
    Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUf As Long) As Long
    Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
    Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDc As Long, ByVal nIndex As Long) As Long
    Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDc As Long) As Long
    Private Declare Function TranslateColor Lib "oleAut32.dll" Alias "OleTranslateColor" (ByVal Clr As OLE_COLOR, ByVal palet As Long, col As Long) As Long
    Private Declare Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
    Private Declare Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
    Private Declare Function CombineRgn Lib "gdi32" (ByVal hDestRgn As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long) As Long
    Private Declare Function SetWindowRgn Lib "user32" (ByVal hwnd As Long, ByVal hRgn As Long, ByVal bRedraw As Long) As Long
    Private Declare Function ScreenToClient Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long
    Private Declare Function SetRect Lib "user32" (lpRect As RECT, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
    Private Declare Function GetPixel Lib "gdi32" (ByVal hDc As Long, ByVal x As Long, ByVal Y As Long) As Long
    Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare Function CreateCompatibleDC Lib "gdi32" (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 hObject As Long) As Long
    Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long
    Private Declare Function Arc Lib "gdi32" (ByVal hDc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal X3 As Long, ByVal Y3 As Long, ByVal X4 As Long, ByVal Y4 As Long) As Long
    Private Declare Function IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As Long) As Long
#End If

Public Event Click()
Private WithEvents oFrame As MSForms.Frame

Private tButtonProperties As BUTTON_PROPERTIES
Private IButtonPressedPic As stdole.IPicture, IButtonRelasedPic As stdole.IPicture
Private IButtonActiveRelasedPic As stdole.IPicture

Private Const HIDDEN_SHAPES_HOLDER_SHEET = "TempDrawingSheet" '<< Change hidden sheet name as required.




'_______________________________________ Props And Methods ____________________________________________

Public Property Get Parent() As Object
    Set Parent = tButtonProperties.Parent
End Property

Public Property Get Name() As String
    Name = tButtonProperties.Name
End Property

Public Property Get TabIndex() As Long
    TabIndex = tButtonProperties.TabIndex
End Property

Public Property Let TabIndex(ByVal vNewValue As Long)
    tButtonProperties.TabIndex = vNewValue
End Property

Public Property Get FontName() As String
    FontName = tButtonProperties.FontName
End Property

Public Property Let FontName(ByVal vNewValue As String)
    tButtonProperties.FontName = vNewValue
End Property

Public Property Get Left() As Single
    Left = tButtonProperties.Left
End Property

Public Property Get Top() As Single
    Top = tButtonProperties.Top
End Property

Public Property Get Width() As Single
    Width = tButtonProperties.Width
End Property

Public Property Get Height() As Single
    Height = tButtonProperties.Height
End Property

Public Property Get Caption() As String
    Caption = tButtonProperties.Caption
End Property

Public Property Let Caption(ByVal vNewValue As String)
    tButtonProperties.Caption = vNewValue
End Property

Public Property Get FontColor() As Long
    FontColor = tButtonProperties.FontColor
End Property

Public Property Let FontColor(ByVal vNewValue As Long)
    tButtonProperties.FontColor = vNewValue
End Property

Public Property Get FontSize() As Long
    FontSize = tButtonProperties.FontSize
End Property

Public Property Let FontSize(ByVal vNewValue As Long)
    tButtonProperties.FontSize = vNewValue
End Property

Public Property Get FontBold() As Boolean
    FontBold = tButtonProperties.FontBold
End Property

Public Property Let FontBold(ByVal vNewValue As Boolean)
    tButtonProperties.FontBold = vNewValue
End Property

Public Property Get BackColor() As Long
    BackColor = tButtonProperties.BackColor
End Property

Public Property Let BackColor(ByVal vNewValue As Long)
    Dim lNewColor As Long
    Call TranslateColor(vNewValue, 0, lNewColor)
    tButtonProperties.BackColor = lNewColor
End Property

Public Property Get FrameContainer() As MSForms.Frame
    Set FrameContainer = oFrame
End Property

Public Sub Init( _
    ByVal Name As String, _
    ByVal Parent As Object, _
    ByVal Left As Single, _
    ByVal Top As Single, _
    ByVal Width As Single, _
    ByVal Height As Single _
    )
      
    With tButtonProperties
        .Name = Name
        Set .Parent = Parent
        .Left = Left
        .Top = Top
        .Width = Width
        .Height = Height
    End With
  
    Set oFrame = Parent.Controls.Add("Forms.Frame.1")
    SetControlEvents(oFrame) = True
  
    With oFrame
        .Left = Left: .Top = Top
        .Width = Width: .Height = Height
        .BorderStyle = fmBorderStyleSingle
        .BorderStyle = fmBorderStyleNone
        .Caption = ""
    End With

End Sub

Public Sub ShowButton()
    If Not oFrame Is Nothing Then
        oFrame.TabIndex = tButtonProperties.TabIndex
        'Button Pressed.
        Call CreateAndCopyShape(BevelHardEdge, False)
        Call DrawOnClipboardDib(BevelHardEdge, False)
        'Button released and active.
        Call CreateAndCopyShape(BevelConvex, True)
        Call DrawOnClipboardDib(BevelConvex, True)
        'Button released and not active.
        Call CreateAndCopyShape(BevelConvex, False)
        Call DrawOnClipboardDib(BevelConvex, False)
    End If
End Sub



'_____________________________________________ Button Events ______________________________________________________

Private Sub oFrame_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    If IsCursorOverButton Then
        Set oFrame.Picture = IButtonPressedPic
    End If
End Sub


Private Sub oFrame_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    If IsCursorOverButton Then
        Set oFrame.Picture = IButtonActiveRelasedPic
    End If
End Sub

Private Sub oFrame_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    If KeyCode = VBA.vbKeyReturn Then
        RaiseEvent Click
    End If
End Sub

Private Sub oFrame_Click()
    If IsCursorOverButton Then
        RaiseEvent Click
    End If
End Sub

Public Sub OnEnter_DO_NOT_USE()
    'Attribute OnEnter_DO_NOT_USE.VB_UserMemId = &H80018202
    Set oFrame.Picture = IButtonActiveRelasedPic
End Sub

Public Sub OnExit_DO_NOT_USE(ByVal Cancel As MSForms.ReturnBoolean)
    'Attribute OnExit_DO_NOT_USE.VB_UserMemId = &H80018203
    Set oFrame.Picture = IButtonRelasedPic
End Sub



'___________________________________________ Helper Routines __________________________________________________

Private Property Let SetControlEvents(ByVal Frame As MSForms.Frame, ByVal SetEvents As Boolean)
    Const S_OK = &H0
    Dim tIID As GUID
    Static lCookie As Long
  
    Set oFrame = Frame
    If IIDFromString(StrPtr("{00020400-0000-0000-C000-000000000046}"), tIID) = S_OK Then
        Call ConnectToConnectionPoint(Me, tIID, SetEvents, Frame, lCookie)
        If lCookie Then
            Debug.Print "Connection set for: " & Frame.Name
        Else
            Debug.Print "Connection failed for: " & Frame.Name
        End If
    End If
End Property

Private Sub DrawOnClipboardDib(ByVal BevelType As eBevelTopType, Optional ByVal bActive As Boolean)
 
    #If Win64 Then
        Dim hFrame As LongLong
        Dim hDib As LongLong, hPrevDIB As LongLong
        Dim hPen As LongLong, hPrevPen As LongLong
        Dim hDc As LongLong, hMemDc As LongLong
    #Else
        Dim hFrame As Long
        Dim hDib As Long, hPrevDIB As Long
        Dim hPen As Long, hPrevPen As Long
        Dim hDc As Long, hMemDc As Long
    #End If
  
    Const CF_DIB = 8
    Const PS_DOT = 2
  
    Dim tBMIH As BITMAPINFOHEADER
    Dim tRoundRect As RECT
    Dim p1 As POINTAPI, p2 As POINTAPI
    Dim bDIBData() As Byte
    Dim lWidth As Long, lHeight As Long
  
    If Not GetClipData(CF_DIB, bDIBData) Then
'        MsgBox "Unable to get DIB data from clipboard."
        Exit Sub
    End If
  
    hDib = GetDIBHandle(bDIBData)
    Call ApiGetObject(hDib, LenB(tBMIH), tBMIH)
    lWidth = tBMIH.biWidth: lHeight = Abs(tBMIH.biHeight)
  
    If BevelType = BevelHardEdge Or (BevelType = BevelConvex And bActive) Then
        hDc = GetDC(0)
        hMemDc = CreateCompatibleDC(hDc)
        hPrevDIB = SelectObject(hMemDc, hDib)
        Call SetRect(tRoundRect, PTtoPX(Me.Left, False), PTtoPX(Me.Top, True), _
            PTtoPX((Me.Width + Me.Left), False), PTtoPX((Me.Height + Me.Top), True))
        Call IUnknown_GetWindow(oFrame, VarPtr(hFrame))
        With tRoundRect
            hPen = CreatePen(PS_DOT, 1, vbBlack)
            hPrevPen = SelectObject(hMemDc, hPen)
            Call Arc(hMemDc, 18, 18, lWidth - 18, lHeight - 18, 0, 0, 0, 0)
        End With
        Set IButtonActiveRelasedPic = CreateIPicture(hDib, BevelType)
        Set oFrame.Picture = CreateIPicture(hDib, BevelType)
        Call SelectObject(hMemDc, hPrevDIB)
        Call SelectObject(hMemDc, hPrevPen)
        Call DeleteObject(hPen)
        Call DeleteDC(hMemDc)
        Call ReleaseDC(0, hDc)
    Else
        Set oFrame.Picture = CreateIPicture(hDib, BevelType)
    End If
    Call DeleteObject(hDib)

End Sub

Private Function GetClipData(ByVal lFormat As Long, baData() As Byte) As Boolean

    #If Win64 Then
        Dim hMem  As LongLong
        Dim lPtr  As LongLong
        Dim lSize As LongLong
    #Else
        Dim hMem   As Long
        Dim lPtr   As Long
        Dim lSize  As Long
    #End If
  
    If IsClipboardFormatAvailable(lFormat) = 0 Then
'        MsgBox "CF_DIB Format not found in the clipboard."
        Exit Function
    End If
    If OpenClipboard(0) = 0 Then
        MsgBox "Unable to open the clipboard."
        Exit Function
    End If
    hMem = GetClipboardData(lFormat)
    lPtr = GlobalLock(hMem)
    lSize = GlobalSize(hMem)
    If lSize > 0 Then
        ReDim baData(0 To CLng(lSize) - 1) As Byte
        Call CopyMemory(baData(0), ByVal lPtr, lSize)
    Else
        baData = vbNullString
    End If
    Call GlobalUnlock(hMem)
    Call CloseClipboard
    GetClipData = True
  
End Function

#If Win64 Then
    Private Function GetDIBHandle(baData() As Byte) As LongLong
#Else
    Private Function GetDIBHandle(baData() As Byte) As Long
#End If

    Const IMAGE_BITMAP = 0
    Const LR_LOADFROMFILE = &H10
    Const LR_CREATEDIBSECTION = &H2000
    Dim baHeader() As Byte
    Dim sFile As String
  
    sFile = String$(1000, 0)
    Call GetTempFileName(Environ$("TEMP"), "test", 0, sFile)
    sFile = Left$(sFile, InStr(sFile, vbNullChar) - 1)
    ReDim baHeader(0 To 13) As Byte
    Call CopyMemory(baHeader(0), &H4D42, 2) '--- "BM"
    Call CopyMemory(baHeader(2), UBound(baHeader) + 1 + UBound(baData) + 1, 8)
    WriteBinaryFile sFile, baHeader, baData
    GetDIBHandle = LoadImage(0, sFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE Or LR_CREATEDIBSECTION)
    Kill sFile
  
End Function
'
Private Sub WriteBinaryFile(sFile As String, baHeader() As Byte, baBuffer() As Byte)
    Dim nFile   As Integer

    nFile = FreeFile
    Open sFile For Binary Access Write Shared As nFile
    If UBound(baHeader) >= 0 Then
        Put nFile, , baHeader
    End If
    If UBound(baBuffer) >= 0 Then
        Put nFile, , baBuffer
    End If
    Close nFile
End Sub

#If Win64 Then
    Private Function CreateIPicture( _
        ByVal hDib As LongLong, _
        ByVal BevelType As eBevelTopType _
    ) As IPicture
        Dim hBmp As LongLong
#Else
    Private Function CreateIPicture( _
        ByVal hDib As Long, _
        ByVal BevelType As eBevelTopType _
    ) As IPicture
        Dim hBmp As Long
#End If

    Const IMAGE_BITMAP = 0
    Const LR_COPYDELETEORG = &H8
    Const PICTYPE_BITMAP = 1
    Const S_OK = &H0&

    Dim IID_IDispatch As GUID, uPicInfo As uPicDesc
    Dim iPic As IPicture
    Dim lRet As Long

    hBmp = CopyImage(hDib, IMAGE_BITMAP, 0, 0, LR_COPYDELETEORG)
    Call DeleteObject(hDib)

    If hBmp Then
        With IID_IDispatch
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        With uPicInfo
            .Size = Len(uPicInfo)
            .Type = PICTYPE_BITMAP
            .hPic = hBmp
            .hPal = 0
        End With
        lRet = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, iPic)
        If lRet = S_OK Then
            Set CreateIPicture = iPic
            If BevelType = msoBevelHardEdge Then
                Set IButtonPressedPic = iPic
            Else
                Set IButtonRelasedPic = iPic
            End If
        End If
    End If
 
End Function

Private Function SheetsExists(ByVal Sh As Worksheet) As Boolean
    Dim oHiddenSheet As Worksheet
    On Error Resume Next
        Set oHiddenSheet = Sh
        SheetsExists = Not CBool(oHiddenSheet Is Nothing)
    On Error GoTo 0
End Function

Private Sub CreateAndCopyShape(ByVal BevelType As eBevelTopType, Optional ByVal bSelected As Boolean)

    Const BEVEL_INSERT = 10 '<< Change as required.
    Const BEVEL_DEPTH = 6  '<< Change as required.
    Const RGN_AND = 1
  
    #If Win64 Then
        Dim hFrame As LongLong, hRectRgn As LongLong, hEllipRgn As LongLong
    #Else
        Dim hFrame As Long, hRectRgn As Long, hEllipRgn As Long
    #End If
  
    Dim tFrameRect As RECT, p1 As POINTAPI, p2 As POINTAPI
    Dim oShape As Shape
    Dim tRGB As RGB
    Dim oHiddenSheet As Worksheet
    Dim bWorkbookProtected As Boolean

    On Error Resume Next
        If Sheets(HIDDEN_SHAPES_HOLDER_SHEET) Is Nothing Then
            Set oHiddenSheet = Sheets.Add(After:=Sheets(Sheets.Count))
            oHiddenSheet.Visible = xlSheetVeryHidden
            oHiddenSheet.Name = HIDDEN_SHAPES_HOLDER_SHEET
        End If
        If Not SheetsExists(Sheets(HIDDEN_SHAPES_HOLDER_SHEET)) Then
            bWorkbookProtected = ThisWorkbook.ProtectStructure
            MsgBox "Adding the [" & _
            HIDDEN_SHAPES_HOLDER_SHEET & "] sheet failed !" & vbNewLine & vbNewLine & _
            IIf(bWorkbookProtected, "Unprotect and try again.", ""), vbCritical, "Error."
            End
        End If
    On Error GoTo 0

    With Me
        Set oShape = Sheets(HIDDEN_SHAPES_HOLDER_SHEET).Shapes.AddShape _
            (msoShapeOval, .Left, .Top, .Width - 6, .Height - 6)
    End With
  
    With oShape.Fill
        tRGB = ColorToRGB(Me.BackColor)
        .ForeColor.RGB = RGB(tRGB.R, tRGB.G, tRGB.b)
        .Solid
    End With
  
    With oShape.ThreeD
        If BevelType = BevelConvex Then
            .BevelTopType = msoBevelConvex
            .BevelTopDepth = BEVEL_DEPTH
            If bSelected Then
                .BevelTopDepth = 100
                .LightAngle = 100
            End If
        Else
            .BevelTopType = msoBevelHardEdge
            .BevelTopDepth = BEVEL_DEPTH
        End If
        If Me.Width <= 50 Or Me.Height <= 50 Then
            .BevelTopInset = 6
        Else
            .BevelTopInset = BEVEL_INSERT
        End If
    End With
  
    With oShape.TextFrame2
        .VerticalAnchor = msoAnchorMiddle
        .TextRange.Characters.Text = Me.Caption
        With .TextRange.Characters.ParagraphFormat
            .FirstLineIndent = 0
            .Alignment = msoAlignCenter
        End With
    End With
  
    With oShape.TextFrame2.TextRange.Font
        .Bold = IIf(Me.FontBold, msoTrue, msoFalse)
        .Fill.Visible = msoTrue
        tRGB = ColorToRGB(Me.FontColor)
        .Fill.ForeColor.RGB = RGB(tRGB.R, tRGB.G, tRGB.b)
        .Fill.Transparency = 0
        .Fill.Solid
         .Size = IIf(Me.FontSize = 0, 11, Me.FontSize)
        .Name = IIf(Len(Me.FontName) = 0, "Calibri", Me.FontName)
    End With
  
    With oShape
        On Error Resume Next
            .CopyPicture xlScreen, xlBitmap
            DoEvents
            .CopyPicture xlScreen, xlBitmap
            .Delete
        On Error GoTo 0
    End With
  
    Call IUnknown_GetWindow(oFrame, VarPtr(hFrame))
    Call GetWindowRect(hFrame, tFrameRect)
  
    With tFrameRect
        p1.x = .Left + 3
        p1.Y = .Top + 3
        p2.x = .Right - 3
        p2.Y = .Bottom - 3
    End With
  
    Call ScreenToClient(hFrame, p1)
    Call ScreenToClient(hFrame, p2)
  
    hRectRgn = CreateRectRgn(p1.x, p1.Y, p2.x, p2.Y)
    hEllipRgn = CreateEllipticRgn(p1.x, p1.Y, p2.x, p2.Y)
    Call CombineRgn(hEllipRgn, hEllipRgn, hRectRgn, RGN_AND)
    Call SetWindowRgn(hFrame, hEllipRgn, True)
  
    Call DeleteObject(hRectRgn)
    Call DeleteObject(hEllipRgn)

End Sub

Private Function IsCursorOverButton() As Boolean
    #If Win64 Then
        Dim hFrame As LongLong, hDc As LongLong
    #Else
        Dim hFrame As Long, hDc As Long
    #End If

    Dim tCurPos As POINTAPI, p As POINTAPI
    Dim lNewColor As Long

    Call IUnknown_GetWindow(oFrame, VarPtr(hFrame))
    hDc = GetDC(hFrame)
    Call GetCursorPos(tCurPos)
    p.x = tCurPos.x
    p.Y = tCurPos.Y
    Call ScreenToClient(hFrame, p)
    Call TranslateColor(oFrame.Parent.BackColor, 0, lNewColor)
    If GetPixel(hDc, p.x, p.Y) <> lNewColor Then
        IsCursorOverButton = True
    End If
    Call ReleaseDC(hFrame, hDc)

End Function

Private Function ScreenDPI(ByVal bVert As Boolean) As Long
    Const LOGPIXELSX As Long = 88
    Const LOGPIXELSY As Long = 90
    Static lDPI(1), hDc

    If lDPI(0) = 0 Then
        hDc = GetDC(0)
        lDPI(0) = GetDeviceCaps(hDc, LOGPIXELSX)
        lDPI(1) = GetDeviceCaps(hDc, LOGPIXELSY)
        hDc = ReleaseDC(0, hDc)
    End If
    ScreenDPI = lDPI(Abs(bVert))
End Function

Private Function PTtoPX(Points As Single, bVert As Boolean) As Single
    Const POINTS_PER_INCH = 72
    PTtoPX = (Points * ScreenDPI(bVert) / POINTS_PER_INCH) * tButtonProperties.Parent.Zoom / 100
End Function

Private Function ColorToRGB(ByVal col As Long) As RGB
    ColorToRGB.R = &HFF& And col
    ColorToRGB.G = (&HFF00& And col) \ 256
    ColorToRGB.b = (&HFF0000 And col) \ 65536
End Function


2- CRoundButtons Collection Class Code:
VBA Code:
Option Explicit

Private oButtonsCol As Collection
Private oParentsCol As Collection
Private oParent  As Object

Public Function Add( _
    ByVal Name As String, _
    ByVal ParentContainer As Object, _
    ByVal Left As Single, _
    ByVal Top As Single, _
    ByVal Width As Single, _
    ByVal Height As Single _
) As CRoundButton

    Dim oButton As CRoundButton
    Set oParent = ParentContainer

    If IsButtonOffScreen(Left, Top, Width, Height) Then
        Err.Raise Number:=vbObjectError + 513, _
            Description:="[" & Name & "] is fully or partially outside of its container !!" & _
            vbNewLine & vbNewLine & _
            "Please, adjust the button location\dimensions so that it fits entirely inside its parent container."
    End If
  
    If oButtonsCol Is Nothing Then
        Set oButtonsCol = New Collection
        Set oParentsCol = New Collection
    End If
  
    On Error Resume Next
    oParentsCol.Add ParentContainer, ParentContainer.Name
    If Err.Number = 0 And oParentsCol.Count > 1 Then
        Err.Clear
        'Due to an annoying BUG in the MSForms controls, we cannot nest
        'frames & multpages @runtime within diff frames & multpages.
        MsgBox "All Buttons MUST have a common Parent container." & vbNewLine & vbNewLine & _
            "Set the Parent of all the buttons to a shared container control " & _
            "(such as a Frame) or place them directly on the userform.", vbCritical, "Error!"
        End
    End If
    On Error GoTo 0
  
    Set oButton = New CRoundButton
    oButtonsCol.Add oButton, Name
  
    Set Add = oButton
    Call oButton.Init( _
            Name, _
            ParentContainer, _
            Left, _
            Top, _
            Width, _
            Height _
        )

End Function

Public Sub Remove(ByVal Index As Variant)
    oParent.Controls.Remove oButtonsCol(Index).FrameContainer.Name
    oButtonsCol.Remove Index
End Sub

Public Property Get Item(ByVal Index As Variant) As CRoundButton
    Set Item = oButtonsCol(Index)
End Property

Public Property Get Count() As Long
    Count = oButtonsCol.Count
End Property


Private Function IsButtonOffScreen( _
    ByVal Left As Single, _
    ByVal Top As Single, _
    ByVal Width As Single, _
    ByVal Height As Single _
) As Boolean

    If Left < 0 Or Top < 0 Or Width + Left > oParent.InsideWidth _
        Or Height + Top > oParent.InsideHeight Then
            IsButtonOffScreen = True
    End If
End Function

Private Sub Class_Terminate()
    Dim i As Long
  
    For i = Count To 1 Step -1
        oButtonsCol.Remove oButtonsCol(i).Name
    Next
    Set oButtonsCol = Nothing
End Sub


3- Usage example in a UserForm Module:
VBA Code:
Option Explicit

Private RoundButtons As New CRoundButtons

Private WithEvents Button1 As CRoundButton
Private WithEvents Button2 As CRoundButton
Private WithEvents Button3 As CRoundButton


Private Sub UserForm_Initialize()

    Set RoundButtons = New CRoundButtons
  
    Set Button1 = RoundButtons.Add("Button1", Me.Frame1, 10, 10, 80, 80)
    With Button1
        .Caption = "A"
        .BackColor = 4648156
        .FontColor = vbRed
        .FontBold = 0
        .FontSize = 40
        .TabIndex = 0
        .ShowButton
    End With
  
    Set Button2 = RoundButtons.Add("Button2", Me.Frame1, 110, 10, 80, 80)
    With Button2
        .Caption = "B"
        .BackColor = vbWhite
        .FontSize = 40
        .TabIndex = 1
        .ShowButton
    End With
    ''
    Set Button3 = RoundButtons.Add("Button3", Me.Frame1, 210, 10, 80, 80)
    With Button3
        .Caption = "C"
        .BackColor = 15327561
        .FontColor = vbYellow
        .FontSize = 40
        .TabIndex = 2
        .ShowButton
    End With

End Sub


Private Sub Button1_Click()
    MsgBox "You Clicked [" & Button1.Name & "]"
End Sub

Private Sub Button2_Click()
    MsgBox "You Clicked [" & Button2.Name & "]"
End Sub

Private Sub Button3_Click()
    MsgBox "You Clicked [" & Button3.Name & "]"
End Sub


Private Sub RemoveButtons_Click()
    Dim i As Long
    For i = RoundButtons.Count To 1 Step -1
        RoundButtons.Remove i
    Next
End Sub

Regards.
 
Hi

Excel 365 Beta,
I changed the code without getting the expected result
 

Attachments

  • Immagine.jpg
    Immagine.jpg
    159.2 KB · Views: 20
Upvote 0

Excel Facts

Quick Sum
Select a range of cells. The total appears in bottom right of Excel screen. Right-click total to add Max, Min, Count, Average.
So I just tried it, and your instincts were absolutely correct re: the cause and the workaround.
It didn't show all the buttons with the revised code you proposed, but I took your method and forced it to take extra time with a PAUSE routine:

VBA Code:
Sub Pause(Period As Single)
    Dim T As Single
    T = Timer
    Do
        DoEvents
    Loop Until T + Period < Timer
End Sub

I first tried PAUSE 2, and that worked perfectly (though it took a while to start). Then I tried shorter periods - PAUSE 1 was fine as was PAUSE 0.5. One of the buttons didn't appear at PAUSE 0.2, but I've tried a dozen times at PAUSE 0.3, and they all appear and work perfectly:

VBA Code:
    With oShape
        On Error Resume Next
            Do
                Pause 0.3
                Err.Clear
                DoEvents
                .CopyPicture xlScreen, xlBitmap
            Loop Until Err.Number = 0
        On Error GoTo 0
        .Delete
    End With

I think this is more a reflection of how slow my system is, more than anything!
I haven't worked my way through the code yet, but one thing I've been experimenting with is the Window Image Acquisition COM object, and using it to convert Base64 into stdPicture objects. You could avoid having to use the clipboard with that approach, but I guess it's not exactly speedy either or completely 'runtime' generated.
 
Upvote 0
So I just tried it, and your instincts were absolutely correct re: the cause and the workaround.
It didn't show all the buttons with the revised code you proposed, but I took your method and forced it to take extra time with a PAUSE routine:

VBA Code:
Sub Pause(Period As Single)
    Dim T As Single
    T = Timer
    Do
        DoEvents
    Loop Until T + Period < Timer
End Sub

I first tried PAUSE 2, and that worked perfectly (though it took a while to start). Then I tried shorter periods - PAUSE 1 was fine as was PAUSE 0.5. One of the buttons didn't appear at PAUSE 0.2, but I've tried a dozen times at PAUSE 0.3, and they all appear and work perfectly:

VBA Code:
    With oShape
        On Error Resume Next
            Do
                Pause 0.3
                Err.Clear
                DoEvents
                .CopyPicture xlScreen, xlBitmap
            Loop Until Err.Number = 0
        On Error GoTo 0
        .Delete
    End With

I think this is more a reflection of how slow my system is, more than anything!
I haven't worked my way through the code yet, but one thing I've been experimenting with is the Window Image Acquisition COM object, and using it to convert Base64 into stdPicture objects. You could avoid having to use the clipboard with that approach, but I guess it's not exactly speedy either or completely 'runtime' generated.
Thanks Dan.

I have added your Pause routine to the project in case it is needed. On the machine I am using, the Pause is not needed. In fact, it makes the display of the buttons too slow even whent set at 0.1.

Regards.
 
Upvote 0
@Tom.Jones
@ISY

Thanks guys for testing and finding out about the white background issue.

For some reason, SetWindowRgn is not working for you. I am not exactly sure what the reason is.

In order to remedy the above problem, I have decided to take a slightly different route by painting the DIB pixels that are outside the button elliptic region with the parent background color. That way, we won't need to clip the buttons frame container using SetWindowRgn.

Using this new method, we can't have any overlapping buttons otherwise the background will show up on top of the button that is underneath. But I guess that is not a big issue since we shouldn't place buttons on top of other buttons.

Below is a link to a new workbook with the new code. Please, try it and let me know what the outcome is... If it now works as expected, I will post the new code here for future reference.
Round3D_Buttons_V2.xlsm

Regards.
 
Upvote 0
@Tom.Jones
@ISY

Thanks guys for testing and finding out about the white background issue.

For some reason, SetWindowRgn is not working for you. I am not exactly sure what the reason is.

In order to remedy the above problem, I have decided to take a slightly different route by painting the DIB pixels that are outside the button elliptic region with the parent background color. That way, we won't need to clip the buttons frame container using SetWindowRgn.

Using this new method, we can't have any overlapping buttons otherwise the background will show up on top of the button that is underneath. But I guess that is not a big issue since we shouldn't place buttons on top of other buttons.

Below is a link to a new workbook with the new code. Please, try it and let me know what the outcome is... If it now works as expected, I will post the new code here for future reference.
Round3D_Buttons_V2.xlsm

Regards.
Now is perfect. Thanks
 
Upvote 0
Thank you guys for testing and confirming that the new version is now working.

Round3D_Buttons_V2.xlsm


Here is the new code for future reference in case the workbook link gets broken:

1- CRoundButton Classe code:
VBA Code:
Option Explicit

Private Enum eBevelTopType
    BevelConvex = MsoBevelType.msoBevelConvex
    BevelHardEdge = MsoBevelType.msoBevelHardEdge
End Enum

Private Type BUTTON_PROPERTIES
    Name               As String
    Parent             As Object
    TabIndex           As Long
    Left               As Single
    Top                As Single
    Width              As Single
    Height             As Single
    Caption            As String
    BackColor          As Long
    FontColor          As Long
    FontBold           As Boolean
    FontName           As String
    FontSize           As Long
End Type

Private Type RGB
    R                  As Byte
    G                  As Byte
    b                  As Byte
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 Type GUID
    Data1               As Long
    Data2               As Integer
    Data3               As Integer
    Data4(0 To 7)       As Byte
End Type

Private Type uPicDesc
    Size                As Long
    Type                As Long
    #If Win64 Then
        hPic            As LongLong
        hPal            As LongLong
    #Else
        hPic            As Long
        hPal            As Long
    #End If
End Type

Private Type BITMAPINFOHEADER
    biSize              As Long
    biWidth             As Long
    biHeight            As Long
    biPlanes            As Integer
    biBitCount          As Integer
    biCompression       As Long
    biSizeImage         As Long
    biXPelsPerMeter     As Long
    biYPelsPerMeter     As Long
    biClrUsed           As Long
    biClrImportant      As Long
End Type


Private Type SAFEARRAYBOUND
    cElements           As Long
    lLbound             As Long
End Type

Private Type SAFEARRAY
    cDims               As Integer
    fFeatures           As Integer
    cbElements          As Long
    cLocks              As Long
    #If Win64 Then
        pvData          As LongLong
    #Else
        pvData          As Long
    #End If
    Bounds(0 To 3)      As SAFEARRAYBOUND
End Type


#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function ArrPtr Lib "VBE7" Alias "VarPtr" (var() As Any) As LongLong
    #Else
        Private Declare PtrSafe Function ArrPtr Lib "VBE7" Alias "VarPtr" (var() As Any) As Long
    #End If
    Private 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
    Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr
    Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
    Private Declare PtrSafe Function GlobalSize Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
    Private Declare PtrSafe Function GlobalUnlock Lib "kernel32" (ByVal hMem As LongPtr) As Long
    Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleaut32" (PicDesc As Any, RefIID As Any, ByVal fPictureOwnsHandle As Long, iPic As IPicture) As Long
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As LongPtr, ByVal lpszName As String, ByVal lType As Long, ByVal cxWidth As Long, ByVal cyHeight As Long, ByVal fuLoad As Long) As Long
    Private Declare PtrSafe Function GetTempFileName Lib "kernel32" Alias "GetTempFileNameA" (ByVal lpszPath As String, ByVal lpPrefixString As String, ByVal wUnique As Long, ByVal lpTempFileName As String) As Long
    Private Declare PtrSafe Function ApiGetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As LongPtr, ByVal nCount As Long, lpObject As Any) As Long
    Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
    Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUf As LongPtr) As Long
    Private Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hwnd As LongPtr, lpRect As RECT) As Long
    Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDc As LongPtr, ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hDc As LongPtr) As Long
    Private Declare PtrSafe Function TranslateColor Lib "oleAut32.dll" Alias "OleTranslateColor" (ByVal Clr As OLE_COLOR, ByVal palet As LongPtr, col As Long) As Long
    Private Declare PtrSafe Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As LongPtr
    Private Declare PtrSafe Function PtInRegion Lib "gdi32" (ByVal hRgn As LongPtr, ByVal X As Long, ByVal Y As Long) As Long
    Private Declare PtrSafe Function ScreenToClient Lib "user32" (ByVal hwnd As LongPtr, lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function SetRect Lib "user32" (lpRect As RECT, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
    Private Declare PtrSafe Function GetPixel Lib "gdi32" (ByVal hDc As LongPtr, ByVal X As Long, ByVal Y As Long) As Long
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hDc As LongPtr) As LongPtr
    Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hDc As LongPtr) As Long
    Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hDc As LongPtr, ByVal hObject As LongPtr) As LongPtr
    Private Declare PtrSafe Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As LongPtr
    Private Declare PtrSafe Function Arc Lib "gdi32" (ByVal hDc As LongPtr, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal X3 As Long, ByVal Y3 As Long, ByVal X4 As Long, ByVal Y4 As Long) As Long
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As LongPtr) As Long
#Else
    Private Declare Function ArrPtr Lib "VBE6" Alias "VarPtr" (var() As Any) As Long
    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
    Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function CloseClipboard Lib "user32" () As Long
    Private Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function EmptyClipboard Lib "user32" () As Long
    Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
    Private Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
    Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
    Private Declare Function OleCreatePictureIndirect Lib "oleaut32" (PicDesc As Any, RefIID As Any, ByVal fPictureOwnsHandle As Long, iPic As IPicture) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpszName As String, ByVal lType As Long, ByVal cxWidth As Long, ByVal cyHeight As Long, ByVal fuLoad As Long) As Long
    Private Declare Function GetTempFileName Lib "kernel32" Alias "GetTempFileNameA" (ByVal lpszPath As String, ByVal lpPrefixString As String, ByVal wUnique As Long, ByVal lpTempFileName As String) As Long
    Private Declare Function ApiGetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
    Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUf As Long) As Long
    Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
    Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDc As Long, ByVal nIndex As Long) As Long
    Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDc As Long) As Long
    Private Declare Function TranslateColor Lib "oleAut32.dll" Alias "OleTranslateColor" (ByVal Clr As OLE_COLOR, ByVal palet As Long, col As Long) As Long
    Private Declare Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
    Private Declare Function PtInRegion Lib "gdi32" (ByVal hRgn As Long, ByVal X As Long, ByVal Y As Long) As Long
    Private Declare Function ScreenToClient Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long
    Private Declare Function SetRect Lib "user32" (lpRect As RECT, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
    Private Declare Function GetPixel Lib "gdi32" (ByVal hDc As Long, ByVal X As Long, ByVal Y As Long) As Long
    Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare Function CreateCompatibleDC Lib "gdi32" (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 hObject As Long) As Long
    Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long
    Private Declare Function Arc Lib "gdi32" (ByVal hDc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal X3 As Long, ByVal Y3 As Long, ByVal X4 As Long, ByVal Y4 As Long) As Long
    Private Declare Function IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As Long) As Long
#End If

Public Event Click()
Private WithEvents oFrame As MSForms.Frame

Private tButtonProperties As BUTTON_PROPERTIES
Private IButtonPressedPic As stdole.IPicture, IButtonRelasedPic As stdole.IPicture
Private IButtonActiveRelasedPic As stdole.IPicture

Private Const HIDDEN_SHAPES_HOLDER_SHEET = "TempDrawingSheet" '<< Change hidden sheet name as required.




'_______________________________________ Props And Methods ____________________________________________

Public Property Get Parent() As Object
    Set Parent = tButtonProperties.Parent
End Property

Public Property Get Name() As String
    Name = tButtonProperties.Name
End Property

Public Property Get TabIndex() As Long
    TabIndex = tButtonProperties.TabIndex
End Property

Public Property Let TabIndex(ByVal vNewValue As Long)
    tButtonProperties.TabIndex = vNewValue
End Property

Public Property Get FontName() As String
    FontName = tButtonProperties.FontName
End Property

Public Property Let FontName(ByVal vNewValue As String)
    tButtonProperties.FontName = vNewValue
End Property

Public Property Get Left() As Single
    Left = tButtonProperties.Left
End Property

Public Property Get Top() As Single
    Top = tButtonProperties.Top
End Property

Public Property Get Width() As Single
    Width = tButtonProperties.Width
End Property

Public Property Get Height() As Single
    Height = tButtonProperties.Height
End Property

Public Property Get Caption() As String
    Caption = tButtonProperties.Caption
End Property

Public Property Let Caption(ByVal vNewValue As String)
    tButtonProperties.Caption = vNewValue
End Property

Public Property Get FontColor() As Long
    FontColor = tButtonProperties.FontColor
End Property

Public Property Let FontColor(ByVal vNewValue As Long)
    tButtonProperties.FontColor = vNewValue
End Property

Public Property Get FontSize() As Long
    FontSize = tButtonProperties.FontSize
End Property

Public Property Let FontSize(ByVal vNewValue As Long)
    tButtonProperties.FontSize = vNewValue
End Property

Public Property Get FontBold() As Boolean
    FontBold = tButtonProperties.FontBold
End Property

Public Property Let FontBold(ByVal vNewValue As Boolean)
    tButtonProperties.FontBold = vNewValue
End Property

Public Property Get BackColor() As Long
    BackColor = tButtonProperties.BackColor
End Property

Public Property Let BackColor(ByVal vNewValue As Long)
    Dim lNewColor As Long
    Call TranslateColor(vNewValue, 0, lNewColor)
    tButtonProperties.BackColor = lNewColor
End Property

Public Property Get FrameContainer() As MSForms.Frame
    Set FrameContainer = oFrame
End Property

Public Sub Init( _
    ByVal Name As String, _
    ByVal Parent As Object, _
    ByVal Left As Single, _
    ByVal Top As Single, _
    ByVal Width As Single, _
    ByVal Height As Single _
    )
        
    With tButtonProperties
        .Name = Name
        Set .Parent = Parent
        .Left = Left
        .Top = Top
        .Width = Width
        .Height = Height
    End With

    Set oFrame = Parent.Controls.Add("Forms.Frame.1")
    SetControlEvents(oFrame) = True
    With oFrame
        .Left = Left: .Top = Top
        .Width = Width: .Height = Height
        .BorderStyle = fmBorderStyleSingle
        .BorderStyle = fmBorderStyleNone
        .Caption = ""
    End With

End Sub

Public Sub ShowButton()
    If Not oFrame Is Nothing Then
        oFrame.TabIndex = tButtonProperties.TabIndex
        Call CreateAndCopyShape(BevelHardEdge, False)
        Call DrawOnClipboardDib(BevelHardEdge, False)
        Call CreateAndCopyShape(BevelConvex, True)
        Call DrawOnClipboardDib(BevelConvex, True)
        Call CreateAndCopyShape(BevelConvex, False)
        Call DrawOnClipboardDib(BevelConvex, False)
    End If
End Sub



'_____________________________________________ Button Events ______________________________________________________

Private Sub oFrame_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    If IsCursorOverButton Then
        Set oFrame.Picture = IButtonPressedPic
    End If
End Sub


Private Sub oFrame_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    If IsCursorOverButton Then
        Set oFrame.Picture = IButtonActiveRelasedPic
    End If
End Sub

Private Sub oFrame_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    If KeyCode = VBA.vbKeyReturn Then
        RaiseEvent Click
    End If
End Sub

Private Sub oFrame_Click()
    If IsCursorOverButton Then
        RaiseEvent Click
    End If
End Sub

Public Sub OnEnter_DO_NOT_USE()
    'Attribute OnEnter_DO_NOT_USE.VB_UserMemId = &H80018202
    Set oFrame.Picture = IButtonActiveRelasedPic
End Sub

Public Sub OnExit_DO_NOT_USE(ByVal Cancel As MSForms.ReturnBoolean)
    'Attribute OnExit_DO_NOT_USE.VB_UserMemId = &H80018203
    Set oFrame.Picture = IButtonRelasedPic
End Sub



'___________________________________________ Helper Routines __________________________________________________

Private Property Let SetControlEvents(ByVal Frame As MSForms.Frame, ByVal SetEvents As Boolean)
    Const S_OK = &H0
    Dim tIID As GUID
    Static lCookie As Long
    
    Set oFrame = Frame
    If IIDFromString(StrPtr("{00020400-0000-0000-C000-000000000046}"), tIID) = S_OK Then
        Call ConnectToConnectionPoint(Me, tIID, SetEvents, Frame, lCookie)
        If lCookie Then
            Debug.Print "Connection set for: " & Frame.Name
        Else
            Debug.Print "Connection failed for: " & Frame.Name
        End If
    End If
End Property


Private Sub DrawOnClipboardDib(ByVal BevelType As eBevelTopType, Optional ByVal bActive As Boolean)
 
    Const CF_DIB = 8
    Const PS_DOT = 2

    #If Win64 Then
        Dim hFrame As LongLong
        Dim hDib As LongLong, hPrevDIB As LongLong, lpBits  As LongLong
        Dim hPen As LongLong, hPrevPen As LongLong
        Dim hDc As LongLong, hMemDc As LongLong
        Dim hEllipRgn As LongLong
    #Else
        Dim hFrame As Long
        Dim hDib As Long, hPrevDIB As Long, lpBits  As Long
        Dim hPen As Long, hPrevPen As Long
        Dim hDc As Long, hMemDc As Long
        Dim hEllipRgn As Long
    #End If

    Dim baData()    As Byte
    Dim lWidth      As Long
    Dim lHeight     As Long
    Dim aBuffer()   As Long
    Dim uArray      As SAFEARRAY
    Dim lX          As Long
    Dim lY          As Long
    Dim lRealColor  As Long
    Dim tRoundRect  As RECT
    
    If Not GetClipData(CF_DIB, baData) Then
        Debug.Print "Unable to get DIB data from clipboard."
        Exit Sub
    End If
    
    hDib = GetDIBHandle(baData)
    lpBits = GetDIBPointer(hDib)
    If pvGetDibDimension(hDib, lWidth, lHeight) Then
    
        hEllipRgn = CreateEllipticRgn(0, 0, lWidth, lHeight)
        pvInitOverlayArray aBuffer, uArray, lpBits, lWidth, lHeight
        Call TranslateColor(oFrame.Parent.BackColor, 0, lRealColor)
        
        For lY = 0 To lHeight - 1
            For lX = 0 To lWidth - 1
                If PtInRegion(hEllipRgn, lX, lY) = 0 Then
                    aBuffer(lX, lY) = BGRtoRGB(lRealColor)
                End If
            Next
        Next

        Call DeleteObject(hEllipRgn)
          
        If BevelType = BevelHardEdge Or (BevelType = BevelConvex And bActive) Then
            hDc = GetDC(0)
            hMemDc = CreateCompatibleDC(hDc)
            hPrevDIB = SelectObject(hMemDc, hDib)
            Call SetRect(tRoundRect, PTtoPX(Me.Left, False), PTtoPX(Me.Top, True), _
            PTtoPX((Me.Width + Me.Left), False), PTtoPX((Me.Height + Me.Top), True))
            Call IUnknown_GetWindow(oFrame, VarPtr(hFrame))
            With tRoundRect
                hPen = CreatePen(PS_DOT, 1, vbBlack)
                hPrevPen = SelectObject(hMemDc, hPen)
                Call Arc(hMemDc, 18, 18, lWidth - 18, lHeight - 18, 0, 0, 0, 0)
            End With
            Set IButtonActiveRelasedPic = CreateIPicture(hDib, BevelType)
            Set oFrame.Picture = CreateIPicture(hDib, BevelType)
            Call SelectObject(hMemDc, hPrevDIB)
            Call SelectObject(hMemDc, hPrevPen)
            Call DeleteObject(hPen)
            Call DeleteDC(hMemDc)
            Call ReleaseDC(0, hDc)
        Else
            Set oFrame.Picture = CreateIPicture(hDib, BevelType)
        End If
        Call DeleteObject(hDib)
   End If

End Sub

Private Function GetClipData(ByVal lFormat As Long, baData() As Byte) As Boolean

    #If Win64 Then
        Dim hMem  As LongLong
        Dim lPtr  As LongLong
        Dim lSize As LongLong
    #Else
        Dim hMem   As Long
        Dim lPtr   As Long
        Dim lSize  As Long
    #End If
    
    If IsClipboardFormatAvailable(lFormat) = 0 Then
        Debug.Print "CF_DIB Format not found in the clipboard."
        Exit Function
    End If
    If OpenClipboard(0) = 0 Then
        Debug.Print "Unable to open the clipboard."
        Exit Function
    End If
    hMem = GetClipboardData(lFormat)
    lPtr = GlobalLock(hMem)
    lSize = GlobalSize(hMem)
    If lSize > 0 Then
        ReDim baData(0 To CLng(lSize) - 1) As Byte
        Call CopyMemory(baData(0), ByVal lPtr, lSize)
    Else
        baData = vbNullString
    End If
    Call GlobalUnlock(hMem)
    Call CloseClipboard
    GetClipData = True
    
End Function

#If Win64 Then
    Private Function GetDIBHandle(baData() As Byte) As LongLong
#Else
    Private Function GetDIBHandle(baData() As Byte) As Long
#End If

    Const IMAGE_BITMAP = 0
    Const LR_LOADFROMFILE = &H10
    Const LR_CREATEDIBSECTION = &H2000
    Dim baHeader() As Byte
    Dim sFile As String
    
    sFile = String$(1000, 0)
    Call GetTempFileName(Environ$("TEMP"), "test", 0, sFile)
    sFile = Left$(sFile, InStr(sFile, vbNullChar) - 1)
    ReDim baHeader(0 To 13) As Byte
    Call CopyMemory(baHeader(0), &H4D42, 2) '--- "BM"
    Call CopyMemory(baHeader(2), UBound(baHeader) + 1 + UBound(baData) + 1, 8)
    WriteBinaryFile sFile, baHeader, baData
    GetDIBHandle = LoadImage(0, sFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE Or LR_CREATEDIBSECTION)
    Kill sFile
    
End Function
'
Private Sub WriteBinaryFile(sFile As String, baHeader() As Byte, baBuffer() As Byte)
    Dim nFile   As Integer

    nFile = FreeFile
    Open sFile For Binary Access Write Shared As nFile
    If UBound(baHeader) >= 0 Then
        Put nFile, , baHeader
    End If
    If UBound(baBuffer) >= 0 Then
        Put nFile, , baBuffer
    End If
    Close nFile
End Sub

#If Win64 Then
    Private Function CreateIPicture( _
        ByVal hDib As LongLong, _
        ByVal BevelType As eBevelTopType _
    ) As IPicture
        Dim hBmp As LongLong
#Else
    Private Function CreateIPicture( _
        ByVal hDib As Long, _
        ByVal BevelType As eBevelTopType _
    ) As IPicture
        Dim hBmp As Long
#End If

    Const IMAGE_BITMAP = 0
    Const LR_COPYDELETEORG = &H8
    Const PICTYPE_BITMAP = 1
    Const S_OK = &H0&

    Dim IID_IDispatch As GUID, uPicInfo As uPicDesc
    Dim iPic As IPicture
    Dim lRet As Long

    hBmp = CopyImage(hDib, IMAGE_BITMAP, 0, 0, LR_COPYDELETEORG)
    Call DeleteObject(hDib)

    If hBmp Then
        With IID_IDispatch
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        With uPicInfo
            .Size = Len(uPicInfo)
            .Type = PICTYPE_BITMAP
            .hPic = hBmp
            .hPal = 0
        End With
        lRet = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, iPic)
        If lRet = S_OK Then
            Set CreateIPicture = iPic
            If BevelType = msoBevelHardEdge Then
                Set IButtonPressedPic = iPic
            Else
                Set IButtonRelasedPic = iPic
            End If
        End If
    End If
 
End Function

Private Sub CreateAndCopyShape(ByVal BevelType As eBevelTopType, Optional ByVal bSelected As Boolean)

    Const BEVEL_INSERT = 10 '<< Change as required.
    Const BEVEL_DEPTH = 6  '<< Change as required.
    
    Dim oShape As Shape
    Dim tRGB As RGB
    Dim oHiddenSheet As Worksheet
    Dim bWorkbookProtected As Boolean

    On Error Resume Next
        If Sheets(HIDDEN_SHAPES_HOLDER_SHEET) Is Nothing Then
            Set oHiddenSheet = Sheets.Add(After:=Sheets(Sheets.Count))
            oHiddenSheet.Visible = xlSheetVeryHidden
            oHiddenSheet.Name = HIDDEN_SHAPES_HOLDER_SHEET
        End If
        If Not SheetsExists(Sheets(HIDDEN_SHAPES_HOLDER_SHEET)) Then
            bWorkbookProtected = ThisWorkbook.ProtectStructure
            MsgBox "Adding the [" & _
            HIDDEN_SHAPES_HOLDER_SHEET & "] sheet failed !" & vbNewLine & vbNewLine & _
            IIf(bWorkbookProtected, "Unprotect and try again.", ""), vbCritical, "Error."
            End
        End If
    On Error GoTo 0

    With Me
        Set oShape = Sheets(HIDDEN_SHAPES_HOLDER_SHEET).Shapes.AddShape _
            (msoShapeOval, .Left, .Top, .Width - 6, .Height - 6)
    End With
    
    With oShape.Fill
        tRGB = ColorToRGB(Me.BackColor)
        .ForeColor.RGB = RGB(tRGB.R, tRGB.G, tRGB.b)
        .Solid
    End With
    
    With oShape.ThreeD
        If BevelType = BevelConvex Then
            .BevelTopType = msoBevelConvex
            .BevelTopDepth = BEVEL_DEPTH
            If bSelected Then
                .BevelTopDepth = 100
                .LightAngle = 100
            End If
        Else
            .BevelTopType = msoBevelHardEdge
            .BevelTopDepth = BEVEL_DEPTH
        End If
        If Me.Width <= 50 Or Me.Height <= 50 Then
            .BevelTopInset = 6
        Else
            .BevelTopInset = BEVEL_INSERT
        End If
    End With
    
    With oShape.TextFrame2
        .VerticalAnchor = msoAnchorMiddle
        .TextRange.Characters.Text = Me.Caption
        With .TextRange.Characters.ParagraphFormat
            .FirstLineIndent = 0
            .Alignment = msoAlignCenter
        End With
    End With
    
    With oShape.TextFrame2.TextRange.Font
        .Bold = IIf(Me.FontBold, msoTrue, msoFalse)
        .Fill.Visible = msoTrue
        tRGB = ColorToRGB(Me.FontColor)
        .Fill.ForeColor.RGB = RGB(tRGB.R, tRGB.G, tRGB.b)
        .Fill.Transparency = 0
        .Fill.Solid
         .Size = IIf(Me.FontSize = 0, 11, Me.FontSize)
        .Name = IIf(Len(Me.FontName) = 0, "Calibri", Me.FontName)
    End With
 
    With oShape
        On Error Resume Next
            Do
                'Call Pause(0.3) '<< may need to add a small delay. Change as required.
                Err.Clear
                DoEvents
                .CopyPicture xlScreen, xlBitmap
            Loop Until Err.Number = 0
        On Error GoTo 0
        .Delete
    End With

End Sub

Private Sub Pause(Period As Single)
    Dim T As Single
    T = Timer
    Do
        DoEvents
    Loop Until T + Period < Timer
End Sub

#If Win64 Then
    Private Function pvGetDibDimension( _
        ByVal hDib As LongLong, _
        lWidth As Long, _
        lHeight As Long _
    ) As Boolean
#Else
    Private Function pvGetDibDimension( _
        ByVal hDib As Long, _
        lWidth As Long, _
        lHeight As Long _
    ) As Boolean
#End If

    Dim uHdr  As BITMAPINFOHEADER
    
    Call ApiGetObject(hDib, LenB(uHdr), uHdr)
    If uHdr.biWidth = 0 Or uHdr.biHeight = 0 Then
        GoTo QH
    End If
    lWidth = uHdr.biWidth
    lHeight = Abs(uHdr.biHeight)
    '--- success
    pvGetDibDimension = True
QH:
End Function


#If Win64 Then
    Private Function GetDIBPointer(ByVal hDib As LongLong) As LongLong
        Const sizeof_DIBSECTION As Long = 104
        Const offsetof_bmBits As Long = 24
#Else
    Private Function GetDIBPointer(ByVal hDib As Long) As Long
        Const sizeof_DIBSECTION As Long = 84
        Const offsetof_bmBits As Long = 20
#End If

    'Credit for this Sub goes to 'wqweto' @ www.vbforums.com
    'https://www.vbforums.com/showthread.php?890248-Read-Bitmap-into-a-2D-array&p=5508041&viewfull=1#post5508041
    Dim baBuffer(0 To sizeof_DIBSECTION - 1) As Byte
    
    Call ApiGetObject(hDib, UBound(baBuffer) + 1, baBuffer(0))
    Call CopyMemory(GetDIBPointer, baBuffer(offsetof_bmBits), 8)
End Function

#If Win64 Then
    Private Sub pvInitOverlayArray( _
        aBuffer() As Long, _
        uArray As SAFEARRAY, _
        ByVal lDataPtr As LongLong, _
        ParamArray Bounds() As Variant)
#Else
    Private Sub pvInitOverlayArray( _
        aBuffer() As Long, _
        uArray As SAFEARRAY, _
        ByVal lDataPtr As Long, _
        ParamArray Bounds() As Variant)
#End If

    #If Win64 Then
        Const PTR_SIZE = 8
    #Else
        Const PTR_SIZE = 4
    #End If

    'Credit for this Sub goes to 'wqweto' @ www.vbforums.com
    'https://www.vbforums.com/showthread.php?890248-Read-Bitmap-into-a-2D-array&p=5508041&viewfull=1#post5508041
    Dim lIdx As Long
    
    Debug.Assert UBound(Bounds) <= UBound(uArray.Bounds)
    With uArray
        .cDims = UBound(Bounds) + 1
        .fFeatures = 1 ' FADF_AUTO
        .cbElements = 4 ' sizeof COLORQUAD
        .cLocks = 1
        .pvData = lDataPtr
        If .cDims = 0 Then
            .cDims = 1
            .Bounds(0).cElements = &H40000000
        Else
            For lIdx = 0 To UBound(Bounds)
                .Bounds(lIdx).cElements = Bounds(UBound(Bounds) - lIdx)
            Next
        End If
    End With
    Call CopyMemory(ByVal ArrPtr(aBuffer), VarPtr(uArray), PTR_SIZE)
    
End Sub

Private Function IsCursorOverButton() As Boolean
    #If Win64 Then
        Dim hFrame As LongLong, hDc As LongLong
    #Else
        Dim hFrame As Long, hDc As Long
    #End If

    Dim tCurPos As POINTAPI, p As POINTAPI
    Dim lNewColor As Long

    Call IUnknown_GetWindow(oFrame, VarPtr(hFrame))
    hDc = GetDC(hFrame)
    Call GetCursorPos(tCurPos)
    p.X = tCurPos.X
    p.Y = tCurPos.Y
    Call ScreenToClient(hFrame, p)
    Call TranslateColor(oFrame.Parent.BackColor, 0, lNewColor)
    If GetPixel(hDc, p.X, p.Y) <> lNewColor Then
        IsCursorOverButton = True
    End If
    Call ReleaseDC(hFrame, hDc)

End Function

Private Function ScreenDPI(ByVal bVert As Boolean) As Long
    Const LOGPIXELSX As Long = 88
    Const LOGPIXELSY As Long = 90
    Static lDPI(1), hDc

    If lDPI(0) = 0 Then
        hDc = GetDC(0)
        lDPI(0) = GetDeviceCaps(hDc, LOGPIXELSX)
        lDPI(1) = GetDeviceCaps(hDc, LOGPIXELSY)
        hDc = ReleaseDC(0, hDc)
    End If
    ScreenDPI = lDPI(Abs(bVert))
End Function

Private Function PTtoPX(Points As Single, bVert As Boolean) As Single
    Const POINTS_PER_INCH = 72
    PTtoPX = (Points * ScreenDPI(bVert) / POINTS_PER_INCH) * tButtonProperties.Parent.Zoom / 100
End Function

Private Function ColorToRGB(ByVal col As Long) As RGB
    ColorToRGB.R = &HFF& And col
    ColorToRGB.G = (&HFF00& And col) \ 256
    ColorToRGB.b = (&HFF0000 And col) \ 65536
End Function

Private Function BGRtoRGB(ByVal c As Long) As Long
  BGRtoRGB = (c And &HFF&) * &H10000 Or (c And &HFF00&) Or (c And &HFF0000) \ &H10000
End Function

Private Function SheetsExists(ByVal Sh As Worksheet) As Boolean
    Dim oHiddenSheet As Worksheet
    On Error Resume Next
        Set oHiddenSheet = Sh
        SheetsExists = Not CBool(oHiddenSheet Is Nothing)
    On Error GoTo 0
End Function


2- CRoundButtons Class code:
VBA Code:
Option Explicit

Private oButtonsCol As Collection
Private oParentsCol As Collection
Private oParent  As Object

Public Function Add( _
    ByVal Name As String, _
    ByVal ParentContainer As Object, _
    ByVal Left As Single, _
    ByVal Top As Single, _
    ByVal Width As Single, _
    ByVal Height As Single _
) As CRoundButton

    Dim oButton As CRoundButton
    Set oParent = ParentContainer
    
    If oButtonsCol Is Nothing Then
        Set oButtonsCol = New Collection
        Set oParentsCol = New Collection
    End If
    
    On Error Resume Next
        oParentsCol.Add ParentContainer, ParentContainer.Name
        If Err.Number = 0 And oParentsCol.Count > 1 Then
            Err.Clear
            'Due to an annoying BUG in the MSForms controls, we cannot nest
            'frames & multpages @runtime within diff frames & multpages.
            MsgBox "All Buttons MUST have a common Parent container." & vbNewLine & vbNewLine & _
                "Set the Parent of all the buttons to a shared container control " & _
                "(such as a Frame) or place them directly on the userform.", vbCritical, "Error!"
            End
        End If
    On Error GoTo 0
    
    Set oButton = New CRoundButton
    oButtonsCol.Add oButton, Name
    
    Set Add = oButton
    Call oButton.Init( _
            Name, _
            ParentContainer, _
            Left, _
            Top, _
            Width, _
            Height _
        )

End Function

Public Sub Remove(ByVal Index As Variant)
    oParent.Controls.Remove oButtonsCol(Index).FrameContainer.Name
    oButtonsCol.Remove Index
End Sub

Public Property Get Item(ByVal Index As Variant) As CRoundButton
    Set Item = oButtonsCol(Index)
End Property

Public Property Get Count() As Long
    Count = oButtonsCol.Count
End Property


Private Sub Class_Terminate()
    Dim i As Long
    
    For i = Count To 1 Step -1
        oButtonsCol.Remove oButtonsCol(i).Name
    Next
    Set oButtonsCol = Nothing
End Sub

UserForm(s) code implementations stay the same.

Regards.
 
Upvote 0

Forum statistics

Threads
1,214,868
Messages
6,122,005
Members
449,059
Latest member
mtsheetz

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