I need a VBA macro for MS Word 2013 to save the embedded PDF attachments in the Word file into a folder.
I found a working solution in Excel which saves embedded files in the Excel document, I have made some modifications to work in Word VBA, but it doesn't work any ideas to make it work in Word ?
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare PtrSafe Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hWnd As Long) As Long
Private Declare PtrSafe Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
Private Declare PtrSafe Sub RtlMoveMemory Lib "kernel32" (Destination As Any, Source As Any, ByVal Length As Long)
Sub Embed_Files_Save_PDF_Run()
For Each file In ThisDocument.InlineShapes
Call Embed_Files_Save_PDF(file)
Next
End Sub
Sub Embed_Files_Save_PDF(ByVal Embedded_PDF)
On Error Resume Next
Dim PDF_Path As String
PDF_Path = ActiveDocument.Path
If Right$(PDF_Path, 1) <> Application.PathSeparator Then PDF_Path = PDF_Path & Application.PathSeparator
Dim PDF_Name As String
PDF_Name = UCase$(Left$(Embedded_PDF.OLEFormat.IconLabel, 1)) & Mid$(Embedded_PDF.OLEFormat.IconLabel, 2)
PDF_Name = PDF_Name & ".PDF"
Dim FileEOF As Long
Dim FileLOF As Long
Dim CB_Lock As Long ' ClipBoard Lock
Dim CB_Size As Long ' ClibBoard Size
Dim PDF_File() As Byte
Dim Temp_PDF() As Byte
Embedded_PDF.Copy
If OpenClipboard(0) Then
Counter = GetClipboardData(49156)
If Counter <> 0 Then CB_Size = GlobalSize(Counter)
If CB_Size <> 0 Then CB_Lock = GlobalLock(Counter)
If CB_Lock <> 0 Then
ReDim Temp_PDF(1 To CLng(CB_Size))
RtlMoveMemory Temp_PDF(1), ByVal CB_Lock, CB_Size
Call GlobalUnlock(Counter)
Counter = InStrB(Temp_PDF, StrConv("%PDF", vbFromUnicode))
If Counter > 0 Then
FileEOF = InStrB(Counter, Temp_PDF, StrConv("%%EOF", vbFromUnicode))
While FileEOF
FileLOF = FileEOF - Counter + 7
FileEOF = InStrB(FileEOF + 5, Temp_PDF, StrConv("%%EOF", vbFromUnicode))
Wend
ReDim PDF_File(1 To FileLOF)
For FileEOF = 1 To FileLOF
PDF_File(FileEOF) = Temp_PDF(Counter + FileEOF - 1)
Next
End If
End If
CloseClipboard
If Counter > 0 Then
Counter = FreeFile
Open PDF_Path & PDF_Name For Binary As #Counter
Put #Counter, 1, PDF_File
Close #Counter
End If
End If
Set Embedded_PDF = Nothing
End Sub
Any help would be appreciated.
try this
it does not save the pdf file but it opens it in acrobat so that you can save it
Sub pdfExtract()
' opens embedded pdf file in acrobat reader for saving
Dim shap As InlineShape
For Each shap In ActiveDocument.InlineShapes
If Not shap.OLEFormat Is Nothing Then
If shap.OLEFormat.ClassType = "AcroExch.Document.DC" Then
shap.OLEFormat.DoVerb wdOLEVerbOpen
End If
End If
Next shap
End Sub
Related
I'm currently making a ms access database and I have made a form where the user inputs data. I would like the user to be able to press a button which copies the label and the entered data so they can paste it elsewhere. I have found a project which achieves exactly what I want however I cannot seem to get it to work for my application. The code below is what I found online and this is the link to the thread. It is the one labeled copypaste.zip https://www.access-programmers.co.uk/forums/threads/copy-all-date-on-form-to-clipboard-to-user-can-past-this-into-another-system.309872/ .Thank you.
This is on the module code:
Option Compare Database
Option Explicit
Private Declare Function OpenClipboard Lib "user32.dll" (ByVal hWnd As Long) As Long
Private Declare Function EmptyClipboard Lib "user32.dll" () As Long
Private Declare Function CloseClipboard Lib "user32.dll" () As Long
Private Declare Function IsClipboardFormatAvailable Lib "user32.dll" (ByVal wFormat As Long) As Long
Private Declare Function GetClipboardData Lib "user32.dll" (ByVal wFormat As Long) As Long
Private Declare Function SetClipboardData Lib "user32.dll" (ByVal wFormat As Long, ByVal hMem As Long) As Long
Private Declare Function GlobalAlloc Lib "kernel32.dll" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Function GlobalLock Lib "kernel32.dll" (ByVal hMem As Long) As Long
Private Declare Function GlobalUnlock Lib "kernel32.dll" (ByVal hMem As Long) As Long
Private Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function lstrcpy Lib "kernel32.dll" Alias "lstrcpyW" (ByVal lpString1
As Long, ByVal lpString2 As Long) As Long
Public Sub SetClipboard(sUniText As String)
Dim iStrPtr As Long
Dim iLen As Long
Dim iLock As Long
Const GMEM_MOVEABLE As Long = &H2
Const GMEM_ZEROINIT As Long = &H40
Const CF_UNICODETEXT As Long = &HD
OpenClipboard 0&
EmptyClipboard
iLen = LenB(sUniText) + 2&
iStrPtr = GlobalAlloc(GMEM_MOVEABLE Or GMEM_ZEROINIT, iLen)
iLock = GlobalLock(iStrPtr)
lstrcpy iLock, StrPtr(sUniText)
GlobalUnlock iStrPtr
SetClipboardData CF_UNICODETEXT, iStrPtr
CloseClipboard
End Sub
Public Function GetClipboard() As String
Dim iStrPtr As Long
Dim iLen As Long
Dim iLock As Long
Dim sUniText As String
Const CF_UNICODETEXT As Long = 13&
OpenClipboard 0&
If IsClipboardFormatAvailable(CF_UNICODETEXT) Then
iStrPtr = GetClipboardData(CF_UNICODETEXT)
If iStrPtr Then
iLock = GlobalLock(iStrPtr)
iLen = GlobalSize(iStrPtr)
sUniText = String$(iLen \ 2& - 1&, vbNullChar)
lstrcpy StrPtr(sUniText), iLock
GlobalUnlock iStrPtr
End If
GetClipboard = sUniText
End If
CloseClipboard
End Function
This is on the form code:
Option Compare Database
Option Explicit
Private Sub Command6_Click()
Dim strSql As String
Dim ctl As Variant
For Each ctl In Me.Controls
If ctl.Tag = "?" Then
strSql = strSql & ctl.Controls(0).Caption & " " & Nz(ctl, "") & vbNewLine
End If
Next
Me.Text4 = ""
Me.Text4 = strSql
Me.Text7 = ""
SetClipboard strSql
End Sub
That is much code for nothing. This will do:
Private Sub CommandCopy_Click()
Dim Control As Control
Dim Value As String
For Each Control In Me.Controls
If Control.Tag = "?" Then
Value = Value & Control.Caption & " " & Nz(Control.Value) & vbNewLine
End If
Next
' Renamed Text4.
Me!ValueCopy.Value = Value
Me!ValueCopy.SetFocus
DoCmd.RunCommand acCmdCopy
End Sub
I am using Office 365 under Windows 10, 64 bit. I am trying to clear the clipboard. The macro recorder produces an empty sub.
The following attempts are mostly collected from How to Clear Office Clipboard with VBA :
Option Explicit
Public Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Public Declare Function EmptyClipboard Lib "user32" () As Long
Public Declare Function CloseClipboard Lib "user32" () As Long
Public Sub ClearClipboardA()
Application.CutCopyMode = False
End Sub
Public Sub ClearClipBoardB()
' Source: http://www.vbaexpress.com/kb/getarticle.php?kb_id=462
Dim oData As New DataObject
oData.SetText Text:=Empty ' Clear
oData.PutInClipboard ' Putting empty text into the clipboard to empty it
End Sub
Public Sub ClearClipboardC()
OpenClipboard (0&)
EmptyClipboard
CloseClipboard
End Sub
Version A:
Method or data member not found
Version B: Runs without clearing the clipboard. A small yellow status window appears shortly:
"7 of 24 - Clipboard | Element not fetched"
(translated into English)
Version C: Nothing seems to happen.
In the above reference user iamstrained writes: “... if anyone is hunting for how to do this in Office 365 under 64-Bit, you now need to use the modifications for backwards compatibility to make this work: Private Declare PtrSafe and LongPtr as your two changes to these values will resolve issues and allow it to still work.”
I found a reference to a Microsoft page, where this has perhaps been done:
https://learn.microsoft.com/da-dk/office/vba/access/Concepts/Windows-API/send-information-to-the-clipboard
Using subs shown here I can insert text into the clipboard and extract from it, but not clear it.
to clear the office Clipboard (from Excel):
#If VBA7 Then
Private Declare PtrSafe Function AccessibleChildren Lib "oleacc" (ByVal paccContainer As Office.IAccessible, _
ByVal iChildStart As Long, ByVal cChildren As Long, _
ByRef rgvarChildren As Any, ByRef pcObtained As Long) As Long
Public Const myVBA7 As Long = 1
#Else
Private Declare Function AccessibleChildren Lib "oleacc" (ByVal paccContainer As Office.IAccessible, _
ByVal iChildStart As Long, ByVal cChildren As Long, _
ByRef rgvarChildren As Any, ByRef pcObtained As Long) As Long
Public Const myVBA7 As Long = 0
#End If
Public Sub EvRClearOfficeClipBoard()
Dim cmnB, IsVis As Boolean, j As Long, Arr As Variant
Arr = Array(4, 7, 2, 0) '4 and 2 for 32 bit, 7 and 0 for 64 bit
Set cmnB = Application.CommandBars("Office Clipboard")
IsVis = cmnB.Visible
If Not IsVis Then
cmnB.Visible = True
DoEvents
End If
For j = 1 To Arr(0 + myVBA7)
AccessibleChildren cmnB, Choose(j, 0, 3, 0, 3, 0, 3, 1), 1, cmnB, 1
Next
cmnB.accDoDefaultAction CLng(Arr(2 + myVBA7))
Application.CommandBars("Office Clipboard").Visible = IsVis
End Sub
I can confirm that the code below clears the Windows clipboard
#If Win64 Then
Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
#Else
Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Declare Function CloseClipboard Lib "user32" () As Long
Declare Function EmptyClipboard Lib "user32" () As Long
#End If
Public Sub ClearClipboard()
OpenClipboard (0&)
EmptyClipboard
CloseClipboard
End Sub
You can download the pointer safe declarations from https://www.microsoft.com/en-us/download/confirmation.aspx?id=9970
I've used this above code snippet and it worked well until recent software updates that prevented me to clear the office clipboard without opening clipboard window. My solution it's very simple - add just this to the code:
#If VBA7 Then
Private Declare PtrSafe Function AccessibleChildren Lib "oleacc" (ByVal paccContainer As Office.IAccessible, _
ByVal iChildStart As Long, ByVal cChildren As Long, _
ByRef rgvarChildren As Any, ByRef pcObtained As Long) As Long
Public Const myVBA7 As Long = 1
#Else
Private Declare Function AccessibleChildren Lib "oleacc" (ByVal paccContainer As Office.IAccessible, _
ByVal iChildStart As Long, ByVal cChildren As Long, _
ByRef rgvarChildren As Any, ByRef pcObtained As Long) As Long
Public Const myVBA7 As Long = 0
#End If
Public Sub EvRClearOfficeClipBoard()
Dim cmnB, IsVis As Boolean, j As Long, Arr As Variant
Arr = Array(4, 7, 2, 0) '4 and 2 for 32 bit, 7 and 0 for 64 bit
Set cmnB = Application.CommandBars("Office Clipboard")
'Just add here...First
'---------------------
With Application
.DisplayClipboardWindow = True
End With
IsVis = cmnB.Visible
If Not IsVis Then
cmnB.Visible = True
DoEvents
End If
For j = 1 To Arr(0 + myVBA7)
AccessibleChildren cmnB, Choose(j, 0, 3, 0, 3, 0, 3, 1), 1, cmnB, 1
Next
cmnB.accDoDefaultAction CLng(Arr(2 + myVBA7))
Application.CommandBars("Office Clipboard").Visible = IsVis
'And finish with this
'--------------------
With Application
.DisplayClipboardWindow = False
End With
End Sub
EvR's macro to clear the Office Clipboard is very clever. (See above.) It works in Excel with my 64-bit Microsoft (Office) 365 and Windows 10.
VBA7 was introduced with Office 2010; everyone should have it by now. The vba7 directive does not distinguish 64-bit; the win64 directive does that, if it was needed. So here is my version of EvR's macro:
Declare PtrSafe Function AccessibleChildren Lib "oleacc" ( _
ByVal paccContainer As Office.IAccessible, _
ByVal iChildStart As Long, ByVal cChildren As Long, _
ByRef rgvarChildren As Any, ByRef pcObtained As Long) As Long
Sub ClearOfficeClipboard()
Dim A() As Variant, CB As Variant, n As Variant, i As Integer
With Application
If Not .DisplayClipboardWindow Then
ClearClipboard
.DisplayClipboardWindow = True
ClearOfficeClipboard ' recurse
Exit Sub
End If
On Error GoTo ErrHandler
Set CB = .CommandBars("Office Clipboard")
A = Array(0, 3, 0, 3, 0, 3, 1)
For i = 0 To UBound(A)
AccessibleChildren CB, A(i), 1, CB, n
Next i
CB.accDoDefaultAction CLng(0)
End With
ErrHandler:: Set CB = Nothing
End Sub
The ClearClipboard macro is from Chip Pearson, http://www.cpearson.com/Excel/Clipboard.aspx. (See Timothy Rylatt above, but substitute vba7 in place of win64.)
I don't understand the For loop that calls AccessibleChildren. Can someone explain how it works? Note that CB must be Variant, not CommandBar or Object.
So I wrote a 180 page program in VBA that is fairly robust and works great, except the clipboard doesn't work when running simultaneously with any VNC viewer (RealVNC, logmein, etc.) After digging around I found this was a somewhat known but rare issue, so Windows wrote API code to utilize getting data from and sending data to the clipboard:
http://msdn.microsoft.com/en-us/library/office/dn124100.aspx
Function ClipBoard_SetData(MyString As String) works fine with any data set I've tried. And Function ClipBoard_GetData() works fine with a small data set, but when I try it with a 80KB data set it crashes excel every time.
Any help with running VBA next to a VNC viewer or Win API code would be greatly appreciated. Here is WinAPI code:
Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) _
As Long
Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) _
As Long
Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, _
ByVal dwBytes As Long) As Long
Declare Function CloseClipboard Lib "User32" () As Long
Declare Function OpenClipboard Lib "User32" (ByVal hwnd As Long) _
As Long
Declare Function EmptyClipboard Lib "User32" () As Long
Declare Function lstrcpy Lib "kernel32" (ByVal lpString1 As Any, _
ByVal lpString2 As Any) As Long
Declare Function SetClipboardData Lib "User32" (ByVal wFormat _
As Long, ByVal hMem As Long) As Long
Declare Function GetClipboardData Lib "User32" (ByVal wFormat As _
Long) As Long
Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) _
As Long
Public Const GHND = &H42
Public Const CF_TEXT = 1
Public Const MAXSIZE = 4096
Function ClipBoard_SetData(MyString As String)
Dim hGlobalMemory As Long, lpGlobalMemory As Long
Dim hClipMemory As Long, X As Long
' Allocate moveable global memory.
'-------------------------------------------
hGlobalMemory = GlobalAlloc(GHND, Len(MyString) + 1)
' Lock the block to get a far pointer
' to this memory.
lpGlobalMemory = GlobalLock(hGlobalMemory)
' Copy the string to this global memory.
lpGlobalMemory = lstrcpy(lpGlobalMemory, MyString)
' Unlock the memory.
If GlobalUnlock(hGlobalMemory) <> 0 Then
MsgBox "Could not unlock memory location. Copy aborted."
GoTo OutOfHere2
End If
' Open the Clipboard to copy data to.
If OpenClipboard(0&) = 0 Then
MsgBox "Could not open the Clipboard. Copy aborted."
Exit Function
End If
' Clear the Clipboard.
X = EmptyClipboard()
' Copy the data to the Clipboard.
hClipMemory = SetClipboardData(CF_TEXT, hGlobalMemory)
OutOfHere2:
If CloseClipboard() = 0 Then
MsgBox "Could not close Clipboard."
End If
End Function
Function ClipBoard_GetData()
Dim hClipMemory As Long
Dim lpClipMemory As Long
Dim MyString As String
Dim RetVal As Long
If OpenClipboard(0&) = 0 Then
MsgBox "Cannot open Clipboard. Another app. may have it open"
Exit Function
End If
' Obtain the handle to the global memory
' block that is referencing the text.
hClipMemory = GetClipboardData(CF_TEXT)
If IsNull(hClipMemory) Then
MsgBox "Could not allocate memory"
GoTo OutOfHere
End If
' Lock Clipboard memory so we can reference
' the actual data string.
lpClipMemory = GlobalLock(hClipMemory)
If Not IsNull(lpClipMemory) Then
MyString = Space$(MAXSIZE)
RetVal = lstrcpy(MyString, lpClipMemory)
RetVal = GlobalUnlock(hClipMemory)
' Peel off the null terminating character.
MyString = Mid(MyString, 1, InStr(1, MyString, Chr$(0), 0) - 1)
Else
MsgBox "Could not lock memory to copy string from."
End If
OutOfHere:
RetVal = CloseClipboard()
ClipBoard_GetData = MyString
End Function
You allocate a string of MAXSIZE (4096) spaces and use lstrcpy to override it with the clipboard content. No surprise if Excel crash when there is 80k in the Clipboard. Use lstrlen to know the size of the actual content of the clipboard.
I want to get an absolute path to images folder with the image name (e.g \image1.jpg) at the end of the path, where ImagePath is the name of the image path field in the table. I am just not sure how to correctly format it.
How would I do this?
Here is what I have tried already:
=IIf(IsNull([ImagePath]),Null,GetPath() & "C:\Criminal Records Database\Persons_Images\" & [ImagePath])
GetUNCPath is a method to translate any path into the a Universal Naming Convention path, across network drives. It will return a local drive as an absolute path if not networked. I use this function to guarantee I have a full absolute path.
I wrote the code below (with some assistance from #GSerg) to make it easy to convert a path into a full absolute UNC path.
Usage
Dim fullPath as string
fullPath = GetUNCPath("T:\SomeDir\SomeFile.Txt")
It will convert T:\SomeDir\SomeFile.Txt into \\SomeServer\SomeShare\SomeDir\SomeFile.Txt
This has been tested on Access 2003 and Access 2010. It is 32-bit and 64-bit compatible.
Module: GetUNC
Option Compare Database
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Function WNetGetConnection Lib "mpr.dll" Alias "WNetGetConnectionW" (ByVal lpLocalName As LongPtr, ByVal lpRemoteName As Long, lpnLength As Long) As Long
Private Declare PtrSafe Function PathIsUNC Lib "shlwapi.dll" Alias "PathIsUNCW" (ByVal pszPath As LongPtr) As Long
Private Declare PtrSafe Function PathIsNetworkPath Lib "shlwapi.dll" Alias "PathIsNetworkPathW" (ByVal pszPath As LongPtr) As Long
Private Declare PtrSafe Function PathStripToRoot Lib "shlwapi.dll" Alias "PathStripToRootW" (ByVal pPath As LongPtr) As LongPtr
Private Declare PtrSafe Function PathSkipRoot Lib "shlwapi.dll" Alias "PathSkipRootW" (ByVal pPath As LongPtr) As Long
Private Declare PtrSafe Function PathRemoveBackslash Lib "shlwapi.dll" Alias "PathRemoveBackslashW" (ByVal strPath As LongPtr) As LongPtr
#Else
Private Declare Function WNetGetConnection Lib "mpr.dll" Alias "WNetGetConnectionW" (ByVal lpLocalName As Long, ByVal lpRemoteName As Long, lpnLength As Long) As Long
Private Declare Function PathIsUNC Lib "shlwapi.dll" Alias "PathIsUNCW" (ByVal pszPath As Long) As Long
Private Declare Function PathIsNetworkPath Lib "shlwapi.dll" Alias "PathIsNetworkPathW" (ByVal pszPath As Long) As Long
Private Declare Function PathStripToRoot Lib "shlwapi.dll" Alias "PathStripToRootW" (ByVal pPath As Long) As Long
Private Declare Function PathSkipRoot Lib "shlwapi.dll" Alias "PathSkipRootW" (ByVal pPath As Long) As Long
Private Declare Function PathRemoveBackslash Lib "shlwapi.dll" Alias "PathRemoveBackslashW" (ByVal strPath As Long) As Long
#End If
Public Function GetUNCPath(sLocalPath As String) As String
Dim lResult As Long
#If VBA7 Then
Dim lpResult As LongPtr
#Else
Dim lpResult As Long
#End If
Dim ASLocal As APIString
Dim ASPath As APIString
Dim ASRoot As APIString
Dim ASRemoteRoot As APIString
Dim ASTemp As APIString
Set ASLocal = New APIString
ASLocal.Value = sLocalPath
If ASLocal.Pointer > 0 Then
lResult = PathIsUNC(ASLocal.Pointer)
End If
If lResult <> 0 Then
GetUNCPath = ASLocal.Value
Exit Function
End If
If ASLocal.Pointer > 0 Then
lResult = PathIsNetworkPath(ASLocal.Pointer)
End If
If lResult = 0 Then
GetUNCPath = ASLocal.Value
Exit Function
End If
' Extract Root
Set ASRoot = New APIString
ASRoot.Value = sLocalPath
If ASRoot.Length = 2 And Mid(ASRoot.Value, 2, 1) = ":" Then
' We have a Root with no Path
Set ASPath = New APIString
ASPath.Value = ""
Else
If ASRoot.Pointer > 0 Then
lpResult = PathStripToRoot(ASRoot.Pointer)
End If
ASRoot.TruncToNull
If ASRoot.Pointer > 0 And Mid(ASRoot.Value, ASRoot.Length) = "\" Then
lpResult = PathRemoveBackslash(ASRoot.Pointer)
ASRoot.TruncToPointer lpResult
End If
' Extract Path
Set ASPath = New APIString
ASPath.Value = sLocalPath
lpResult = PathSkipRoot(ASPath.Pointer)
ASPath.TruncFromPointer lpResult
If ASPath.Length > 0 Then
If ASPath.Pointer > 0 And Mid(ASPath.Value, ASPath.Length) = "\" Then
lpResult = PathRemoveBackslash(ASPath.Pointer)
ASPath.TruncToPointer lpResult
End If
End If
End If
' Resolve Local Root into Remote Root
Set ASRemoteRoot = New APIString
ASRemoteRoot.Init 255
If ASRoot.Pointer > 0 And ASRemoteRoot.Pointer > 0 Then
lResult = WNetGetConnection(ASRoot.Pointer, ASRemoteRoot.Pointer, LenB(ASRemoteRoot.Value))
End If
ASRemoteRoot.TruncToNull
GetUNCPath = ASRemoteRoot.Value & ASPath.Value
End Function
Class Module: APIString
Option Compare Database
Option Explicit
Private sBuffer As String
Private Sub Class_Initialize()
sBuffer = vbNullChar
End Sub
Private Sub Class_Terminate()
sBuffer = ""
End Sub
Public Property Get Value() As String
Value = sBuffer
End Property
Public Property Let Value(ByVal sNewStr As String)
sBuffer = sNewStr
End Property
' Truncates Length
#If VBA7 Then
Public Sub TruncToPointer(ByVal lpNewUBound As LongPtr)
#Else
Public Sub TruncToPointer(ByVal lpNewUBound As Long)
#End If
Dim lpDiff As Long
If lpNewUBound <= StrPtr(sBuffer) Then Exit Sub
lpDiff = (lpNewUBound - StrPtr(sBuffer)) \ 2
sBuffer = Mid(sBuffer, 1, lpDiff)
End Sub
' Shifts Starting Point forward
#If VBA7 Then
Public Sub TruncFromPointer(ByVal lpNewLBound As LongPtr)
#Else
Public Sub TruncFromPointer(ByVal lpNewLBound As Long)
#End If
Dim lDiff As Long
If lpNewLBound <= StrPtr(sBuffer) Then Exit Sub
If lpNewLBound >= (StrPtr(sBuffer) + LenB(sBuffer)) Then
sBuffer = ""
Exit Sub
End If
lDiff = (lpNewLBound - StrPtr(sBuffer)) \ 2
sBuffer = Mid(sBuffer, lDiff)
End Sub
Public Sub Init(Size As Long)
sBuffer = String(Size, vbNullChar)
End Sub
Public Sub TruncToNull()
Dim lPos As Long
lPos = InStr(sBuffer, vbNullChar)
If lPos = 0 Then Exit Sub
sBuffer = Mid(sBuffer, 1, lPos - 1)
End Sub
Public Property Get Length() As Long
Length = Len(sBuffer)
End Property
#If VBA7 Then
Public Property Get Pointer() As LongPtr
#Else
Public Property Get Pointer() As Long
#End If
Pointer = StrPtr(sBuffer)
End Property
If the Clipboard contains an Excel Worksheet Range, you can access that Range's Data with the DataObject Object
Can you also find the actual Source Range (ie Worksheet, Row & Column) of that Data?
Alternatively, can you find the Last Copied Range, which is indicated with a Dashed Outline Border (NOT the Selected Range)?
Preferably using Excel 2003 VBA
This code is being used in Excel 2019 64 bit to get the range of the cells on the clipboard as opposed to the contents of the cells.
fGetClipRange returns a range object for the Excel range that is cut or copied onto the clipboard, including book and sheet. It reads it directly from the clipboard using the "Link" format, and requires the ID number for this format. The ID associated with the registered formats can change, so fGetFormatId finds the current format ID from a format name. Use Application.CutCopyMode to determine whether the cells were cut or copied.
This site was useful for working with the clipboard in VBA: https://social.msdn.microsoft.com/Forums/office/en-US/ee9e0d28-0f1e-467f-8d1d-1a86b2db2878/a-clipboard-object-for-vba-including-microsoft-word?forum=worddev
Private Declare PtrSafe Function lstrcpy Lib "kernel32" (ByVal lpString1 As Any, ByVal lpString2 As Any) As LongPtr
Private Declare PtrSafe Function GlobalLock 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 OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal lngFormat As Long) As LongPtr
Private Declare PtrSafe Function EnumClipboardFormats Lib "user32" (ByVal wFormat As Long) As Long
Private Declare PtrSafe Function GetClipboardFormatNameA Lib "user32" (ByVal wFormat As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long
'2020-02-11 get excel copy or cut range from clipboard
Function fGetClipRange() As Range
Dim strGetClipRange As String 'return range
Dim lptClipData As LongPtr 'pointer to clipboard data
Dim strClipData As String 'clipboard data
Dim intOffset As Integer 'for parsing clipboard data
Dim lngRangeLink As Long 'clipboard format
Const intMaxSize As Integer = 256 'limit for r1c1 to a1 conversion
lngRangeLink = fGetFormatId("Link") 'we need the id number for link format
If OpenClipboard(0&) = 0 Then GoTo conDone 'could not open clipboard
lptClipData = GetClipboardData(lngRangeLink) 'pointer to clipboard data
If IsNull(lptClipData) Then GoTo conDone 'could not allocate memory
lptClipData = GlobalLock(lptClipData) 'lock clipboard memory so we can reference
If IsNull(lptClipData) Then GoTo conDone 'could not lock clipboard memory
intOffset = 0 'start parsing data
strClipData = Space$(intMaxSize) 'initialize string
Call lstrcpy(strClipData, lptClipData + intOffset) 'copy pointer to string
If strClipData = Space$(intMaxSize) Then GoTo conDone 'not excel range on clipboard
strClipData = Mid(strClipData, 1, InStr(1, strClipData, Chr$(0), 0) - 1) 'trim null character
If strClipData <> "Excel" Then GoTo conDone 'not excel range on clipboard
intOffset = intOffset + 1 + Len(strClipData) 'can't retrieve string past null character
strClipData = Space$(intMaxSize) 'reset string
Call lstrcpy(strClipData, lptClipData + intOffset) 'book and sheet next
strClipData = Mid(strClipData, 1, InStr(1, strClipData, Chr$(0), 0) - 1)
strGetClipRange = "'" & strClipData & "'!" 'get book and sheet
intOffset = intOffset + 1 + Len(strClipData) 'next offset
strClipData = Space$(intMaxSize) 'initialize string
Call lstrcpy(strClipData, lptClipData + intOffset) 'range next
strClipData = Mid(strClipData, 1, InStr(1, strClipData, Chr$(0), 0) - 1)
strGetClipRange = strGetClipRange & strClipData 'add range
strGetClipRange = Application.ConvertFormula(strGetClipRange, xlR1C1, xlA1)
Set fGetClipRange = Range(strGetClipRange) 'range needs a1 style
conDone:
Call GlobalUnlock(lptClipData)
Call CloseClipboard
End Function
'2020-02-11 clipboard format id number changes so get it from format name
Function fGetFormatId(strFormatName As String) As Long
Dim lngFormatId As Long
Dim strFormatRet As String
Dim intLength As Integer
If OpenClipboard(0&) = 0 Then Exit Function 'could not open clipboard
intLength = Len(strFormatName) + 3 'we only need a couple extra to make sure there isn't more
lngFormatId = 0 'start at zero
Do
strFormatRet = Space(intLength) 'initialize string
GetClipboardFormatNameA lngFormatId, strFormatRet, intLength 'get the name for the id
strFormatRet = Trim(strFormatRet) 'trim spaces
If strFormatRet <> "" Then 'if something is left
strFormatRet = Left(strFormatRet, Len(strFormatRet) - 1) 'get rid of terminal character
If strFormatRet = strFormatName Then 'if it matches our name
fGetFormatId = lngFormatId 'this is the id number
Exit Do 'done
End If
End If
lngFormatId = EnumClipboardFormats(lngFormatId) 'get the next used id number
Loop Until lngFormatId = 0 'back at zero after last id number
Call CloseClipboard 'close clipboard
End Function
Not directly, no - the clipboard object seems to only contain the values of the cells (though Excel obviously somehow remembers the border):
Sub testClipborard()
Dim test As String
Dim clipboard As MSForms.DataObject
Set clipboard = New MSForms.DataObject
clipboard.GetFromClipboard
test = clipboard.GetText
MsgBox (test)
End Sub
Note you will need a reference to the Microsoft Forms 2.0 Library to get this to run (and if you don't have values in the cells it will also fail).
That being said, you can try something like the following - add this to a module in the VBA editor.
Public NewRange As String
Public OldRange As String
Public SaveRange As String
Public ChangeRange As Boolean
And use the following in a sheet object
Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)
'save previous selection
OldRange = NewRange
'get current selection
NewRange = Selection.Address
'check if copy mode has been turned off
If Application.CutCopyMode = False Then
ChangeRange = False
End If
'if copy mode has been turned on, save Old Range
If Application.CutCopyMode = 1 And ChangeRange = False Then
'boolean to hold "SaveRange" address til next copy/paste operation
ChangeRange = True
'Save last clipboard contents range address
SaveRange = OldRange
End If
End Sub
It seemingly works, but, it's also probably fairly prone to different bugs as it is attempting to get around the issues with the clipboard. http://www.ozgrid.com/forum/showthread.php?t=66773
I completely rewrote the previous answer because I needed to get other kinds of data into Excel besides ranges. The new code is more versatile, and gets different formats off the clipboard as strings. Extracting the Excel range ends up being much simpler, and I'm also using it for bitmaps and text.
The last routine gets the number for non-built-in formats. The middle routine gets the clipboard contents as a string for the specified format. The first routine parses the Excel range from this string with the split function.
'https://officeaccelerators.wordpress.com/2013/11/27/reading-data-with-format-from-clipboard/
'https://social.msdn.microsoft.com/Forums/sqlserver/en-US/ee9e0d28-0f1e-467f-8d1d-1a86b2db2878/a-clipboard-object-for-vba-including-microsoft-word?forum=worddev
#If VBA7 And Win64 Then
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.dll" Alias "RegisterClipboardFormatA" (ByVal lpString As String) As Long
Private Declare PtrSafe Function GlobalLock Lib "kernel32.dll" (ByVal hMem As LongPtr) As LongPtr
Private Declare PtrSafe Function GlobalUnlock Lib "kernel32.dll" (ByVal hMem As LongPtr) As Long
Private Declare PtrSafe Function GlobalSize Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
#Else
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 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 Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cbCopy As Long)
#End If
'test routine displays a message box with the marching ants range
'_2022_10_30
Function fTest_GetClipboardRange()
Dim rngClipboard As Range
Set rngClipboard = fGetClipboardRange
If rngClipboard Is Nothing Then
MsgBox ("No Excel range was found on the clipboard.")
ElseIf Application.CutCopyMode = xlCopy Then 'this is always copy because of sheet add
MsgBox (fGetClipboardRange.Address & " has been copied to the clipboard.")
ElseIf Application.CutCopyMode = xlCut Then
MsgBox (fGetClipboardRange.Address & " has been cut to the clipboard.")
End If
End Function
'reads excel copy-paste range from the clipboard and returns range object or nothing if not found
'_2022_03_19
Function fGetClipboardRange() As Range 'get excel copy or cut range from clipboard
Dim strClipboard As String 'raw clipboard data
Dim arrClipboard() As String 'parse into an array
Set fGetClipboardRange = Nothing 'default is nothing
strClipboard = fGetClipboardData("link") 'get the link data string
If strClipboard = "" Then Exit Function 'done if it's empty
arrClipboard = Split(strClipboard, Chr(0)) 'else parse at null characters
If arrClipboard(0) <> "Excel" Then Exit Function 'excel should be first
strClipboard = "'" & arrClipboard(1) & "'!" & arrClipboard(2) 'parse the range from the others
strClipboard = Application.ConvertFormula(strClipboard, xlR1C1, xlA1) 'convert to a1 style
Set fGetClipboardRange = Range(strClipboard) 'range needs a1 style
End Function
'read clipboard for specified format into string or null string
'_2022_03_19
Function fGetClipboardData(strFormatId As String) As String 'read clipboard into string
#If VBA7 And Win64 Then
Dim hMem As LongPtr 'memory handle
Dim lngPointer As LongPtr 'memory pointer
#Else
Dim hMem As Long 'memory handle
Dim lngPointer As Long 'memory pointer
#End If
Dim arrData() As Byte 'clipboard reads into this array
Dim lngSize As Long 'size on clipboard
Dim lngFormatId As Long 'id number, for format name
fGetClipboardData = "" 'default
lngFormatId = fGetClipboardFormat(strFormatId) 'get format
If lngFormatId <= 0 Then Exit Function 'zero if format not found
CloseClipboard 'in case clipboard is open
If CBool(OpenClipboard(0)) Then 'open clipboard
hMem = GetClipboardData(lngFormatId) 'get memory handle
If hMem > 0 Then 'if there's a handle
lngSize = CLng(GlobalSize(hMem)) 'get memory size
If lngSize > 0 Then 'if we know the size
lngPointer = GlobalLock(hMem) 'get memory pointer
If lngPointer > 0 Then 'make sure we have the pointer
ReDim arrData(0 To lngSize - 1) 'size array
CopyMemory arrData(0), ByVal lngPointer, lngSize 'data from pointer to array
fGetClipboardData = StrConv(arrData, vbUnicode) 'convert array to string
End If
GlobalUnlock hMem 'unlock memory
End If
End If
End If
CloseClipboard 'don't leave the clipboard open
End Function
'return format number form format number, format number from format name or 0 for not found
'_2022_03_19
Function fGetClipboardFormat(strFormatId As String) As Long 'verify, or get format number from format name
Dim lngFormatId As Long 'format id number
fGetClipboardFormat = 0 'default false
If IsNumeric(strFormatId) Then 'for format number
lngFormatId = CLng(strFormatId) 'use number for built in format
CloseClipboard 'in case clipboard is already open
If CBool(OpenClipboard(0)) = False Then 'done if can't open clipboard
ElseIf CBool(IsClipboardFormatAvailable(lngFormatId)) = True Then 'true if format number found
fGetClipboardFormat = lngFormatId 'return format number
End If
CloseClipboard 'don't leave the clipboard open
Else
lngFormatId = RegisterClipboardFormat(strFormatId & Chr(0)) 'else get number from format name
If (lngFormatId > &HC000&) Then fGetClipboardFormat = lngFormatId 'if valid return format number
End If
End Function