Option Explicit
#If Win64 Then
Private Const NULL_PTR = 0^
Private Const PTR_LEN = 8&
#Else
Private Const NULL_PTR = 0&
Private Const PTR_LEN = 4&
#End If
Private Enum RoleSystems
ROLE_SYSTEM_LISTITEM = 34&
ROLE_SYSTEM_PUSHBUTTON = 43&
ROLE_SYSTEM_COMBOBOX = 46&
End Enum
Private Enum TreeScope
TreeScope_None = 0&
TreeScope_Element = 1&
TreeScope_Children = 2&
TreeScope_Descendants = 4&
TreeScope_Subtree = ((TreeScope_Element + TreeScope_Children) + TreeScope_Descendants)
End Enum
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0& To 7&) As Byte
End Type
#If VBA7 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
Private Declare PtrSafe Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As LongPtr) As Long
Private Declare PtrSafe Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
Private Declare PtrSafe Sub SetLastError Lib "kernel32" (ByVal dwErrCode As Long)
Private Declare PtrSafe Function StringFromGUID2 Lib "ole32" (ByRef rguid As GUID, ByVal lpsz As LongPtr, ByVal cchMax As Long) As Long
Private Declare PtrSafe Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As GUID) As Long
Private Declare PtrSafe Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr
Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hhk As LongPtr) As Long
Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal nCode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
Private Declare PtrSafe Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
Private Declare PtrSafe Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr
Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
#Else
Private Enum LongPtr
[_]
End Enum
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As LongPtr) As Long
Private Declare Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
Private Declare Sub SetLastError Lib "kernel32" (ByVal dwErrCode As Long)
Private Declare Function StringFromGUID2 Lib "ole32" (ByRef rguid As GUID, ByVal lpsz As LongPtr, ByVal cchMax As Long) As Long
Private Declare Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As GUID) As Long
Private Declare Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr
Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hhk As LongPtr) As Long
Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal nCode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
Private Declare Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr
Private Declare Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
#End If
Private arThemes() As String
Private sTheme As String
Private bFindAndApplyThemesRet As Boolean
Private bRetrievingThemes As Boolean
' ******************************************************************
' This code is language sensitive !!!
' Languages supported: (English - Spanish - French)
' Constants for supported languages:
Private Const LANG_OFFICE_THEME_LABEL = "OFFICE THEME: TEMA DE OFFICE: THÈME OFFICE:"
Private Const LANG_OK_BUTTON = "OK ACEPTAR"
Private Const LANG_CANCEL_BUTTON = "CANCEL CANCELAR ANNULER"
' ******************************************************************
Function GetAvailableOfficeThemes() As String()
Const WH_CBT = 5&
Dim lHook As LongPtr
If OfficeThemesExist = False Then GoTo WrongOfficeVersion
bRetrievingThemes = True
Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
lHook = SetWindowsHookEx(WH_CBT, AddressOf HookProc, NULL_PTR, GetCurrentThreadId)
Call SetProp(Application.hwnd, "Hook", lHook)
Application.CommandBars.ExecuteMso ("ApplicationOptionsDialog")
Call RemoveProp(Application.hwnd, "Hook")
bRetrievingThemes = False
GetAvailableOfficeThemes = arThemes
Erase arThemes
Exit Function
WrongOfficeVersion:
MsgBox "Office themes are only availble in editions greater than 2007.", vbCritical, "Error"
End Function
Function ApplyOfficeTheme(ByVal ThemeName As String) As Boolean
Const WH_CBT = 5&
Dim lHook As LongPtr
If OfficeThemesExist = False Then GoTo WrongOfficeVersion
sTheme = ThemeName
bFindAndApplyThemesRet = False
Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
lHook = SetWindowsHookEx(WH_CBT, AddressOf HookProc, NULL_PTR, GetCurrentThreadId)
Call SetProp(Application.hwnd, "Hook", lHook)
Application.CommandBars.ExecuteMso ("ApplicationOptionsDialog")
Call RemoveProp(Application.hwnd, "Hook")
sTheme = ""
ApplyOfficeTheme = bFindAndApplyThemesRet 'FindAndApplyThemes
Exit Function
WrongOfficeVersion:
MsgBox "Office themes are only availble in editions greater than 2007.", vbCritical, "Error"
End Function
' _____________________________________ PRIVATE ROUTINES _____________________________________________
Private Function HookProc( _
ByVal idHook As Long, _
ByVal wParam As LongPtr, _
ByVal lParam As LongPtr _
) As LongPtr
Const HC_ACTION = 0&, HCBT_CREATEWND = 3&, HCBT_ACTIVATE = 5&, GW_CHILD = 5&
Dim sBuff As String * 256&, lRet As Long
If idHook < HC_ACTION Then
HookProc = CallNextHookEx(GetProp(Application.hwnd, "Hook"), idHook, wParam, lParam)
Exit Function
End If
If idHook = HCBT_CREATEWND Then
Call SetTimer(Application.hwnd, NULL_PTR, 1000&, AddressOf UnHookJustInCase)
End If
If idHook = HCBT_ACTIVATE Then
lRet = GetClassName(wParam, sBuff, 256&)
If Left(sBuff, lRet) = "NUIDialog" Then
Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
Call FindAndApplyThemes(GetNextWindow(wParam, GW_CHILD), TreeScope_Subtree)
End If
End If
End Function
Private Sub UnHookJustInCase()
On Error Resume Next
Call KillTimer(Application.hwnd, NULL_PTR)
Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
Call RemoveProp(Application.hwnd, "Hook")
End Sub
Private Sub FindAndApplyThemes( _
ByVal hwnd As LongPtr, _
Optional ByVal Scope As TreeScope = TreeScope_Subtree _
)
Const UIA_LegacyIAccessiblePatternId = 10018&
Const IID_IUIAutomationLegacyIAccessiblePattern = "{828055ad-355b-4435-86d5-3b51c14a9b1b}"
Const SELFLAG_TAKEFOCUS = 1&, SELFLAG_TAKESELECTION = 2&
Dim iidCuiAuto As GUID
Dim lIndex As Long, lElementsCount As Long
Dim j As Long, k As Long, lSelectionFlag As Long
Dim lRet As Long, vTblOffset As Long, vFuncOrdinal As Long
Dim sName As String, sValue As String, lRole As Long, lChildCount As Long
Dim pAutomation As LongPtr, pPattern As LongPtr, pElement As LongPtr
Dim pElementArray As LongPtr, pChildrenArray As LongPtr, pTrueCond As LongPtr
Dim pChildElement As LongPtr
Dim pLegacyName As LongPtr, pValue As LongPtr
Dim pOk As LongPtr, pCancel As LongPtr, pThemeCombo As LongPtr
pAutomation = GetAutomationPtr
If pAutomation = NULL_PTR Then MsgBox "Automation failed.": Exit Sub
vFuncOrdinal = 6& ' IUIAutomation::ElementFromHandle
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pAutomation, vTblOffset, vbLong, hwnd, VarPtr(pElement))
vFuncOrdinal = 21& ' IUIAutomation::CreateTrueCondition
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pAutomation, vTblOffset, vbLong, VarPtr(pTrueCond))
vFuncOrdinal = 6& ' IUIAutomationElement::FindAll
vTblOffset = vFuncOrdinal * PTR_LEN ' IUIAutomationElement::FindAll
lRet = vtblStdCall(pElement, vTblOffset, vbLong, Scope, pTrueCond, VarPtr(pElementArray))
vFuncOrdinal = 3& ' IUIAutomationElementArray :get_Length
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pElementArray, vTblOffset, vbLong, VarPtr(lElementsCount))
If lElementsCount = 0& Then
GoTo Release_Interfaces
End If
lRet = CLSIDFromString(StrPtr(IID_IUIAutomationLegacyIAccessiblePattern), iidCuiAuto)
Call DispGUID(iidCuiAuto)
For lIndex = 0& To lElementsCount - 1&
vFuncOrdinal = 4& ' IUIAutomationElementArray :GetElement
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pElementArray, vTblOffset, vbLong, lIndex, VarPtr(pElement))
vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pElement, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern))
If pPattern <> NULL_PTR Then
'Retrieve descendants properties.
vFuncOrdinal = 7& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentName
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pLegacyName))
sName = GetStrFromPtrW(pLegacyName)
vFuncOrdinal = 10& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentRole
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(lRole))
' lRl = lRole
vFuncOrdinal = 8& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentValue
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pValue))
sValue = GetStrFromPtrW(pValue)
Select Case True
Case InStr(LANG_OK_BUTTON, UCase(sName)) And lRole = ROLE_SYSTEM_PUSHBUTTON
pOk = pElement
Case InStr(LANG_CANCEL_BUTTON, UCase(sName)) And lRole = ROLE_SYSTEM_PUSHBUTTON
pCancel = pElement
Case InStr(LANG_OFFICE_THEME_LABEL, UCase(sName)) And lRole = ROLE_SYSTEM_COMBOBOX
pThemeCombo = pElement
End Select
End If
vFuncOrdinal = 2& 'IUIAutomationElement::Release
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pElement, vTblOffset, vbLong)
Next lIndex
vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pThemeCombo, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern))
vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong)
'Retrieve ChildCount.
vFuncOrdinal = 6& ' IUIAutomationElement::FindAll
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pThemeCombo, vTblOffset, vbLong, TreeScope_Subtree, pTrueCond, VarPtr(pChildrenArray))
vFuncOrdinal = 3& ' IUIAutomationElementArray :get_Length
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong, VarPtr(lChildCount))
If lChildCount = 0& Then
GoTo Release_Interfaces
End If
For j = 0& To lChildCount - 1&
vFuncOrdinal = 4& ' IUIAutomationElementArray :GetElement
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong, j, VarPtr(pChildElement))
vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pChildElement, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern))
vFuncOrdinal = 7& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentName
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pValue))
sValue = GetStrFromPtrW(pValue)
vFuncOrdinal = 10& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentRole
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(lRole))
' lRl = lRole
If lRole = ROLE_SYSTEM_LISTITEM And bRetrievingThemes Then
ReDim Preserve arThemes(k)
arThemes(k) = sValue: k = k + 1&
End If
If lRole = ROLE_SYSTEM_LISTITEM And bRetrievingThemes = False Then
If UCase(sValue) = UCase(sTheme) Then
lSelectionFlag = SELFLAG_TAKEFOCUS + SELFLAG_TAKESELECTION
vFuncOrdinal = 3& 'IUIAutomationLegacyIAccessiblePattern::Select
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong, lSelectionFlag)
vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pOk, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern))
vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong)
bFindAndApplyThemesRet = True '< set global flag to indicate success
GoTo Release_Interfaces
End If
End If
vFuncOrdinal = 2& 'IUIAutomationLegacyIAccessiblePattern::Release
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong)
vFuncOrdinal = 2& 'IUIAutomationElement::Release
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pChildElement, vTblOffset, vbLong) 'IUIAutomationElementArray ::Release
Next j
vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pCancel, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern))
vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction
vTblOffset = vFuncOrdinal * PTR_LEN
lRet = vtblStdCall(pPattern, vTblOffset, vbLong)
Release_Interfaces:
vFuncOrdinal = 2& 'Release
vTblOffset = vFuncOrdinal * PTR_LEN
If pAutomation Then
lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong) 'IUIAutomationElementArray::Release
lRet = vtblStdCall(pTrueCond, vTblOffset, vbLong) 'IUIAutomationCondition ::Release
lRet = vtblStdCall(pElementArray, vTblOffset, vbLong) 'IUIAutomationElementArray ::Release
lRet = vtblStdCall(pAutomation, vTblOffset, vbLong) 'IUIAutomation::Release
End If
End Sub
Private Function GetAutomationPtr() As LongPtr
Dim pAutomation As LongPtr
Const IID_CUIAUTOMATION = "{FF48DBA4-60EF-4201-AA87-54103EEF594E}"
Const IID_IUIAUTOMATION = "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}"
Const CLSCTX_INPROC_SERVER = &H1, CC_STDCALL = 4&, S_OK = 0&
Dim iidCuiAuto As GUID, iidIuiAuto As GUID, lRet As Long
lRet = CLSIDFromString(StrPtr(IID_CUIAUTOMATION), iidCuiAuto)
Call DispGUID(iidCuiAuto)
lRet = CLSIDFromString(StrPtr(IID_IUIAUTOMATION), iidIuiAuto)
Call DispGUID(iidIuiAuto)
lRet = CoCreateInstance(iidCuiAuto, NULL_PTR, CLSCTX_INPROC_SERVER, iidIuiAuto, pAutomation)
If lRet = S_OK Then GetAutomationPtr = pAutomation
End Function
Private Function vtblStdCall( _
ByVal InterfacePointer As LongPtr, _
ByVal VTableOffset As Long, _
ByVal FunctionReturnType As Long, _
ParamArray FunctionParameters() As Variant _
) As Variant
Const CC_STDCALL = 4&
Dim vParamPtr() As LongPtr
Dim pIndex As Long, pCount As Long
Dim vParamType() As Integer
Dim vRtn As Variant, vParams() As Variant
vParams() = FunctionParameters()
pCount = Abs(UBound(vParams) - LBound(vParams) + 1&)
If pCount = 0& Then
ReDim vParamPtr(0& To 0&)
ReDim vParamType(0& To 0&)
Else
ReDim vParamPtr(0& To pCount - 1&)
ReDim vParamType(0& To pCount - 1&)
For pIndex = 0& To pCount - 1&
vParamPtr(pIndex) = VarPtr(vParams(pIndex))
vParamType(pIndex) = VarType(vParams(pIndex))
Next
End If
pIndex = DispCallFunc( _
InterfacePointer, VTableOffset, CC_STDCALL, FunctionReturnType, _
pCount, vParamType(0&), vParamPtr(0&), vRtn)
If pIndex = 0& Then
vtblStdCall = vRtn
Else
Call SetLastError(pIndex)
End If
End Function
Private Sub DispGUID(objGuid As GUID)
Dim lRet As Long, sTmp As String, buf(100&) As Byte
lRet = StringFromGUID2(objGuid, VarPtr(buf(0&)), UBound(buf) - 1&)
sTmp = buf
End Sub
Private Function GetStrFromPtrW(ByVal lpString As LongPtr) As String
Dim lLength As Long, sBuffer As String
lLength = lstrlen(lpString)
sBuffer = Space$(lLength)
Call CopyMemory(ByVal StrPtr(sBuffer), ByVal lpString, lLength * 2&)
GetStrFromPtrW = sBuffer
End Function
Private Function OfficeThemesExist() As Boolean
OfficeThemesExist = CBool(Val(Application.Version) >= 12&) ' office>=2007
End Function
Option Explicit
Private Sub UserForm_Initialize()
Dim arThemes() As String
arThemes = GetAvailableOfficeThemes
If Not Not arThemes Then
ComboBox1.List = arThemes
ComboBox1.ListIndex = 0&
End If
End Sub
Private Sub CommandButton1_Click()
If ApplyOfficeTheme(ComboBox1.Value) Then
'success
Else
'Failure
End If
End Sub
This is great, just what I was seeking.Hi @tiredofit
I seem to have managed to retrieve/change/apply the office themes via code using UIAutomation and legacy Accessibility. No extrenal references are required to be set in the vba project. Just plain, plug and play vba.
Since this uses UI automation, the code is language-sensitive. The code as is, should work with English, Spanish and French editions of office only.
To make this work with other languages, you will need to change the global constants declared at the top of the api module. I have placed the constants there for easier future editing.
File Demo:
OfficeThemes_VBA.xlsm
Tested in (xl 2013 x32) and (xl 2016 x64 ). I hope it works in Office 365.
1- API code in a bas Module:
VBA Code:Option Explicit #If Win64 Then Private Const NULL_PTR = 0^ Private Const PTR_LEN = 8& #Else Private Const NULL_PTR = 0& Private Const PTR_LEN = 4& #End If Private Enum RoleSystems ROLE_SYSTEM_LISTITEM = 34& ROLE_SYSTEM_PUSHBUTTON = 43& ROLE_SYSTEM_COMBOBOX = 46& End Enum Private Enum TreeScope TreeScope_None = 0& TreeScope_Element = 1& TreeScope_Children = 2& TreeScope_Descendants = 4& TreeScope_Subtree = ((TreeScope_Element + TreeScope_Children) + TreeScope_Descendants) End Enum Private Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(0& To 7&) As Byte End Type #If VBA7 Then Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr) Private Declare PtrSafe Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As LongPtr) As Long Private Declare PtrSafe Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long Private Declare PtrSafe Sub SetLastError Lib "kernel32" (ByVal dwErrCode As Long) Private Declare PtrSafe Function StringFromGUID2 Lib "ole32" (ByRef rguid As GUID, ByVal lpsz As LongPtr, ByVal cchMax As Long) As Long Private Declare PtrSafe Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As GUID) As Long Private Declare PtrSafe Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hhk As LongPtr) As Long Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal nCode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr Private Declare PtrSafe Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr Private Declare PtrSafe Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long #Else Private Enum LongPtr [_] End Enum Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr) Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As LongPtr) As Long Private Declare Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long Private Declare Sub SetLastError Lib "kernel32" (ByVal dwErrCode As Long) Private Declare Function StringFromGUID2 Lib "ole32" (ByRef rguid As GUID, ByVal lpsz As LongPtr, ByVal cchMax As Long) As Long Private Declare Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As GUID) As Long Private Declare Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hhk As LongPtr) As Long Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal nCode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr Private Declare Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr Private Declare Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr Private Declare Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long #End If Private arThemes() As String Private sTheme As String Private bFindAndApplyThemesRet As Boolean Private bRetrievingThemes As Boolean ' ****************************************************************** ' This code is language sensitive !!! ' Languages supoorted: (English - Spanish - French) ' Constants for supported languages: Private Const LANG_OFFICE_THEME_LABEL = "OFFICE THEME: TEMA DE OFFICE: THÈME OFFICE:" Private Const LANG_OK_BUTTON = "OK ACEPTAR" Private Const LANG_CANCEL_BUTTON = "CANCEL CANCELAR ANNULER" ' ****************************************************************** Function GetAvailableOfficeThemes() As String() Const WH_CBT = 5& Dim lHook As LongPtr If OfficeThemesExist = False Then GoTo WrongOfficeVersion bRetrievingThemes = True Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) lHook = SetWindowsHookEx(WH_CBT, AddressOf HookProc, NULL_PTR, GetCurrentThreadId) Call SetProp(Application.hwnd, "Hook", lHook) Application.CommandBars.ExecuteMso ("ApplicationOptionsDialog") Call RemoveProp(Application.hwnd, "Hook") bRetrievingThemes = False GetAvailableOfficeThemes = arThemes Erase arThemes Exit Function WrongOfficeVersion: MsgBox "Office themes are only availble in editions greater than 2007.", vbCritical, "Error" End Function Function ApplyOfficeTheme(ByVal ThemeName As String) As Boolean Const WH_CBT = 5& Dim lHook As LongPtr If OfficeThemesExist = False Then GoTo WrongOfficeVersion sTheme = ThemeName bFindAndApplyThemesRet = False Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) lHook = SetWindowsHookEx(WH_CBT, AddressOf HookProc, NULL_PTR, GetCurrentThreadId) Call SetProp(Application.hwnd, "Hook", lHook) Application.CommandBars.ExecuteMso ("ApplicationOptionsDialog") Call RemoveProp(Application.hwnd, "Hook") sTheme = "" ApplyOfficeTheme = bFindAndApplyThemesRet 'FindAndApplyThemes Exit Function WrongOfficeVersion: MsgBox "Office themes are only availble in editions greater than 2007.", vbCritical, "Error" End Function ' _____________________________________ PRIVATE ROUTINES _____________________________________________ Private Function HookProc( _ ByVal idHook As Long, _ ByVal wParam As LongPtr, _ ByVal lParam As LongPtr _ ) As LongPtr Const HC_ACTION = 0&, HCBT_CREATEWND = 3&, HCBT_ACTIVATE = 5&, GW_CHILD = 5& Dim sBuff As String * 256&, lRet As Long If idHook < HC_ACTION Then HookProc = CallNextHookEx(GetProp(Application.hwnd, "Hook"), idHook, wParam, lParam) Exit Function End If If idHook = HCBT_CREATEWND Then Call SetTimer(Application.hwnd, NULL_PTR, 1000&, AddressOf UnHookJustInCase) End If If idHook = HCBT_ACTIVATE Then lRet = GetClassName(wParam, sBuff, 256&) If Left(sBuff, lRet) = "NUIDialog" Then Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) Call FindAndApplyThemes(GetNextWindow(wParam, GW_CHILD), TreeScope_Subtree) End If End If End Function Private Sub UnHookJustInCase() On Error Resume Next Call KillTimer(Application.hwnd, NULL_PTR) Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) Call RemoveProp(Application.hwnd, "Hook") End Sub Private Sub FindAndApplyThemes( _ ByVal hwnd As LongPtr, _ Optional ByVal Scope As TreeScope = TreeScope_Subtree _ ) Const UIA_LegacyIAccessiblePatternId = 10018& Const IID_IUIAutomationLegacyIAccessiblePattern = "{828055ad-355b-4435-86d5-3b51c14a9b1b}" Const SELFLAG_TAKEFOCUS = 1&, SELFLAG_TAKESELECTION = 2& Dim iidCuiAuto As GUID Dim lIndex As Long, lElementsCount As Long Dim j As Long, k As Long, lSelectionFlag As Long Dim lRet As Long, vTblOffset As Long, vFuncOrdinal As Long Dim sName As String, sValue As String, lRole As Long, lChildCount As Long Dim pAutomation As LongPtr, pPattern As LongPtr, pElement As LongPtr Dim pElementArray As LongPtr, pChildrenArray As LongPtr, pTrueCond As LongPtr Dim pChildElement As LongPtr Dim pLegacyName As LongPtr, pValue As LongPtr Dim pOk As LongPtr, pCancel As LongPtr, pThemeCombo As LongPtr pAutomation = GetAutomationPtr If pAutomation = NULL_PTR Then MsgBox "Automation failed.": Exit Sub vFuncOrdinal = 6& ' IUIAutomation::ElementFromHandle vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pAutomation, vTblOffset, vbLong, hwnd, VarPtr(pElement)) vFuncOrdinal = 21& ' IUIAutomation::CreateTrueCondition vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pAutomation, vTblOffset, vbLong, VarPtr(pTrueCond)) vFuncOrdinal = 6& ' IUIAutomationElement::FindAll vTblOffset = vFuncOrdinal * PTR_LEN ' IUIAutomationElement::FindAll lRet = vtblStdCall(pElement, vTblOffset, vbLong, Scope, pTrueCond, VarPtr(pElementArray)) vFuncOrdinal = 3& ' IUIAutomationElementArray :get_Length vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pElementArray, vTblOffset, vbLong, VarPtr(lElementsCount)) If lElementsCount = 0& Then GoTo Release_Interfaces End If lRet = CLSIDFromString(StrPtr(IID_IUIAutomationLegacyIAccessiblePattern), iidCuiAuto) Call DispGUID(iidCuiAuto) For lIndex = 0& To lElementsCount - 1& vFuncOrdinal = 4& ' IUIAutomationElementArray :GetElement vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pElementArray, vTblOffset, vbLong, lIndex, VarPtr(pElement)) vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pElement, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) If pPattern <> NULL_PTR Then 'Retrieve descendants properties. vFuncOrdinal = 7& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentName vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pLegacyName)) sName = GetStrFromPtrW(pLegacyName) vFuncOrdinal = 10& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentRole vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(lRole)) ' lRl = lRole vFuncOrdinal = 8& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentValue vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pValue)) sValue = GetStrFromPtrW(pValue) Select Case True Case InStr(LANG_OK_BUTTON, UCase(sName)) And lRole = ROLE_SYSTEM_PUSHBUTTON pOk = pElement Case InStr(LANG_CANCEL_BUTTON, UCase(sName)) And lRole = ROLE_SYSTEM_PUSHBUTTON pCancel = pElement Case InStr(LANG_OFFICE_THEME_LABEL, UCase(sName)) And lRole = ROLE_SYSTEM_COMBOBOX pThemeCombo = pElement End Select End If vFuncOrdinal = 2& 'IUIAutomationElement::Release vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pElement, vTblOffset, vbLong) Next lIndex vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pThemeCombo, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong) 'Retrieve ChildCount. vFuncOrdinal = 6& ' IUIAutomationElement::FindAll vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pThemeCombo, vTblOffset, vbLong, TreeScope_Subtree, pTrueCond, VarPtr(pChildrenArray)) vFuncOrdinal = 3& ' IUIAutomationElementArray :get_Length vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong, VarPtr(lChildCount)) If lChildCount = 0& Then GoTo Release_Interfaces End If For j = 0& To lChildCount - 1& vFuncOrdinal = 4& ' IUIAutomationElementArray :GetElement vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong, j, VarPtr(pChildElement)) vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pChildElement, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) vFuncOrdinal = 7& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentName vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pValue)) sValue = GetStrFromPtrW(pValue) vFuncOrdinal = 10& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentRole vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(lRole)) ' lRl = lRole If lRole = ROLE_SYSTEM_LISTITEM And bRetrievingThemes Then ReDim Preserve arThemes(k) arThemes(k) = sValue: k = k + 1& End If If lRole = ROLE_SYSTEM_LISTITEM And bRetrievingThemes = False Then If UCase(sValue) = UCase(sTheme) Then lSelectionFlag = SELFLAG_TAKEFOCUS + SELFLAG_TAKESELECTION vFuncOrdinal = 3& 'IUIAutomationLegacyIAccessiblePattern::Select vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, lSelectionFlag) vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pOk, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong) bFindAndApplyThemesRet = True '< set global flag to indicate success GoTo Release_Interfaces End If End If vFuncOrdinal = 2& 'IUIAutomationLegacyIAccessiblePattern::Release vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong) vFuncOrdinal = 2& 'IUIAutomationElement::Release vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pChildElement, vTblOffset, vbLong) 'IUIAutomationElementArray ::Release Next j vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pCancel, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong) Release_Interfaces: vFuncOrdinal = 2& 'Release vTblOffset = vFuncOrdinal * PTR_LEN If pAutomation Then lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong) 'IUIAutomationElementArray::Release lRet = vtblStdCall(pTrueCond, vTblOffset, vbLong) 'IUIAutomationCondition ::Release lRet = vtblStdCall(pElementArray, vTblOffset, vbLong) 'IUIAutomationElementArray ::Release lRet = vtblStdCall(pAutomation, vTblOffset, vbLong) 'IUIAutomation::Release End If End Sub Private Function GetAutomationPtr() As LongPtr Dim pAutomation As LongPtr Const IID_CUIAUTOMATION = "{FF48DBA4-60EF-4201-AA87-54103EEF594E}" Const IID_IUIAUTOMATION = "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}" Const CLSCTX_INPROC_SERVER = &H1, CC_STDCALL = 4&, S_OK = 0& Dim iidCuiAuto As GUID, iidIuiAuto As GUID, lRet As Long lRet = CLSIDFromString(StrPtr(IID_CUIAUTOMATION), iidCuiAuto) Call DispGUID(iidCuiAuto) lRet = CLSIDFromString(StrPtr(IID_IUIAUTOMATION), iidIuiAuto) Call DispGUID(iidIuiAuto) lRet = CoCreateInstance(iidCuiAuto, NULL_PTR, CLSCTX_INPROC_SERVER, iidIuiAuto, pAutomation) If lRet = S_OK Then GetAutomationPtr = pAutomation End Function Private Function vtblStdCall( _ ByVal InterfacePointer As LongPtr, _ ByVal VTableOffset As Long, _ ByVal FunctionReturnType As Long, _ ParamArray FunctionParameters() As Variant _ ) As Variant Const CC_STDCALL = 4& Dim vParamPtr() As LongPtr Dim pIndex As Long, pCount As Long Dim vParamType() As Integer Dim vRtn As Variant, vParams() As Variant vParams() = FunctionParameters() pCount = Abs(UBound(vParams) - LBound(vParams) + 1&) If pCount = 0& Then ReDim vParamPtr(0& To 0&) ReDim vParamType(0& To 0&) Else ReDim vParamPtr(0& To pCount - 1&) ReDim vParamType(0& To pCount - 1&) For pIndex = 0& To pCount - 1& vParamPtr(pIndex) = VarPtr(vParams(pIndex)) vParamType(pIndex) = VarType(vParams(pIndex)) Next End If pIndex = DispCallFunc( _ InterfacePointer, VTableOffset, CC_STDCALL, FunctionReturnType, _ pCount, vParamType(0&), vParamPtr(0&), vRtn) If pIndex = 0& Then vtblStdCall = vRtn Else Call SetLastError(pIndex) End If End Function Private Sub DispGUID(objGuid As GUID) Dim lRet As Long, sTmp As String, buf(100&) As Byte lRet = StringFromGUID2(objGuid, VarPtr(buf(0&)), UBound(buf) - 1&) sTmp = buf End Sub Private Function GetStrFromPtrW(ByVal lpString As LongPtr) As String Dim lLength As Long, sBuffer As String lLength = lstrlen(lpString) sBuffer = Space$(lLength) Call CopyMemory(ByVal StrPtr(sBuffer), ByVal lpString, lLength * 2&) GetStrFromPtrW = sBuffer End Function Private Function OfficeThemesExist() As Boolean OfficeThemesExist = CBool(Val(Application.Version) >= 12&) ' office>=2007 End Function
2- Code Usage example in the UserForm Module:
VBA Code:Option Explicit Private Sub UserForm_Initialize() Dim arThemes() As String arThemes = GetAvailableOfficeThemes If Not Not arThemes Then ComboBox1.List = arThemes ComboBox1.ListIndex = 0& End If End Sub Private Sub CommandButton1_Click() If ApplyOfficeTheme(ComboBox1.Value) Then 'success Else 'Failure End If End Sub
Did you actually try it in office 365? If so, did it work as expected? Thanks.This is great, just what I was seeking.
Thanks for uploading it.
Hope others can make use of it too.
Two things I noticed:Hi @tiredofit
I seem to have managed to retrieve/change/apply the office themes via code using UIAutomation and legacy Accessibility. No extrenal references are required to be set in the vba project. Just plain, plug and play vba.
Since this uses UI automation, the code is language-sensitive. The code as is, should work with English, Spanish and French editions of office only.
To make this work with other languages, you will need to change the global constants declared at the top of the api module. I have placed the constants there for easier future editing.
File Demo:
OfficeThemes_VBA.xlsm
Tested in (xl 2013 x32) and (xl 2016 x64 ). I hope it works in Office 365.
1- API code in a bas Module:
VBA Code:Option Explicit #If Win64 Then Private Const NULL_PTR = 0^ Private Const PTR_LEN = 8& #Else Private Const NULL_PTR = 0& Private Const PTR_LEN = 4& #End If Private Enum RoleSystems ROLE_SYSTEM_LISTITEM = 34& ROLE_SYSTEM_PUSHBUTTON = 43& ROLE_SYSTEM_COMBOBOX = 46& End Enum Private Enum TreeScope TreeScope_None = 0& TreeScope_Element = 1& TreeScope_Children = 2& TreeScope_Descendants = 4& TreeScope_Subtree = ((TreeScope_Element + TreeScope_Children) + TreeScope_Descendants) End Enum Private Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(0& To 7&) As Byte End Type #If VBA7 Then Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr) Private Declare PtrSafe Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As LongPtr) As Long Private Declare PtrSafe Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long Private Declare PtrSafe Sub SetLastError Lib "kernel32" (ByVal dwErrCode As Long) Private Declare PtrSafe Function StringFromGUID2 Lib "ole32" (ByRef rguid As GUID, ByVal lpsz As LongPtr, ByVal cchMax As Long) As Long Private Declare PtrSafe Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As GUID) As Long Private Declare PtrSafe Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hhk As LongPtr) As Long Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal nCode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr Private Declare PtrSafe Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr Private Declare PtrSafe Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long #Else Private Enum LongPtr [_] End Enum Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr) Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As LongPtr) As Long Private Declare Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long Private Declare Sub SetLastError Lib "kernel32" (ByVal dwErrCode As Long) Private Declare Function StringFromGUID2 Lib "ole32" (ByRef rguid As GUID, ByVal lpsz As LongPtr, ByVal cchMax As Long) As Long Private Declare Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As GUID) As Long Private Declare Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hhk As LongPtr) As Long Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal nCode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr Private Declare Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr Private Declare Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr Private Declare Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long #End If Private arThemes() As String Private sTheme As String Private bFindAndApplyThemesRet As Boolean Private bRetrievingThemes As Boolean ' ****************************************************************** ' This code is language sensitive !!! ' Languages supported: (English - Spanish - French) ' Constants for supported languages: Private Const LANG_OFFICE_THEME_LABEL = "OFFICE THEME: TEMA DE OFFICE: THÈME OFFICE:" Private Const LANG_OK_BUTTON = "OK ACEPTAR" Private Const LANG_CANCEL_BUTTON = "CANCEL CANCELAR ANNULER" ' ****************************************************************** Function GetAvailableOfficeThemes() As String() Const WH_CBT = 5& Dim lHook As LongPtr If OfficeThemesExist = False Then GoTo WrongOfficeVersion bRetrievingThemes = True Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) lHook = SetWindowsHookEx(WH_CBT, AddressOf HookProc, NULL_PTR, GetCurrentThreadId) Call SetProp(Application.hwnd, "Hook", lHook) Application.CommandBars.ExecuteMso ("ApplicationOptionsDialog") Call RemoveProp(Application.hwnd, "Hook") bRetrievingThemes = False GetAvailableOfficeThemes = arThemes Erase arThemes Exit Function WrongOfficeVersion: MsgBox "Office themes are only availble in editions greater than 2007.", vbCritical, "Error" End Function Function ApplyOfficeTheme(ByVal ThemeName As String) As Boolean Const WH_CBT = 5& Dim lHook As LongPtr If OfficeThemesExist = False Then GoTo WrongOfficeVersion sTheme = ThemeName bFindAndApplyThemesRet = False Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) lHook = SetWindowsHookEx(WH_CBT, AddressOf HookProc, NULL_PTR, GetCurrentThreadId) Call SetProp(Application.hwnd, "Hook", lHook) Application.CommandBars.ExecuteMso ("ApplicationOptionsDialog") Call RemoveProp(Application.hwnd, "Hook") sTheme = "" ApplyOfficeTheme = bFindAndApplyThemesRet 'FindAndApplyThemes Exit Function WrongOfficeVersion: MsgBox "Office themes are only availble in editions greater than 2007.", vbCritical, "Error" End Function ' _____________________________________ PRIVATE ROUTINES _____________________________________________ Private Function HookProc( _ ByVal idHook As Long, _ ByVal wParam As LongPtr, _ ByVal lParam As LongPtr _ ) As LongPtr Const HC_ACTION = 0&, HCBT_CREATEWND = 3&, HCBT_ACTIVATE = 5&, GW_CHILD = 5& Dim sBuff As String * 256&, lRet As Long If idHook < HC_ACTION Then HookProc = CallNextHookEx(GetProp(Application.hwnd, "Hook"), idHook, wParam, lParam) Exit Function End If If idHook = HCBT_CREATEWND Then Call SetTimer(Application.hwnd, NULL_PTR, 1000&, AddressOf UnHookJustInCase) End If If idHook = HCBT_ACTIVATE Then lRet = GetClassName(wParam, sBuff, 256&) If Left(sBuff, lRet) = "NUIDialog" Then Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) Call FindAndApplyThemes(GetNextWindow(wParam, GW_CHILD), TreeScope_Subtree) End If End If End Function Private Sub UnHookJustInCase() On Error Resume Next Call KillTimer(Application.hwnd, NULL_PTR) Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) Call RemoveProp(Application.hwnd, "Hook") End Sub Private Sub FindAndApplyThemes( _ ByVal hwnd As LongPtr, _ Optional ByVal Scope As TreeScope = TreeScope_Subtree _ ) Const UIA_LegacyIAccessiblePatternId = 10018& Const IID_IUIAutomationLegacyIAccessiblePattern = "{828055ad-355b-4435-86d5-3b51c14a9b1b}" Const SELFLAG_TAKEFOCUS = 1&, SELFLAG_TAKESELECTION = 2& Dim iidCuiAuto As GUID Dim lIndex As Long, lElementsCount As Long Dim j As Long, k As Long, lSelectionFlag As Long Dim lRet As Long, vTblOffset As Long, vFuncOrdinal As Long Dim sName As String, sValue As String, lRole As Long, lChildCount As Long Dim pAutomation As LongPtr, pPattern As LongPtr, pElement As LongPtr Dim pElementArray As LongPtr, pChildrenArray As LongPtr, pTrueCond As LongPtr Dim pChildElement As LongPtr Dim pLegacyName As LongPtr, pValue As LongPtr Dim pOk As LongPtr, pCancel As LongPtr, pThemeCombo As LongPtr pAutomation = GetAutomationPtr If pAutomation = NULL_PTR Then MsgBox "Automation failed.": Exit Sub vFuncOrdinal = 6& ' IUIAutomation::ElementFromHandle vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pAutomation, vTblOffset, vbLong, hwnd, VarPtr(pElement)) vFuncOrdinal = 21& ' IUIAutomation::CreateTrueCondition vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pAutomation, vTblOffset, vbLong, VarPtr(pTrueCond)) vFuncOrdinal = 6& ' IUIAutomationElement::FindAll vTblOffset = vFuncOrdinal * PTR_LEN ' IUIAutomationElement::FindAll lRet = vtblStdCall(pElement, vTblOffset, vbLong, Scope, pTrueCond, VarPtr(pElementArray)) vFuncOrdinal = 3& ' IUIAutomationElementArray :get_Length vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pElementArray, vTblOffset, vbLong, VarPtr(lElementsCount)) If lElementsCount = 0& Then GoTo Release_Interfaces End If lRet = CLSIDFromString(StrPtr(IID_IUIAutomationLegacyIAccessiblePattern), iidCuiAuto) Call DispGUID(iidCuiAuto) For lIndex = 0& To lElementsCount - 1& vFuncOrdinal = 4& ' IUIAutomationElementArray :GetElement vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pElementArray, vTblOffset, vbLong, lIndex, VarPtr(pElement)) vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pElement, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) If pPattern <> NULL_PTR Then 'Retrieve descendants properties. vFuncOrdinal = 7& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentName vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pLegacyName)) sName = GetStrFromPtrW(pLegacyName) vFuncOrdinal = 10& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentRole vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(lRole)) ' lRl = lRole vFuncOrdinal = 8& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentValue vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pValue)) sValue = GetStrFromPtrW(pValue) Select Case True Case InStr(LANG_OK_BUTTON, UCase(sName)) And lRole = ROLE_SYSTEM_PUSHBUTTON pOk = pElement Case InStr(LANG_CANCEL_BUTTON, UCase(sName)) And lRole = ROLE_SYSTEM_PUSHBUTTON pCancel = pElement Case InStr(LANG_OFFICE_THEME_LABEL, UCase(sName)) And lRole = ROLE_SYSTEM_COMBOBOX pThemeCombo = pElement End Select End If vFuncOrdinal = 2& 'IUIAutomationElement::Release vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pElement, vTblOffset, vbLong) Next lIndex vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pThemeCombo, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong) 'Retrieve ChildCount. vFuncOrdinal = 6& ' IUIAutomationElement::FindAll vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pThemeCombo, vTblOffset, vbLong, TreeScope_Subtree, pTrueCond, VarPtr(pChildrenArray)) vFuncOrdinal = 3& ' IUIAutomationElementArray :get_Length vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong, VarPtr(lChildCount)) If lChildCount = 0& Then GoTo Release_Interfaces End If For j = 0& To lChildCount - 1& vFuncOrdinal = 4& ' IUIAutomationElementArray :GetElement vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong, j, VarPtr(pChildElement)) vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pChildElement, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) vFuncOrdinal = 7& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentName vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(pValue)) sValue = GetStrFromPtrW(pValue) vFuncOrdinal = 10& 'IID_IUIAutomationLegacyIAccessiblePattern::get_CurrentRole vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, VarPtr(lRole)) ' lRl = lRole If lRole = ROLE_SYSTEM_LISTITEM And bRetrievingThemes Then ReDim Preserve arThemes(k) arThemes(k) = sValue: k = k + 1& End If If lRole = ROLE_SYSTEM_LISTITEM And bRetrievingThemes = False Then If UCase(sValue) = UCase(sTheme) Then lSelectionFlag = SELFLAG_TAKEFOCUS + SELFLAG_TAKESELECTION vFuncOrdinal = 3& 'IUIAutomationLegacyIAccessiblePattern::Select vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong, lSelectionFlag) vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pOk, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong) bFindAndApplyThemesRet = True '< set global flag to indicate success GoTo Release_Interfaces End If End If vFuncOrdinal = 2& 'IUIAutomationLegacyIAccessiblePattern::Release vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong) vFuncOrdinal = 2& 'IUIAutomationElement::Release vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pChildElement, vTblOffset, vbLong) 'IUIAutomationElementArray ::Release Next j vFuncOrdinal = 14& 'IID_IUIAutomationElement::GetCurrentPatternAs vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pCancel, vTblOffset, vbLong, UIA_LegacyIAccessiblePatternId, VarPtr(iidCuiAuto), VarPtr(pPattern)) vFuncOrdinal = 4& 'IID_IUIAutomationLegacyIAccessiblePattern::DoDefaultAction vTblOffset = vFuncOrdinal * PTR_LEN lRet = vtblStdCall(pPattern, vTblOffset, vbLong) Release_Interfaces: vFuncOrdinal = 2& 'Release vTblOffset = vFuncOrdinal * PTR_LEN If pAutomation Then lRet = vtblStdCall(pChildrenArray, vTblOffset, vbLong) 'IUIAutomationElementArray::Release lRet = vtblStdCall(pTrueCond, vTblOffset, vbLong) 'IUIAutomationCondition ::Release lRet = vtblStdCall(pElementArray, vTblOffset, vbLong) 'IUIAutomationElementArray ::Release lRet = vtblStdCall(pAutomation, vTblOffset, vbLong) 'IUIAutomation::Release End If End Sub Private Function GetAutomationPtr() As LongPtr Dim pAutomation As LongPtr Const IID_CUIAUTOMATION = "{FF48DBA4-60EF-4201-AA87-54103EEF594E}" Const IID_IUIAUTOMATION = "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}" Const CLSCTX_INPROC_SERVER = &H1, CC_STDCALL = 4&, S_OK = 0& Dim iidCuiAuto As GUID, iidIuiAuto As GUID, lRet As Long lRet = CLSIDFromString(StrPtr(IID_CUIAUTOMATION), iidCuiAuto) Call DispGUID(iidCuiAuto) lRet = CLSIDFromString(StrPtr(IID_IUIAUTOMATION), iidIuiAuto) Call DispGUID(iidIuiAuto) lRet = CoCreateInstance(iidCuiAuto, NULL_PTR, CLSCTX_INPROC_SERVER, iidIuiAuto, pAutomation) If lRet = S_OK Then GetAutomationPtr = pAutomation End Function Private Function vtblStdCall( _ ByVal InterfacePointer As LongPtr, _ ByVal VTableOffset As Long, _ ByVal FunctionReturnType As Long, _ ParamArray FunctionParameters() As Variant _ ) As Variant Const CC_STDCALL = 4& Dim vParamPtr() As LongPtr Dim pIndex As Long, pCount As Long Dim vParamType() As Integer Dim vRtn As Variant, vParams() As Variant vParams() = FunctionParameters() pCount = Abs(UBound(vParams) - LBound(vParams) + 1&) If pCount = 0& Then ReDim vParamPtr(0& To 0&) ReDim vParamType(0& To 0&) Else ReDim vParamPtr(0& To pCount - 1&) ReDim vParamType(0& To pCount - 1&) For pIndex = 0& To pCount - 1& vParamPtr(pIndex) = VarPtr(vParams(pIndex)) vParamType(pIndex) = VarType(vParams(pIndex)) Next End If pIndex = DispCallFunc( _ InterfacePointer, VTableOffset, CC_STDCALL, FunctionReturnType, _ pCount, vParamType(0&), vParamPtr(0&), vRtn) If pIndex = 0& Then vtblStdCall = vRtn Else Call SetLastError(pIndex) End If End Function Private Sub DispGUID(objGuid As GUID) Dim lRet As Long, sTmp As String, buf(100&) As Byte lRet = StringFromGUID2(objGuid, VarPtr(buf(0&)), UBound(buf) - 1&) sTmp = buf End Sub Private Function GetStrFromPtrW(ByVal lpString As LongPtr) As String Dim lLength As Long, sBuffer As String lLength = lstrlen(lpString) sBuffer = Space$(lLength) Call CopyMemory(ByVal StrPtr(sBuffer), ByVal lpString, lLength * 2&) GetStrFromPtrW = sBuffer End Function Private Function OfficeThemesExist() As Boolean OfficeThemesExist = CBool(Val(Application.Version) >= 12&) ' office>=2007 End Function
2- Code Usage example in the UserForm Module:
VBA Code:Option Explicit Private Sub UserForm_Initialize() Dim arThemes() As String arThemes = GetAvailableOfficeThemes If Not Not arThemes Then ComboBox1.List = arThemes ComboBox1.ListIndex = 0& End If End Sub Private Sub CommandButton1_Click() If ApplyOfficeTheme(ComboBox1.Value) Then 'success Else 'Failure End If End Sub
Function ApplyOfficeTheme(ByVal ThemeName As String) As Boolean
Const WH_CBT = 5&
Dim lHook As LongPtr
If OfficeThemesExist = False Then GoTo WrongOfficeVersion
sTheme = ThemeName
bFindAndApplyThemesRet = False
Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook")) '*****************************************************GETTING TO THIS LINE CLOSES EXCEL
lHook = SetWindowsHookEx(WH_CBT, AddressOf HookProc, NULL_PTR, GetCurrentThreadId)
Call SetProp(Application.hwnd, "Hook", lHook)
Application.CommandBars.ExecuteMso ("ApplicationOptionsDialog")
Call RemoveProp(Application.hwnd, "Hook")
sTheme = ""
ApplyOfficeTheme = bFindAndApplyThemesRet 'FindAndApplyThemes
Exit Function
WrongOfficeVersion:
MsgBox "Office themes are only availble in editions greater than 2007.", vbCritical, "Error"
End Function
The code immediately closes the Options Dialog and it is hardly noticeable as we can see in the clip preview I posted . I tried preventing the dialog from appearing in response to the HCBT_CREATEWND hook section but it didn't work. I will see if I can do something about it.1. When I cliick on the button, the Office Theme Options, ie Dark Grey, Black, White, etc. temporarily pops up then disappears.
No. You are never supposed to step into callback routines. But why would you need to? The code is very easy to use both for retrieving the available themes as well as for applying the desired theme.2. Running the code is fine but if I tried stepping into it, it closes Excel when it reaches here:
' For reading the available office themes:
Sub Test1()
Dim arThemes() As String, i As Long
arThemes = GetAvailableOfficeThemes
If Not Not arThemes Then
For i = LBound(arThemes) To UBound(arThemes)
Debug.Print arThemes(i)
Next i
End If
End Sub
' For Applying an office theme:
Sub Test2()
ApplyOfficeTheme "White"
End Sub
The popping up is not a problem, I was just curious.The code immediately closes the Options Dialog and it is hardly noticeable as we can see in the clip preview I posted . I tried preventing the dialog from appearing in response to the HCBT_CREATEWND hook section but it didn't work. I will see if I can do something about it.
No. You are never supposed to step into callback routines. But why would you need to? The code is very easy to use both for retrieving the available themes as well as for applying the desired theme.
You don't have to have a userform to make tthis work.
In some module:
VBA Code:' For reading the available office themes: Sub Test1() Dim arThemes() As String, i As Long arThemes = GetAvailableOfficeThemes If Not Not arThemes Then For i = LBound(arThemes) To UBound(arThemes) Debug.Print arThemes(i) Next i End If End Sub ' For Applying an office theme: Sub Test2() ApplyOfficeTheme "White" End Sub