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
Most of us just 'assume' that a file ending in '.docx' is a Word Document.
If I right-click on the file and show properties, then the properties window shows
Microsoft Word Document (.docx)
I want to know whether I can get that information programmatically.
IE, there's a way to declare
Private Declare Function FindExecutable Lib "shell32.dll" Alias "FindExecutableA" _
(ByVal lpFile As String, _
ByVal lpDirectory As String, _
ByVal lpResult As String) As Long
and use that to find what program is associated with the file.
If there is, WHAT is the call I need to make Windows give me the file type?
I'm updating an existing application that scans barcodes and is written in VB.net running on windows compact framework 3.5. The scanner is a POCKETPC running windows mobile handheld 6.5. I have a combobox DropDownStyle='DropDown'. I want to programmatically drop down the box.
I have coded the following:
<DllImport("coredll.dll", SetLastError:=True)> _
Public Shared Function SendMessage(ByVal hWnd As IntPtr, _
ByVal Msg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As IntPtr
End Function
Const CB_SHOWDROPDOWN As Int32 = &H14F
SendMessage(cmbVisitoringUnit.Handle, CB_SHOWDROPDOWN, 1, 0)
The return code is 1 but no dropdown takes place. What am I missing?
Based on a lot more testing I have done, let me answer my own question. In order for the dropdown message to the combobox to work the combobox must have focus, so the code must first set focus to the control and then send the message to dropdown. There is another quirk with the combobox. If it is dropped down and it losses focus for any reason the control cancels the dropdown and then destroys the event. This leads to an impression that the program is not responding and the user needs to repeat the action again. The only fix I could come up with is to always cancel the dropdown where possible.
If I'm using Visual Basic to run an executable using the Shell() command, how would I run that executable as an administrator? My method works, at least in practice, but one executable won't run properly and I think that's the issue.
Here's an example of the command I'm trying to run.
Shell("%temp%\ninite.exe")
Edit: Sorry, this is VB.net, not VBA. I put a bad tag on there.
To stay strictly in VBA with no .NET dependencies, you should be able to use the ShellExecute Win32 function.
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" ( _
ByVal hWnd As Long, _
ByVal lpOperation As String,
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long
You can pass "runas" to lpOperation instead of the more usual "open" (commonly called the verb). Note that this may cause a dialog box to come up prompting the user for credentials.
A better way might be to use the CreateProcess or CreateProcessAsUser function which is probably the most powerful way to launch anything in Windows, but that is certainly more complex and I cannot tell you the details of how to get it to work right.
Is there any option to close the currently opened MsgBox using any code in VBA access form application?
Check out Randy Birch's response on this thread in microsoft.public.vb.general.discussion
He recommends creating a function in a .bas file called MsgBox. Doing so will cause VB to call your function rather than the built in one.
You'd then create your own MsgBox form and build in a timer to close your form after a set period of time. He provides links showing how to do this.
He also discusses a way to explicitly call the built in MsgBox function in case you need to do this.
Note: I've never done this but I've found that Randy Birch is a knowledgeable resource.
MsgBoxes are not intended to be programmatically closed, that's why it's difficult to do so. If you find yourself in a design where you must force close a MsgBox, you should probably re-evaluate your design.
I may be wrong but MsgBox is a blocking call creating a modal form so I don't think there is an easy way such as an option to do that. Do you have a specific use case for this ?
As MarkJ points out, could this could be a dialog generated by Access (rather than a VBA.MsgBox called in your own code)?
For example, when using table's 'dataview' in the Access UI to add a row you get a message, "You are about to append 1 record..." (or similar). Is this the kind of message you mean? If so, there are indeed ways to suppress them...
I used to have an easy answer to this: use the Windows Scripting Shell object, which has a 'Popup' function - a Message Box, just like the VBA MsgBox() function, with a 'SecondsToWait' parameter that provides exactly the timeout you wanted.
With CreateObject("Scripting.WsShell")
.Popup "Watch me disappear in 5 seconds", 5, Application.Name & ": test", vbInformation + vbOkCancel
End With
If you include a 'Cancel' button, it might still work: the available parameters are vbOkCancel, vbYesNoCancel, and vbRetryCancel.
If you're trying to close a dialog box you didn't initiate with your own msgBox() function call, that's unhelpful: and, as I've hinted above, the 'SecondsToWait' parameter doesn't really work these days - someone in Redmond really does't like the idea of one thread closing another thread's helpful warnings and important interruptions to the user's workflow.
However, you can launch a delayed Message Box 'Close' command using the API Timer() function - not quite 'close the currently opened MsgBox', as this requires advance warning that you intended to open it - but it's the closest thing I have, it fits into a self-contained VBA module, and I posted the code in an answer to a very similar StackOverflow question to yours.
I should warn you that the answer in question is using a sledgehammer to crack a nut, with a side order of lengthy explanation.
Rather than writing an alternative to the Access MsgBox from scratch, you might consider using (or at least studying) Arvin Meyer's Custom MessageBox Creator (downloadable on this page).
Message Boxes are meant to depict some information to the user. Hence programmatically closing is not a good design, unless you are not automating some process.
You can use sendkeys or win APIs.
I have had a similar problem; theoretically you can use the "SendKeys" function (see http://msdn.microsoft.com/en-us/library/8c6yea83%28VS.85%29.aspx). However, the MsgBox blocks the running so you cannot use the command. If you know when it going to pop up, you may run (from the script) external whshell that wait some time and then use the SendKeys. But if you know how to do that, tell me (here). Other similar possibility is to open different thread/process for it that will not be block, but I don't know if it is possible in VBA.
An easy to use Win32 based answer to actually be useful calling it. Using the below code you can easily do a timer-based PopUpBox. I like 1/2 second timer control so 2 = 1 second. Found this answer looking for the answer to this thread. Above answers don't seem to work. I want to use this when I want to show a quick message or quick answer default is normally right can be used for more.
Function Code usually in a Module:
Declare Function MessageBoxTimeout Lib "user32.dll" Alias "MessageBoxTimeoutA" ( _
ByVal hwnd As Long, _
ByVal lpText As String, _
ByVal lpCaption As String, _
ByVal uType As Long, _
ByVal wLanguageID As Long, _
ByVal lngMilliseconds As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Public Function PopUpBox(Optional stMessage As String _
= "Yes or No? leaving this window for 1 min is the same as clicking Yes.", _
Optional stTitle As String = "PopUp Window", _
Optional HalfSecTimer As Long = 120, Optional lgVBmsgType As Long = vbYesNo) As Long
Dim RetVal As Long
HalfSecTimer = HalfSecTimer * 500
RetVal = MessageBoxTimeout(FindWindow(vbNullString, Title), stMessage, stTitle, lgVBmsgType, _
0, HalfSecTimer)
PopUpBox = RetVal
End Function
Call Function Code
Examples: Actual code from my database
PopUpBox "Re-Linking and Closing dB", "Closing dB", 3, vbOKOnly
intAnswer = PopUpBox("Software Lock Down Active?", "Security", 10, vbYesNo)
I know this is an old post, put it seems there is a new a easy way to do this:
Sub MessageBoxTimer()
Dim AckTime As Integer, InfoBox As Object
Set InfoBox = CreateObject("WScript.Shell")
'Set the message box to close after 10 seconds
AckTime = 10
Select Case InfoBox.Popup("Click OK (this window closes automatically after 10 seconds).", _
AckTime, "This is your Message Box", 0)
Case 1, -1
Exit Sub
End Select
End Sub
Sample code provided by: Tom Urtis
https://learn.microsoft.com/en-us/office/vba/excel/concepts/controls-dialogboxes-forms/automatically-dismiss-a-message-box