Stop VB from Clearing Clipboard

eforti

Board Regular
Joined
Aug 15, 2005
Messages
220
Hello All,
I have a file with Workbook Activate/Deactivate and Worksheet Activate/Deactivate commands in it. When someone is trying to copy information from or to this workbook the VB automatically clears the clipboard (or at least that's how I understand it), which makes it so they can't paste that data anywhere else. How can I prevent this, but still keep my current commands?
 
I don't reccommend using this without some testing as I never really finished it. It does work as far as I can tell by backing up the clipboard to memory and then restoring to the clipboard's memory location.

Example usage:
Code:
Sub Example()
    BackUpClipBoard
'   your code here
    RestoreClipBoard
End Sub

Code:
Option Explicit

Private Declare Function CountClipboardFormats Lib "user32" () As Long
Private Declare Function EmptyClipboard Lib "user32" () As Long
Private Declare Function EnumClipboardFormats Lib "user32" (ByVal wFormat 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 SetClipboardData Lib "user32" (ByVal wFormat As Long, ByVal hMem As Long) 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 GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)


Private Const GMEM_FIXED As Long = &H0
Private Const GMEM_ZEROINIT As Long = &H40
Private Const GPTR As Long = (GMEM_FIXED Or GMEM_ZEROINIT)

Private FormatIds() As Long
Private MemObj2() As Long
Private MemObjPtr2() As Long
Private NumFormats As Long

Public Sub BackUpClipBoard()
    Dim MemObj() As Long
    Dim MemObjSize() As Long
    Dim MemObjPtr() As Long
    Dim x As Integer
    
    OpenClipboard Application.hwnd
    
    NumFormats = CountClipboardFormats
    NumFormats = NumFormats - 1
    
    If NumFormats > 0 Then
        ReDim FormatIds(NumFormats)
        ReDim MemObj(NumFormats)
        ReDim MemObj2(NumFormats)
        ReDim MemObjSize(NumFormats)
        ReDim MemObjPtr(NumFormats)
        ReDim MemObjPtr2(NumFormats)
        
        FormatIds = GetFormatIds(NumFormats)
         
        For x = 0 To NumFormats
            MemObj(x) = GetClipboardData(FormatIds(x))
            MemObjSize(x) = GlobalSize(MemObj(x))
            MemObjPtr(x) = GlobalLock(MemObj(x))
             
            MemObj2(x) = GlobalAlloc(GPTR, MemObjSize(x))
            MemObjPtr2(x) = GlobalLock(MemObj2(x))
    
            CopyMemory ByVal MemObjPtr2(x), ByVal MemObjPtr(x), MemObjSize(x)
            GlobalUnlock MemObj(0)
        Next
        
    End If
    
    EmptyClipboard
End Sub

Public Sub RestoreClipBoard()
    Dim x As Integer
    
    EmptyClipboard
    
    For x = 0 To NumFormats
        GlobalUnlock MemObj2(x)
        SetClipboardData FormatIds(x), ByVal MemObjPtr2(x)
        GlobalFree MemObj2(x)
    Next
    
    CloseClipboard
    Erase FormatIds
    Erase MemObj2
    Erase MemObjPtr2
End Sub

Private Function GetFormatIds(NumFormats As Long) As Long()
    Dim x As Integer, FormatIds() As Long
    
    ReDim FormatIds(NumFormats)
    FormatIds(0) = EnumClipboardFormats(0)
    
    For x = 1 To NumFormats
        FormatIds(x) = EnumClipboardFormats(FormatIds(x - 1))
    Next
    GetFormatIds = FormatIds
End Function
 
Upvote 0

Excel Facts

What is the last column in Excel?
Excel columns run from A to Z, AA to AZ, AAA to XFD. The last column is XFD.
Hi Tom . Long time no see !

I had gone down that road but was unable to preserve the Formulae only the values .
 
Upvote 0
Hi back at you Jaafar.

You will probably have to re-register the custom formats. I'm not going to spend time on it but had this in my "never will finish" folder and thought I would post it. :)
 
Upvote 0
Thanks to everyone who provided feedback on this. I'm going to use Jaafar's coding and will just worry about the formating later. This is a big help even without the formatting!
 
Upvote 0
Hi, this problem exists from 97 with excel - unlike word / outlook / lotus 123 on win95 until win7 it clears the clipboard after any action, what'soever.

So if you select range and copy, then go to another range and press a key, icon, menu item, etc other than paste, it clears the copy,

Ive sent endless emails and posts for 10yrs plus and MS will not change or even respond, and ive yet to find any solution or setting. we are not allowed spreadsheets with macros at work, so no vb code helps,

the only workaround is to use a third party clipboard app that grabs the copy and then use its hotkey to paste.

sad, but still fighting
fred
 
Upvote 0
Jaafar wrote:
Code:
OpenClipboard 0
Application.Calculation = xlCalculationManual
CloseClipboard

It does lose the flashing dotted rectangle of the copy/cut range!
But the main problem is that Cut becomes Copy! (Excel 2010-64bit/win7)
 
Last edited:
Upvote 0
Hi all,

Bringing back to life this old thread -- I was recently working on a workbook which required that the excel clipboard content stayed preserved after doubleClicking the worksheet, so I came up with this hack which is more generic and which should make the excel clipboard persistent despite altering the workbook (like when editing a cell- DoubleClicking- Displaying the DV dialog- Saving the workbook - execution of some VBA commands etc... all of which clear the clipboard.)

This hack works by retrieving the range being copied/cut.. This is achieved by first registering the excel custom clipboard format "Link" and then getting the range address from its content and Re-Cutting/Re-Copying it if necessary.

I have used the CommandBars "_OnUpdate" event which conviniently fires each time excel clears the clipboard.

Contrary to the previous API solution offered in this thread, this code does retain formulaes in cells not just the data.

Note:
In order to clear the clipboard once the code is running, the user must press the ESC key.

ISSUES when tested:

1-In the unlikely scenario where cutting a cell and pasting it onto another cell that happens to have the same content as the cell being cut, the annoying "marching ants" remain.
2-Doesn't work when copying/cutting entire rows or entire columns.


The entire code goes in the Thisworkbook Module :

Code:
'//Persistent ClibBorad Content for excel.
'//Tested on Office2010 x64Bit/Win x64.

Option Explicit

#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 GlobalUnlock Lib "kernel32" (ByVal hMem As LongLong) As Long
    Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongLong) As LongPtr
    Private Declare PtrSafe Function GlobalSize Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
    Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongLong) As Long
    Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr
    Private Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare PtrSafe Function RegisterClipboardFormat Lib "user32" Alias "RegisterClipboardFormatA" (ByVal lpString As String) As Long
    Private Declare PtrSafe Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#Else
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem 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 CloseClipboard Lib "user32" () As Long
    Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function RegisterClipboardFormat Lib "user32" Alias "RegisterClipboardFormatA" (ByVal lpString As String) As Long
    Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#End If

Private bCellCut As Boolean
Private sClipBrdLinkData As String
Private vFirstCellVal As String
Private vLastCellVal As String

Private WithEvents oCmndBars As CommandBars


Private Sub Workbook_Open()
    Set oCmndBars = Application.CommandBars
End Sub

Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)

    With Target
        If Not IsError(.Cells(1, 1).Value) And Not IsError(.Cells(.Rows.Count, .Columns.Count).Value) Then
            If (.Cells(1, 1).Value <> vFirstCellVal) And _
            (.Cells(.Rows.Count, .Columns.Count).Value <> vLastCellVal) Then bCellCut = True Else bCellCut = False
        End If
    End With

End Sub

Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)

    Set oCmndBars = Application.CommandBars
    With Target
        If Not IsError(.Cells(1, 1).Value) And Not IsError(.Cells(.Rows.Count, .Columns.Count).Value) Then
            vFirstCellVal = .Cells(1, 1).Value
            vLastCellVal = .Cells(.Rows.Count, .Columns.Count).Value
        End If
    End With

End Sub

Private Sub oCmndBars_OnUpdate()

    Static lCopyCutMode As XlCutCopyMode
    Dim oCopyCutRange As Range
    Dim lFormatID As Long
    
    If (GetAsyncKeyState(VBA.vbKeyEscape) <> 0) Then
        sClipBrdLinkData = ""
        Exit Sub
    End If
    lFormatID = RegisterClipboardFormat("Link" & Chr$(0))
    Set oCopyCutRange = GetCopyCutRange(ClipBoard_GetLinkData(lFormatID))
    If Not oCopyCutRange Is Nothing Then
        If Application.CutCopyMode = 0 Then
            If lCopyCutMode = xlCut And bCellCut Then oCopyCutRange.Clear: sClipBrdLinkData = "": _
            Application.CutCopyMode = 0: Exit Sub
            If lCopyCutMode = xlCopy Then oCopyCutRange.Copy Else oCopyCutRange.Cut
        End If
    End If
    lCopyCutMode = Application.CutCopyMode

End Sub

Private Function ClipBoard_GetLinkData(wFormat As Long) As String
 
    #If VBA7 Then
        Dim hData       As LongPtr
        Dim lByteLen    As LongPtr
        Dim lPointer    As LongPtr
        Dim lSize       As LongLong
    #Else
        Dim hData       As Long
        Dim lByteLen    As Long
        Dim lPointer    As Long
        Dim lSize       As Long
    #End If
    Dim lRet        As Long
    Dim arData()    As Byte
    
    lRet = OpenClipboard(0)
    If lRet > 0 Then
        If IsClipboardFormatAvailable(wFormat) Then
            hData = GetClipboardData(wFormat)
            If hData <> 0 Then
                lByteLen = GlobalSize(hData)
                lSize = GlobalSize(hData)
                lPointer = GlobalLock(hData)
                If lSize > 0 Then
                    ReDim arData(0 To CLng(lSize) - CLng(1)) As Byte
                    CopyMemory arData(0), ByVal lPointer, lSize
                    GlobalUnlock hData
                    sClipBrdLinkData = StrConv(arData, vbUnicode)
                End If
            End If
        End If
        CloseClipboard
    End If
    
    ClipBoard_GetLinkData = sClipBrdLinkData

End Function

Private Function GetCopyCutRange(ByVal ClipLinkData As String) As Range

    Dim sWbk As String, sWsh As String, sRangeAddr As String
    Dim arRowsCols() As String, sRangeRows As String
    Dim lTopLeftRow As Long, lTopLeftCol As Long
    Dim lBottomRightRow As Long, lBottomRightCol As Long
    Dim i As Integer
    Dim oCopyCutRange As Range
    
    On Error Resume Next
    If InStrRev(ClipLinkData, Chr$(0) & "L", -1) Then
        Mid(ClipLinkData, InStrRev(ClipLinkData, Chr$(0) & "L"), 2) = Chr$(0) & "R"
        Mid(ClipLinkData, InStrRev(ClipLinkData, ":L"), 2) = ":" & "R"
    End If
    
    sWbk = Mid(ClipLinkData, InStr(ClipLinkData, "[") + 1, InStrRev(ClipLinkData, "]") - 1 - InStr(ClipLinkData, "["))
    sWsh = Mid(ClipLinkData, InStr(ClipLinkData, "]") + 1, InStrRev(ClipLinkData, Chr$(0) & "R") - 1 - InStr(ClipLinkData, "]"))
    sRangeAddr = Mid(ClipLinkData, InStrRev(ClipLinkData, Chr$(0) & "R") + 1, Len(ClipLinkData))
    arRowsCols = Split(sRangeAddr, ":")
    
    For i = 0 To UBound(arRowsCols)
        sRangeRows = Left(arRowsCols(i), InStr(arRowsCols(i), "C") - 1)
        If i = 0 Then
            lTopLeftRow = Replace(sRangeRows, "R", "")
            lTopLeftCol = Right(arRowsCols(i), Len(arRowsCols(i)) - InStr(arRowsCols(i), "C"))
            Set oCopyCutRange = Cells(lTopLeftRow, lTopLeftCol)
        Else
            lBottomRightRow = Replace(sRangeRows, "R", "")
            lBottomRightCol = Right(arRowsCols(i), Len(arRowsCols(i)) - InStr(arRowsCols(i), "C"))
            Set oCopyCutRange = Range(Cells(lTopLeftRow, lTopLeftCol), Cells(lBottomRightRow, lBottomRightCol))
        End If
    Next
    
    Set GetCopyCutRange = Workbooks(sWbk).Worksheets(sWsh).Range(oCopyCutRange.Address)
    
End Function
 
Last edited:
Upvote 0

Forum statistics

Threads
1,216,071
Messages
6,128,626
Members
449,460
Latest member
jgharbawi

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