Office 2016 - Clipboard

Add-in Express™ Support Service
That's what is more important than anything else

Office 2016 - Clipboard
 
André Gerards




Posts: 13
Joined: 2017-11-15
Hi everybody, is there any way to handle the Office-Clipboard with VBA?
Opening or closing the pane? Clear all items?

I found the following article to clear all items (https://www.mrexcel.com/forum/excel-questions/167292-clear-empty-office-clipboard-form-excel-vba.html), but it only works if the clipboard-pane is visible. So this solution is just half way...

Any suggestions?
Posted 02 Sep, 2019 02:49:53 Top
Andrei Smolin


Add-in Express team


Posts: 16846
Joined: 2006-05-11
Hello André Gerards,

The following code line written in VBA works for me:
Application.CommandBars("Office Clipboard").Visible = False

In C# this should be as follows (a raw sketch):


using Office=Microsoft.Office.Core;
Office.CommandBars cmdBars = ExcelApp.CommandBars;
Office.CommandBar cmdBar = cmdBars["Office Clipboard"];
cmdBar.Visible = true;
Marshal.ReleaseComObject(cmdBar); cmdBar = null;
Marshal.ReleaseComObject(cmdBars); cmdBars = null;


Note that for the other part of that code to work, you may need create a delay after your code shows the pane; that delay should let Office draw the pane and initialize it. Say, you can use the SendMessage/OnSendMessage machinery that we describe in section Wait a Little at https://www.add-in-express.com/docs/net-office-tips.php#wait-a-little.

Regards from Belarus (GMT+3),

Andrei Smolin
Add-in Express Team Leader
Posted 02 Sep, 2019 04:42:10 Top
André Gerards




Posts: 13
Joined: 2017-11-15
Hello Andrei,

Thanks for the hints, especially for the tip 'Wait a little' - that is really decisive.
In addition, it is important to know that for non-english clients, there is a difference between the internal 'Commandbar'-name and the 'Taskpane'-name (in English both 'Office Clipboard', but in German the Taskpane name is 'Office-Zwischenablage' - additionally different from the displayed name), so you have to distinguish in determining the window-handle.

Thanks again!
Posted 04 Sep, 2019 04:46:37 Top
André Gerards




Posts: 13
Joined: 2017-11-15
here the VBA code to empty the Office clipboard, tested on an English and German Excel client; this is not complicated and should be easy to translate


Option Explicit


' *** DEKLARATIONEN ***
'
Const CHILDID_SELF = 0&
Const ROLE_PUSHBUTTON = &H2B&
Const WM_GETTEXT = &HD
'
Type tGUID
  lData1 As Long
  nData2 As Integer
  nData3 As Integer
  abytData4(0 To 7) As Byte
End Type
'
Type AccObject
  objIA As IAccessible
  lngChild As Long
End Type
'
Dim lngChild As Long                  'Handle als Rückgabe
Dim strClass As String                'gesuchte Klasse, z.B. Commandbar
Dim strCaption As String              'Titel eines Fensters
Dim strCommandbarName As String       'lokalisierter Name der gesuchten Commandbar
Dim strTaskpaneName As String         'lokalisierter Name der gesuchten Taskpane
Dim strButtonName As String           'lokalisierter Name des gesuchten Schalters

'
Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As Long, ByVal dwId As Long, riid As tGUID, ppvObject As Object) As Long
Declare Function AccessibleChildren Lib "oleacc" (ByVal paccContainer As IAccessible, ByVal iChildStart As Long, ByVal cChildren As Long, rgvarChildren As Variant, pcObtained As Long) As Long
Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function EnumChildWindows Lib "user32" (ByVal hwndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long


' *** AUFRUF der Prozedur ***

' * leert die Office-Zwischenablage
Sub ClearOfficeClipboard()
  'HINWEIS: dieses Tool ist nut mit Excel getestet
  Dim blnScreenUpdating As Boolean      'Voreinstellung der Aktualisierung merken
  Dim intOffLanguage As Integer         'eingestellte Office-Sprache
  Dim blnTaskPaneVis As Boolean         'Voreinstellung der Commandbar merken
  Dim lngTaskPaneHwnd As Long           'Handle der gesuchten Commandbar
  Dim oButton As AccObject
  
  'Aktualisierung der Ansicht erzwingen
  blnScreenUpdating = Application.ScreenUpdating
  Application.ScreenUpdating = True
  
  'eingestellte Sprache abfragen
  Select Case Application.LanguageSettings.LanguageID(msoLanguageIDUI)
    Case 1031     'deutsch
      strCommandbarName = "Office Clipboard"
      strTaskpaneName = "Office-Zwischenablage"
      strButtonName = "Alle löschen"
    Case 1033     'englisch
      strCommandbarName = "Office Clipboard"
      strTaskpaneName = "Office Clipboard"
      strButtonName = "Clear All"
    Case Else     'nicht unterstützt
      MsgBox "Die eingestellte Sprache wird nicht unterschützt!"
      GoTo EndSub
  End Select
  
  'Sichtbarkeit der Commandbar merken und einstellen
  blnTaskPaneVis = Application.CommandBars(strCommandbarName).Visible
  If blnTaskPaneVis = False Then
    Application.CommandBars(strCommandbarName).Visible = True
    'Zeit zur Initalisierung und dem Aufbau schinden
    ' ohne diese Verzögerung wird der Schalter nicht gefunden, falls das Clipboard ausgeblendet war
    Application.Wait (Now + TimeValue("0:00:01"))
  End If
  'ActiveSheet.Select
  
  'Handle der Clipboard-Commandbar ermitteln
  lngTaskPaneHwnd = GetOfficeTaskPaneHwnd(Excel.Application)
  
  'Schalter 'Clear All' in der Clipboard-Commandbar ermitteln
  oButton = FindAccessibleChildInWindow(lngTaskPaneHwnd, strButtonName, ROLE_PUSHBUTTON)
  If oButton.objIA Is Nothing Then
    'Fehlerabfang, wenn nichts gefunden wurde
    MsgBox "Schalter '" & strButtonName & "' nicht gefunden!"
  Else
    'Schalter ausführen
    oButton.objIA.accDoDefaultAction oButton.lngChild
  End If

  'Sichtbarkeit der Commandbar zurücksetzen
  Application.CommandBars(strCommandbarName).Visible = blnTaskPaneVis

EndSub:
  'Aktualisierung der Ansicht zurücksetzen
  Application.ScreenUpdating = blnScreenUpdating
  
End Sub


' *** HILFSFUNKTIONEN ***
'
'Retrieve window class name
Function GetWndClass(ByVal hWnd As Long) As String
  Dim buf As String, retval As Long
  buf = Space(256)
  retval = GetClassName(hWnd, buf, 255)
  GetWndClass = Left(buf, retval)
End Function
'
'Retrieve window title
Function GetWndText(ByVal hWnd As Long) As String
  Dim buf As String, retval As Long
  buf = Space(256)
  retval = SendMessage(hWnd, WM_GETTEXT, 255, buf)
  GetWndText = Left(buf, InStr(1, buf, Chr(0)) - 1)
End Function
'
'The call back function used by EnumChildWindows
Function EnumChildWndProc(ByVal hChild As Long, ByVal lParam As Long) As Long
  Dim found As Boolean
  EnumChildWndProc = -1
  If strClass > "" And strCaption > "" Then
  Debug.Print GetWndClass(hChild) & "; " & GetWndText(hChild)
    found = StrComp(GetWndClass(hChild), strClass, vbTextCompare) = 0 And _
    StrComp(GetWndText(hChild), strCaption, vbTextCompare) = 0
  ElseIf strClass > "" Then
    found = (StrComp(GetWndClass(hChild), strClass, vbTextCompare) = 0)
  ElseIf strCaption > "" Then
    found = (StrComp(GetWndText(hChild), strCaption, vbTextCompare) = 0)
  Else
    found = True
  End If

  If found Then
    lngChild = hChild
    EnumChildWndProc = 0
  Else
    EnumChildWndProc = -1
  End If
End Function
'
'Find the window handle of a child window based on its class and titie
Function FindChildWindow(ByVal hParent As Long, Optional cls As String = "", Optional title As String = "") As Long
  lngChild = 0
  strClass = cls
  strCaption = title
  EnumChildWindows hParent, AddressOf EnumChildWndProc, 0
  FindChildWindow = lngChild
End Function
'
'Retrieve the IAccessible interface from a window handle
'Reference:Jean Ross,Chapter 17: Accessibility in Visual Basic,Advanced Microsoft Visual Basic 6.0, 2nd Edition
Function IAccessibleFromHwnd(hWnd As Long) As IAccessible
  Dim oIA As IAccessible
  Dim tg As tGUID
  Dim lReturn As Long
  
  'Define the GUID for the IAccessible object {618736E0-3C3D-11CF-810C-00AA00389B71}
  With tg
    .lData1 = &H618736E0
    .nData2 = &H3C3D
    .nData3 = &H11CF
    .abytData4(0) = &H81
    .abytData4(1) = &HC
    .abytData4(2) = &H0
    .abytData4(3) = &HAA
    .abytData4(4) = &H0
    .abytData4(5) = &H38
    .abytData4(6) = &H9B
    .abytData4(7) = &H71
  End With
  
  'Retrieve the IAccessible object for the form
  lReturn = AccessibleObjectFromWindow(hWnd, 0, tg, oIA)
  Set IAccessibleFromHwnd = oIA
End Function
'
'Recursively looking for a child with specified accName and accRole in the accessibility tree
Function FindAccessibleChild(oParent As IAccessible, strName As String, lngRole As Long) As AccObject
  Dim lHowMany As Long
  Dim avKids() As Variant
  Dim lGotHowMany As Long, i As Integer
  Dim oChild As IAccessible
  
  FindAccessibleChild.lngChild = CHILDID_SELF
  If oParent.accChildCount = 0 Then
    Set FindAccessibleChild.objIA = Nothing
    Exit Function
  End If
  lHowMany = oParent.accChildCount
  ReDim avKids(lHowMany - 1) As Variant
  lGotHowMany = 0
  If AccessibleChildren(oParent, 0, lHowMany, avKids(0), lGotHowMany) <> 0 Then
    MsgBox "Error retrieving accessible children!"
    Set FindAccessibleChild.objIA = Nothing
    Exit Function
  End If
  
  'To do: the approach described in http://msdn.microsoft.com/msdnmag/issues/0400/aaccess/default.aspx are probably better and more reliable
  On Error Resume Next
  For i = 0 To lGotHowMany - 1
    If IsObject(avKids(i)) Then
      If StrComp(avKids(i).accName, strName) = 0 And avKids(i).accRole = lngRole Then
        Set FindAccessibleChild.objIA = avKids(i)
        Exit For
      Else
        Set oChild = avKids(i)
        FindAccessibleChild = FindAccessibleChild(oChild, strName, lngRole)
        If Not FindAccessibleChild.objIA Is Nothing Then
          Exit For
        End If
      End If
    Else
      If StrComp(oParent.accName(avKids(i)), strName) = 0 And oParent.accRole(avKids(i)) = lngRole Then
        Set FindAccessibleChild.objIA = oParent
        FindAccessibleChild.lngChild = avKids(i)
        Exit For
      End If
    End If
  Next i
End Function
'
Function FindAccessibleChildInWindow(hwndParent As Long, strName As String, lngRole As Long) As AccObject
  Dim oParent As IAccessible
  
  Set oParent = IAccessibleFromHwnd(hwndParent)
  If oParent Is Nothing Then
    Set FindAccessibleChildInWindow.objIA = Nothing
  Else
    FindAccessibleChildInWindow = FindAccessibleChild(oParent, strName, lngRole)
  End If
End Function
'
'Generic routine to retrieve the window handle of the active window of an Office Application
Function GetOfficeAppHwnd(app As Object) As Long
  GetOfficeAppHwnd = FindWindow(vbNullString, GetOfficeAppWindowTitle(app))
End Function
'
'Retrieve the window handle of the task pane
'Notice: the task pane window title is localized!
Function GetOfficeTaskPaneHwnd(app As Object) As Long
  GetOfficeTaskPaneHwnd = FindChildWindow(GetOfficeAppHwnd(app), "MsoCommandBar", strTaskpaneName)
End Function
'
'Generic routine to retrieve the window title of the active window of an Office application
Function GetOfficeAppWindowTitle(app As Object) As String
  On Error GoTo ErrorHandler
  Select Case app.Name
    Case "Microsoft Word"
      GetOfficeAppWindowTitle = app.ActiveWindow.Caption & " - " & "Word"
    Case "Microsoft Excel"
      GetOfficeAppWindowTitle = app.ActiveWindow.Caption & " - " & "Excel"
  End Select
Exit Function

ErrorHandler:
  MsgBox "Unsupported Office application!"
  GetOfficeAppWindowTitle = ""
End Function


Thanks for suggestions and improvements, André
Posted 04 Sep, 2019 05:02:09 Top
Andrei Smolin


Add-in Express team


Posts: 16846
Joined: 2006-05-11
Oh, great! Thank YOU!

Regards from Belarus (GMT+3),

Andrei Smolin
Add-in Express Team Leader
Posted 04 Sep, 2019 06:09:11 Top