Experiencing problems with copy/paste excel objects/shapes with VBA - vba

I have some complex code which takes some user inputs (names of shapes to copy) then copies said shapes from one sheet to another multiple times. The items are grouped shapes drawn in Excel and all named correctly and uniquely.
I receive copy and paste errors intermittently "Method 'Paste' of object _Worksheet' failed" and "Method 'copy' Of Object '_worksheet' Failed. Through researching the problem we understand that it is fairly common and has something to do with programmes which conflict with Excel when they are accessing the clipboard.
So far, my colleague and I have deduced that 2 programmes in particular interfere the most with the copy/paste operations - Adobe Reader and Autodesk Powershape. (Autodesk and Adobe both have Reference Libraries available within VBA, not sure if this is a coincidence?)
The problem used to occur very frequently whilst we had the programmes open, so we wrote the following macros/functions to try and stablise the code:-
Public Sub CopyShape(ItemName, CopyDestination)
Call ClearClipboard Sheets(CopyDestination).Shapes(ItemName).Copy
Do Until IsClipboardEmpty = False DoEvents Loop
End Sub
Where "ClearClipboard" is:-
Public Function ClearClipboard()
OpenClipboard (0&)
EmptyClipboard
CloseClipboard
End Function
and the function IsClipboardEmpty is:-
Function IsClipboardEmpty() As Boolean
IsClipboardEmpty = (CountClipboardFormats() = 0)
End Function
with the following public declarations:-
Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Declare Function EmptyClipboard Lib "user32" () As Long
Declare Function CloseClipboard Lib "user32" () As Long
Public Declare Function CountClipboardFormats Lib "user32" () As Long
This code works quite a lot of the time (far better than trying to use "DoEvents" after the copy operation which just failed miserably) as it forces the code to check if the copied item is in the clipboard before trying to paste it, but it doesn't always work - something in the background still messes up the code.
Is there any way of either:-
locking and unlocking the clipboard using VBA or APIs?
using a completely different method of copying and pasting the shapes?
Any and all solutions welcome and of course happy to answers any questions.
Thanks

Related

How to "flatten" a PDF via "Microsoft Print To PDF" using Win32 API calls in VBA?

I am trying to have my Excel VBA code read an interactive PDF (i.e. a PDF with dropdowns, calendars...etc.). I normally read PDF files by opening them in Word; however, Word removes all the interactive controls while rendering the file (e.g. a calendar control gets completely removed...along with its value).
I know that I can "flatten" the PDF by opening it in Adobe Acrobat Reader (or any browser) and printing it to a PDF (thus replacing all the controls with their selected values). The question is how do I do this programmatically in VBA. I know there are thousands of questions on S.O. about this topic; but, all of the answers either require installing Adobe Acrobat Pro, using a third-party installed application (e.g. CutePDF), submitting the file to an online API, or most commonly, using SendKeys or Win32 API calls to interact with the "Save As" dialog box that appears. The code I am writing will be distributed to multiple users; so, any additional application installations is out of the question (both Acrobat Pro and other third-party conversion tools). The files contain proprietary data; so, an online API is out as well. As far as SendKeys or SendMessage is concerned...frankly, there's got to be a better way.
I've tried numerous methods including Chrome's headless "print-to-pdf"; but it appears that only accepts HTML files (I even tried embedding the PDF into an HTML file but it still did not work).
The closest I've gotten is by using the below code to send data directly to the "Microsoft Print to PDF" driver (it's based on the process described here). The code successfully creates a PDF file...but it's a zero byte file. It seems that the data read from the input file is not being properly accepted by the "WritePrinter" function (although no error occurs). Although the documentation of the "WritePrinter" function states that the "pBuf" parameter should be a byte array, the person who asked this question was passing a string and getting it to work (understanding that they were not trying to print to a GDI printer). My code still just produces a blank PDF file if I convert the byte array to a string or even just read the contents of the input file using FSO's "ReadAll" method.
It is also worth nothing that the return value of the "WritePrinter" function is "1" and the value of the "pcWritten" output is the correct number of bytes...it's just that the PDF file that is produced has a file size of 0 bytes.
So, can anyone figure out how to get the "WritePrinter" function to accept the data being read from the input file?
By the way, an enormous gold star to anyone who can figure this out because, based on my research, the internet is begging for a way to do this without using Acrobat Pro or having to interact with the "Save As" dialog!
UPDATE#1: I have found a few posts online where users are experiencing this issue of the "Microsoft Print to PDF" driver generating blank files manually (here and here). Apparently the primary culprit is special characters in the input file name. I wanted to make it clear that I do not believe this is the issue I am having, as I can print to PDF perfectly fine manually...just not via the code in this post. (Also, just so it's been said, neither the input file path/name nor output file path/name contains special characters). Also, I don't believe it's an issue with the specific install of the print driver (as suggested in some posts) as my code creates 0 byte files on 3 different computers with 3 different OS builds of Windows (18363.1082, 18363.1139, and 19041.572)
UPDATE#2: In my continued research of this issue I found this post on MSDN's Visual Studio help forums. I understand it's for C# but one of the contributers states:
So you should convert managed byte array into unmanaged array, then invoke the method
He provides C# code that uses the "Marshal.AllocCoTaskMem" and "Marshal.Copy" functions to "Copy the managed byte array into the unmanaged array". I'm not familiar with the terms "managed" or "unmanaged" byte arrays so I will continue to do some research on those.
Does anyone have any experience with the "Marshal.AllocCoTaskMem" and "Marshal.Copy" functions from VBA within Excel (VB6)?
UPDATE#3: It has been brought to my attention that the code I've written will ONLY print XPS files to a PDF. I've converted my original interactive PDF to an XPS manually and confirmed that my code worked perfectly in writing that XPS to a PDF.
This now leaves me back at square one: how do I programmatically read an interactive PDF without utilizing any third-party applications or online converters?
Anyone have any ideas?
Type DOCINFO
lpszDocName As String
lpszOutput As String
lpszDatatype As String
End Type
Private Declare PtrSafe Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, ByVal pDefault As Long) As Long
Private Declare PtrSafe Function StartDocPrinter Lib "winspool.drv" Alias "StartDocPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, pDocInfo As DOCINFO) As Long
Private Declare PtrSafe Function StartPagePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function WritePrinter Lib "winspool.drv" (ByVal hPrinter As Long, pBuf As Any, ByVal cdBuf As Long, pcWritten As Long) As Long
Private Declare PtrSafe Function EndPagePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function EndDocPrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Public Sub Print_File_To_PDF(strInputFile As String, strOutputPDF As String)
Dim udtDocInfo As DOCINFO
Dim lngPrinterCheck As Long, lngFileNumber As Long, lngPrinterHandle As Long, lngPrinterOutput as Long
Dim arrBytes() As Byte
'Get handle of printer
lngPrinterCheck = OpenPrinter("Microsoft Print To PDF", lngPrinterHandle, 0)
If lngPrinterCheck = 0 Then
Exit Sub
End If
'Define document info
With udtDocInfo
.lpszDocName = CreateObject("Scripting.FileSystemObject").GetBaseName(strOutputPDF)
.lpszOutput = strOutputPDF
.lpszDatatype = "RAW"
End With
'Read file into byte array
lngFileNumber = FreeFile
Open strInputFile For Binary Access Read As lngFileNumber
ReDim arrBytes(LOF(lngFileNumber))
Get lngFileNumber, , arrBytes()
Close lngFileNumber
'Print byte array to PDF file
Call StartDocPrinter(lngPrinterHandle, 1, udtDocInfo)
Call StartPagePrinter(lngPrinterHandle)
Call WritePrinter(lngPrinterHandle, arrBytes(1), UBound(arrBytes), lngPrinterOutput)
Call EndPagePrinter(lngPrinterHandle)
Call EndDocPrinter(lngPrinterHandle)
Call ClosePrinter(lngPrinterHandle)
End Sub

Copy/Paste in PowerPoint VBA causing errors

So, I've been banging my head against this one for a few days now, and figured it couldn't hurt to see if someone else has a solution.
I'm programming a macro in VBA for PowerPoint. Amongst other things, it needs to copy and paste multiple shapes (approximately 40 every time it's run). However, it randomly stops with an error:
This happens at random points during execution, not at the same point every time; sometimes it will instead paste the previous shape, causing other issues; and sometimes it will fully execute with no problems. I have tried numerous suggested fixes that I've found here and on other sites, and nothing seems to be working.
Right now, this is the relevant bit of code:
ClearClipboard
newPriorityShape.Copy
DoEvents
Set pastedShp = curSlide.Shapes.PasteSpecial(ppPasteDefault)
ClearClipboard
And for ClearClipboard I have:
Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Private Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
Public Function ClearClipboard()
OpenClipboard (0&)
EmptyClipboard
CloseClipboard
End Function
If anyone has any other suggestions, I'd be most grateful.
A little bit too late but I ran into the same issue recently. The implementation of the clipboard history on Windows conflicts with this, so in order to avoid those random errors you need to disable it.

VBA delay millisecond timer using API

I need a VBA millisecond (~100) delay timer and tried using the API:
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Then use Sleep(100) in a sub.
But, when I try using it I get a compiler error:
"Constants, fixed-length strings, arrays, user-defined types and
declare statements not allowed as Public members of object modules"
I get the same error if I change it to 'Private'.
Any clues as to how I can get this to work?
Thanks for any help provided.
The relevant part of the error is this (emphasis mine):
"Constants, fixed-length strings, arrays, user-defined types and
declare statements not allowed as Public members of object modules"
In other words, you apparently can't have a Public Declare statement in a class module.
Add a new standard/procedural (.bas) module, and move the Declare statement(s) there. Or, make it Private if that's the only module it's used in.
Should "just work" ;-)

Positioning the cursor/mouse tip using VBA

I am using Visual Basic for Applications, and in my program, I am opening Inventor parts and capturing thumbnails. I get undesirable results if the mouse tip is left hovering over the parts. I would like to be able to re-position the cursor such that it is well out of the way.
Any VBA code that accomplishes this along with the required references would be much appreciated.
You can use the SetCursorPos API for this, although the UX might be horrible if you don't put it back where you found it:
Private Declare Sub SetCursorPos Lib "user32.dll" (x As Long, y As Long)
Sub Example()
SetCursorPos 0, 0
End Sub
If you do want to restore the original location, just grab it with GetCursorPos first.

VBA in Word: Getting the script execution time

how do I get out the time my VBA script takes to execute?
I know from PHP that there is something like microtime() which is called once before the script and once after in order to be able to calculate the difference from this values...
Is there an VBA equivalent?
It's a sample code that I used in one of my VBA projects to measure the performance of my script temporarily.
Also you can find ample of resources to optimize your script's performance
Public Sub generate(ByRef generators() As Generator)
Dim startTime As Double
OptimizePerformance doc
'/////////////////below is the line that matters
startTime = Timer
'////////// your code that is to be measured (in time) here //////////
MsgBox Format(Timer - startTime, "00.00") & " seconds"
removeOptimization doc
End Sub
There's a function called Timer() that returns time in seconds since midnight. Includes milliseconds. I don't know of a micro-second resolution timer in VBA.
An article on About.com suggests that it's possible to write your own microtimer by making Win32 API calls directly from VBA.
In case you need more accurate timing information, I would recommend using one of the following functions the get the start and end times
#If Win64 Then
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As LongLong
Private Declare PtrSafe Function timeGetTime Lib "winmm.dll" () As LongLong
#Else
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
#End If
They should return more precise information, depending on systems. Personally, on Win7 64bit / Office 2010/2013 32bit environments I prefer timeGetTime
Note that the absolute values of timeGetTime are not recommended to be used, but the DIFFERENCE (e.g. endTime-startTime) is a quite accurate value in miliseconds