How to Close InfoBox.Popup on Timer Expiration? - vba

I cobbled this test procedure together in Outlook 2013 from other posts.
It should display a popup box, and then close after 3 seconds.
It never closes.
Sub MessageBoxTimer()
Dim AckTime As Integer, InfoBox As Object
Set InfoBox = CreateObject("WScript.Shell")
AckTime = 3
Select Case InfoBox.Popup("Click OK (this window closes automatically after 3 seconds).", _
AckTime, "This is your Message Box", 0)
Case 1, -1
Exit Sub
End Select
End Sub

Some research suggests that this may be a bug in some MS Office applications. I'm basing this on the fact that this and this don't seem to say anything which suggests you're using the command in the wrong way, and this shows that other users have managed to get precisely this code to work.
I tested this on my Windows PC running Excel with Office 365 and have had the same issue as you - the message box is displayed, but not closed. I found a suggested workaround here, and the discussion on that page may be of some interest to you (particularly one user's description of trying to submit a bug report to Microsoft about VBA). The solution, proposed by a user called ウィンドウズスクリプトプログラマ, is to make a call through to the native user32.dll by declaring an external function - this page has some examples of how to call C dlls with VBA.The MessageBoxTimeout function is said to be undocumented by Microsoft, but you can find out a lot about it here.
The other option, which worked for me, is run a vbscript call to Shell.Popup with mshta.exe:
Function Test()
Dim Shell
Set Shell = CreateObject("WScript.Shell")
Shell.Run "mshta.exe vbscript:close(CreateObject(""WScript.shell"").Popup(""Test"",3,""Message""))"
End Function
To get this to work with more complex messages, you may need to escape some characters. There is another SO question here which shows other uses for mshta's ability to execute vbscript in a shell/ console.
Finally, as was suggested by one user, you could simply create a custom user form with a doevents loop that counts down and then closes itself.

The WScript.Shell .Popup seems to be hit or miss in Office VBA.
If you are looking for a MsgBox that works in Office VBA and supports a timeout, I posted another method that uses a Windows API call. It supports timeout, carriage returns, and return values. You can find the code at this link. I did not think it was proper etiquette to post it again here.
Note that the mshta method mentioned by #Orphid does not support carriage returns and always shows the message on the primary monitor.

Yes, I can confirm that result: the 'Timeout' on the WsShell.Popup function is no longer working in Office.
It took me a while to notice, because popup dialogs with a 'cancel' button seem to be affected less. So this might be a usable workaround for you:
Dim msg AS String
Dim Title as String
msg ="Click 'Ok' or 'Cancel' (this window closes automatically after 3 seconds)."
Title = Application.name & ": Message Box test"
Select Case InfoBox.Popup(msg, AckTime, Title, vbQuestion + vbOkCancel)
If that doesn't work, you're going to need a much longer explanation: reimplementing the 'Timeout' using an API Timer Callback. As the author of that answer, I should warn you that this is using a sledgehammer to crack a nut after attempting the task with a prolonged naval bombardment.

I have tried the following code to control VBA msg box auto closer after 40 sec. You can try also it. It will work for you.
'The first part
#If Win64 Then '64?
Private Declare PtrSafe Function MsgBoxTimeout _
Lib "user32" _
Alias "MessageBoxTimeoutA" ( _
ByVal hwnd As LongPtr, _
ByVal lpText As String, _
ByVal lpCaption As String, _
ByVal wType As VbMsgBoxStyle, _
ByVal wlange As Long, _
ByVal dwTimeout As Long) _
As Long
#Else
Private Declare Function MsgBoxTimeout _
Lib "user32" _
Alias "MessageBoxTimeoutA" ( _
ByVal hwnd As Long, _
ByVal lpText As String, _
ByVal lpCaption As String, _
ByVal wType As VbMsgBoxStyle, _
ByVal wlange As Long, _
ByVal dwTimeout As Long) _
As Long
#End If
'The second part
Sub btnMsgbox(message As String)
Call MsgBoxTimeout(0, message, "", vbInformation, 0, 40000)
End Sub

Related

Bringing an open file to the fore window with vba code

I am using vba code to open an access database from another access database using the following codes
Public Declare Function ShellExecuteA Lib "shell32.dll" _
(ByVal hWnd As Long, ByVal strOperation As String, _
ByVal strFile As String, ByVal strParameters As String, ByVal strDirectory As _
String, ByVal nShowCmd As Long) As Long
Private Declare Function GetDesktopWindow Lib "user32" () As Long
Dim lngreturn As Long
lngreturn = ShellExecuteA(GetDesktopWindow(), "OPEN", strFilePath, "", "", vbNormalFocus)
It works fine except that it keeps opening new instances of the database even if there is one already open in the background. I need codes to bring the opened database to the foreground. NB:- I have used AppActivate and it does not work because I am already in msaccess and the other database that I want to bring to the foreground is also in access.
Set an Application title for the second database in File - Options - Current database.
Then use that title with AppActivate.
Alternatively, instead of using ShellExecuteA, build a full command line (including the path to msaccess.exe), and use the Shell() function to start the second database:
Runs an executable program and returns a Variant (Double) representing the program's task ID if successful
Store this task ID in a public or static variable, and use it with AppActivate.

Open Hyperlinks in Access

I have a table of products where there is say a pdf for a specific products user manual. I'm storing the model name and it's file path in my products table (in Access). I've created a form in Access that allows the user to search by product name and it narrows down the number of files and shows the results from the search in a list box. However my biggest problem is opening the actual PDF. It opens the file, but I have to store the file path exactly how it is and the path of the files are long. Is there a way to open the PDF hyperlinks without using the Followhyperlink command? Or is there a way that I can show only the file name of the pdf in my list box rather than the entire path name? If I change the display text in my products table it doesn't open the hyperlink, I get an error. Any help would be greatly appreciated!
Application.FollowHyperLink() has problems with security, especially when opening files on a network drive. See e.g. here: http://blogannath.blogspot.de/2011/04/microsoft-access-tips-tricks-opening.html
A better method is the ShellExecute() API function.
Essentially it looks like this (trimmed from http://access.mvps.org/access/api/api0018.htm ):
' This code was originally written by Dev Ashish.
' http://access.mvps.org/access/api/api0018.htm
Private Declare Function apiShellExecute 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
Public Const WIN_NORMAL = 1 'Open Normal
Private Const ERROR_SUCCESS = 32&
Public Function fHandleFile(stFile As String) As Boolean
Dim lRet As Long
lRet = apiShellExecute(hWndAccessApp(), "Open", stFile, vbNullString, vbNullString, WIN_NORMAL)
If lRet > ERROR_SUCCESS Then
' OK
fHandleFile = True
Else
Select Case lRet
' Handle various errors
End Select
fHandleFile = False
End If
End Function
Now for your listbox:
Set it to 2 columns, the first being the model name, the second the file path.
Set the column width of the second column to 0, so it will be invisible.
And in the doubleclick event, call fHandleFile with the second column (file path):
Private Sub lstManuals_DblClick(Cancel As Integer)
Call fHandleFile(Me.lstManuals.Column(1))
End Sub

VBA Excel: FTP GetFile via WinINet

I am trying to get an FTP Download operation to work with VBA Excel (on Excel 2013 x64, Windows 7 SP1 x64). I found some code that maps to the WinInet API and I can successfully upload files using the PutFile function but I am looking to get a GetFile function to work as well.
To get working what I have so far, I used the following: second answer here, and this link. I've been mostly using the second answer from SO to get PutFile to work. I had to make some changes to the original code to make it compatible with 32 bit and 64 bit systems.
You can see my progress here.
What I am looking to do is make an easy to use Sub that calls this declaration:
Private Declare Function FtpGetFile Lib "WinInet" Alias "FtpGetFileA" (ByVal hFtp As Long, ByVal lpszRemoteFile As String, ByVal lpszNewFile As String, ByVal fFailIfExists As Long, ByVal dwFlagsAndAttributes As Long, ByVal dwFlags As Long, ByVal dwContext As Long) As Long
I.E. a Sub like this:
Public Sub GetFile(RemoteFilename As String, LocalFilename As String)
If FtpGetFile(' what arguments do I put here ') = 0 Then
Err.Raise vbObjectError + 1, , LastError
End If
End Sub
I am struggling because I am not very familiar with the WinInet API and am having difficulty parsing the required arguments and what are the appropriate variables to pass for those arguments.
This should get you started on the right path, based on the example provided.
In your class module:
Public Sub GetFile(RemoteFilename As String, NewFilename As String)
If FtpGetFile(m_hFtp, RemoteFilename, NewFilename, False, 0, FTP_TRANSFER_TYPE_BINARY, 0) = 0 Then
Err.Raise vbObjectError + 1, , LastError
End If
End Sub
How to call it:
Sub DownloadFile()
Dim ftp As New CFtp
ftp.Connect "serverAddress", "username", "password"
ftp.GetFile "nameOfFileOnServer.txt", "C:\SomePath\nameOfNewFile.txt"
ftp.Disconnect
End Sub
The parameters specify some attributes to associate with the downloaded file. For example, fFailIfExists is a Boolean that describes whether or not to throw an exception if it is trying to overwrite a local file that already exists. The other flags specify attributes to attach to the file it creates.

VBA to Prevent Keyboard Input While a Package Object (XML) is Read into ADODB Stream?

I am developing an application which opens and reads an XML document previously embedded in a PowerPoint presentation, or a Word document. In order to read this object (xmlFile as Object) I have to do:
xmlFile.OLEFormat.DoVerb 1
This opens the package object, and I have another subroutine that gets the open instance of Notepad.exe, and reads its contents in to ADODB stream.
An example of this procedure is available on Google Docs:
XML_Test.pptm.
During this process there is a few seconds window where the Notepad.exe gains focus, and an inadvertent keystroke may cause undesired results or error reading the XML data.
I am looking for one of two things:
Either a method to prevent the user from inadvertently inputting (via keyboard/mouse/etc) while this operation is being performed. Preferably something that does not take control of the user's machine like MouseKeyboardTest subroutine, below. Or,
A better method of extracting the XML data into a string variable.
For #1: this is the function that I found, which I am leery of using. I am wary of taking this sort of control of the users system. ##Are there any other methods that I might use?##
Private Declare Function BlockInput Lib "USER32.dll" (ByVal fBlockIt As Long) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub MouseKeyboardTest() 'both keyboard and mouse blocked
BlockInput True ' Turns off Keyboard and Mouse
' Routine goes here
Sleep 5000 ' Optional coding
BlockInput False ' Turns on Keyboard and Mouse
End Sub
For #2: Some background, but the issue seems to be the inability to extract the embedded object reliably using any method other than DoVerb 1. Since I am dealing with an unsaved document in an application (Notepad) that is immune to my VBA skillz, this seems to be the only way to do this. Full background on that, here:
Extracting an OLEObject (XML Document) from PowerPoint VBA
As you correctly guessed in the comment above that taking the focus away from notepad will solve your problem. The below code does exactly that.
LOGIC:
A. Loop through the shape and get it's name. In your scenario it would be something like Chart Meta XML_fbc9775a-19ea-.txt
B. Use APIs like FindWindow, GetWindowTextLength, GetWindow etc to get the handle of the notepad window using partial caption.
C. Use the ShowWindow API to minimize the window
Code (tested in VBA-Powerpoint)
Paste this code in a module in the above PPTM
Private Declare Function FindWindow Lib "User32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetWindowText Lib "User32" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "User32" Alias _
"GetWindowTextLengthA" (ByVal hWnd As Long) As Long
Private Declare Function GetWindow Lib "User32" (ByVal hWnd As Long, _
ByVal wCmd As Long) As Long
Private Declare Function ShowWindow Lib "User32" (ByVal hWnd As Long, _
ByVal nCmdShow As Long) As Long
Private Const GW_HWNDNEXT = 2
Private Const SW_SHOWMINIMIZED = 2
Sub Sample()
Dim shp As Shape
Dim winName As String
Dim Ret As Long
For Each shp In ActivePresentation.Slides(1).Shapes
If shp.Type = msoEmbeddedOLEObject Then
winName = shp.Name
shp.OLEFormat.Activate
Exit For
End If
Next
If winName <> "" Then
Wait 1
If GetHwndFromCaption(Ret, Replace(winName, ".txt", "")) = True Then
Call ShowWindow(Ret, SW_SHOWMINIMIZED)
Else
MsgBox "Window not found!", vbOKOnly + vbExclamation
End If
End If
End Sub
Private Function GetHwndFromCaption(ByRef lWnd As Long, ByVal sCaption As String) As Boolean
Dim Ret As Long
Dim sStr As String
GetHwndFromCaption = False
Ret = FindWindow(vbNullString, vbNullString)
Do While Ret <> 0
sStr = String(GetWindowTextLength(Ret) + 1, Chr$(0))
GetWindowText Ret, sStr, Len(sStr)
sStr = Left$(sStr, Len(sStr) - 1)
If InStr(1, sStr, sCaption) > 0 Then
GetHwndFromCaption = True
lWnd = Ret
Exit Do
End If
Ret = GetWindow(Ret, GW_HWNDNEXT)
Loop
End Function
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
End Sub
My understanding is that you have control over how XML file gets embedded into PowerPoint presentation in the first place. Here I do not quite understand why you chose to keep the data you need as contents of an embedded object.
To be sure, the task of getting those contents back is not a piece of cake. Actually, as long as there is no (simple or even moderately difficult) way to call QueryInterface and use IPersist* interfaces from VBA, there is just one way to get to contents of embedded object. The way involves following steps:
Activate an embedded object. You used OLEFormat.DoVerb 1 for that. A better way would be to call OLEFormat.Activate, but this is irrelevant for your particular problem.
Use embedded object's programming model to perform useful operations like getting contents, saving or whatever is exposed. Notepad.exe exposes no such programming model, and you resorted to WinAPI which is the best choice available.
Unfortunately, your current approach has at least 2 flaws:
The one you identified in the question (activation of notepad.exe leading to possibility of user's interference).
If a user has default program for opening .txt files other than notepad.exe, your approach is doomed.
If you do have control over how embedded object is created then better approach would be to store your XML data in some property of Shape object. I would use Shape.AlternativeText (very straightforward to use; shouldn't be used if you export your .pptm to HTML or have some different scenario where AlternativeText matters) or Shape.Tags (this one is probably the most semantically correct for the task) for that.
I don't think that blocking the user is the right approach,
If you must use a content of a notepad window, I would suggest using the SendKeys method, in order to send this combination:
SendKeys("^A^C")
Which is the equivalent of "Select All" and "Copy",
And then you could continue working "offline" on the clipboard, without fear of interference by keystrokes.
My approach, per Sid's suggestion, was to find a way to minimize the Notepad.exe. Since I already found way to get that object and close it, I figured this should not be as hard.
I add these:
Public Declare Function _
ShowWindow& Lib "user32" (ByVal hwnd As Long, _
ByVal ncmdshow As Long)
Public Const SW_MINIMIZE = 6
And then, in the FindNotepad function, right before Exit Function (so, after the Notepad has been found) I minimize the window with:
ShowWindow TopWnd, SW_MINIMIZE

Copy from MS Word document to a web page input box

In an already open word document select all text
copy selected text to clipboard
check default browser open at correct web address
if not open default browser at web address "http://thisaddress.com"
give focus to browser
paste clipboard text into input box called "input1"
or some other way to get MSword document contents to a web page input?
Currently the workflow involves a secretary logging in to the website, then filling out a web form, switching to their open MS Word document, selecting all, copying the WP document, then back to the web form and pasting into an input box, then hitting submit. What I want to do ideally have a button in MS word which opens the browser to the correct web page then copies and pastes the document into the correct input box on the page (in fact it will be the only textarea form field).
The MS Word VBA code is:
Option Explicit
Enum W32_Window_State
Show_Normal = 1
Show_Minimized = 2
Show_Maximized = 3
Show_Min_No_Active = 7
Show_Default = 10
End Enum
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
Function OpenURL(URL As String, WindowState As W32_Window_State) As Boolean
' Opens passed URL with default application, or Error Code (<32) upon error
Dim lngHWnd As Long
Dim lngReturn As Long
lngReturn = ShellExecute(lngHWnd, "open", URL, vbNullString, _
vbNullString, WindowState)
OpenURL = (lngReturn > 32)
End Function
Sub TestMacro()
Application.ActiveDocument.Select
Selection.Copy
OpenURL "http://localhost:8500/index.cfm?wordContent=" & Selection, W32_Window_State.Show_Maximized
End Sub
and in the coldfusion handling form
<html>
<head>
</head>
<body>
<form id="form1">
<Textarea ID="txtArea" rows=6><cfoutput>#url.wordContent#</cfoutput></textarea>
</form>
</body>
</html>
Just would like to work out how to not open a new browser window if one is already open.
In case you can modify the web-application, you may do the following:
MS-Word: Copy content to clipboard.
MS-Word: Open Url as "http://thisaddress.com/SomePage?pasteClipboard=true"
SomePage: if query-string param pasteClipboard == true, then add a javascript function to get the clipboard data into your form field.
Update:
In your macro you simply call Selection.Copy, and to open the URL using default browser check this link http://www.experts-exchange.com/Programming/Languages/Visual_Basic/Q_23225744.html
Using the code from the previous link, I made a test macro as :
Sub TestMacro()
Application.ActiveDocument.Select
Selection.Copy
OpenURL "http://thisaddress.com/SomePage?pasteClipboard=true", W32_Window_State.Show_Maximized
End Sub
I hope this was helpful.
Update 2:
Just use W32_Window_State.Show_Default, Here is the full macro:
Option Explicit
Enum W32_Window_State
Show_Normal = 1
Show_Minimized = 2
Show_Maximized = 3
Show_Min_No_Active = 7
Show_Default = 10
End Enum
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
Function OpenURL(URL As String, WindowState As W32_Window_State) As Boolean
' Opens passed URL with default application, or Error Code (<32) upon error
Dim lngHWnd As Long
Dim lngReturn As Long
lngReturn = ShellExecute(lngHWnd, "open", URL, vbNullString, _
vbNullString, WindowState)
OpenURL = (lngReturn > 32)
End Function
Sub TestMacro()
Application.ActiveDocument.Select
Selection.Copy
OpenURL "http://thisaddress.com/SomePage?pasteClipboard=true", W32_Window_State.Show_Default
End Sub
Another option is to look into controlling Internet Explorer from inside Word using a control.
Here is an example.
Note, this will only work with IE (unless there are dll versions of Firefox etc.)