6

I am calculating a lot of data with VBA in Excel and want to show a MsgBox when it's done. The MsgBox actually shows the time it took for the calculation.

The problem is when the user decides to do something else while the computation happens. Excel continues to calculate, and when it's done, the MsgBox does show but for some reason, Excel doesn't give the focus to the MsgBox. The Excel icon will blink in the taskbar and if we click it, Excel does maximize, but the MsgBox is behind the Excel window and we can NEVER click it. So the only way to get out of it is to taskkill excel.exe... not really nice. Alt+Pause doesn't work either since the code will be stopped only after the current line of code, which ends... when the MsgBox is closed.

I tried the function AppActivate("Microsoft Excel") before without any success (How do I bring focus to a msgbox?). The application name is actually longer than that since Excel 2010 adds the document name to the window title.

Any idea how I could get around this annoying problem?

Community
  • 1
  • 1
dan
  • 3,439
  • 17
  • 54
  • 82
  • 1
    Not that this is a solution, but does Alt-Tab get you there? – peege Dec 04 '14 at 18:52
  • 1
    Nope, I tried that too. The MsgBox isn't considered as a window by Windows (W7 here), so I can only see Excel 2010 and focus Excel 2010. But in Excel, the focus is still somehow on the MsgBox which is behind the Excel window. I could try pressing "Enter" with my keyboard though, that's the only thing I didn't try. – dan Dec 04 '14 at 18:54
  • 1
    Are you using multiple monitor display? Also, could you post some code? – peege Dec 04 '14 at 18:55
  • 1
    I do have 2 monitors, yes. Usually, Excel isn't even minimized. But the MsgBox just goes under it if I'm doing something on the other screen (as soon as Excel loses the focus it seems). – dan Dec 04 '14 at 18:56
  • 1
    I don't have user forms. I'm launching my macros from buttons that I added in the toolbar. I'm using the built-in `MsgBox` function to display my message at the end of the computation. – dan Dec 04 '14 at 18:59
  • 1
    Pressing "Enter" does work even if the MsgBox is in background (and the default MsgBox only has a OK button), so I don't have to kill the whole process. Still annoying though since I plan on sharing the application. – dan Dec 04 '14 at 19:01
  • 1
    Check out this link also, ChipPearson's page about Wait functions.. http://www.cpearson.com/excel/WaitFunctions.aspx – peege Dec 04 '14 at 19:02
  • 1
    Can you identify what behavior is causing this? Is the user minimzing Excel while the macro is performing its calculations/etc.? – David Zemens Dec 04 '14 at 19:07
  • 2
    Also, is a message box necessary? You could use some other notification method. User form, email notification, status bar, etc. – David Zemens Dec 04 '14 at 19:07
  • 1
    User form could probably get the job done. The VBA actually generates ~60 PDF documents by saving the Excel document in PDF and changing some values between every saves. It doesn't mind if Excel is minimized or not, if I click on Google Chrome on my other screen, the MsgBox will show in background. So it's as soon as it loses focus. But I can click Excel and press Enter (knowing that the MsgBox has the focus but is in background). – dan Dec 04 '14 at 19:12
  • 1
    Does `AppActivate Application.Caption` work any better? – Rory Dec 05 '14 at 12:13
  • 1
    @Rory Tested, still doesn't work though. – dan Dec 08 '14 at 15:15

5 Answers5

5

This will work in Excel no matter which other application has focus:

Before the message box or any warning put the following code:

AppActivate Application.Caption
DoEvents

Trust me on this, this is amazing!

DeerSpotter
  • 417
  • 1
  • 6
  • 17
  • 1
    I will need to give this a shot. I put this just before displaying the MsgBox? The problem, 1 year ago (!), was that the MsgBox would display in the background without the user noticing it. Since MsgBox in Excel/Access don't create a task in Windows, there was no way to get the focus to the MsgBox, the only workaround being killing the application or pressing Enter after focussing the application (although the MsgBox is still behind, it somehow has the focus through the main application (Excel)). – dan Nov 26 '15 at 20:28
  • 1
    this doesnt work if excel is on the same monitor as the most recent activated application. Ill look into it more... – Doug Coats Jan 21 '16 at 13:46
  • @DougCoats can you explain more? this is the only thing that worked for me to add focus on the msgbox's – DeerSpotter Feb 04 '16 at 23:52
  • 1
    Well to explain more: I have excel open on monitor right, and the internet page that opens right before a message box prompts is on that same monitor. When this happens, the message box will not set focus. If I have excel on the other monitor it works. – Doug Coats Feb 05 '16 at 15:12
  • @DougCoats, I think what i have above is the closest to any solution that i have yet seen. I will play around with it more and see what i can come up with. If somebody reading this has a solution please speak up. – DeerSpotter Apr 14 '16 at 17:14
  • 1
    It works for me, even if I open a covering third app/window in the same monitor before the AppActivate+msgbox – 6diegodiego9 Nov 08 '19 at 09:13
  • Not sure if it's useful but I'd also add the vbMsgBoxSetForeground attribute to the msgbox: "msgbox "foobar", vbMsgBoxSetForeground" – 6diegodiego9 Nov 08 '19 at 09:17
  • @6diegodiego9 the foobar is not consistent, i tried it and it not always works. – DeerSpotter Nov 11 '19 at 14:39
3

Sounds like the macro process is making the app unresponsive. Not certain if this will help but have you considered adding DoEvents or Sleep (API call) in your long running process to yield control back to the OS? Sleep is an API call so you'd need to declare it in a module to use it. DoEvents prevents the app from locking up, but it does use more CPUs so if it is in a loop I would access it every once in a while (30% or less of the iterations). If it isn't a loop and you know where the bottlenecks are in your long running process you can call DoEvents after each long running process.

#If VBA7 And Win64 Then
' 64 bit Excel
Public Declare PtrSafe Sub Sleep Lib "kernel32" ( _
    ByVal dwMilliseconds As LongLong)
#Else
' 32 bit Excel
Public Declare Sub Sleep Lib "kernel32" ( _
    ByVal dwMilliseconds As Long)
#End If

Sleep API source

Then in your process

Sub SomeLongProcessWithDoEventsExample()
   For i = 1 to 100000
       'Some lengthy code
       If i Mod 333 = 0 Then
          DoEvents
       End If
   Next i
End Sub

Sub SomeLongProcessWithSleepExample()
   For i = 1 to 100000
       'Some lengthy code
       If i Mod 333 = 0 Then
          Sleep 1 * 1000 'Millseconds
       End If
   Next i      
End Sub

I'd suggest setting the Application.ScreenUpdating = False then turn it back on after the process has finished, but it may make matters worse.

Update

Just read the comments that were entered while typing my answer. Another option instead of the messsage box would be to open the folder window where the files are being saved after all the files have been created (replace Environ$("APPDATA") with save location):

Shell "explorer.exe" & " " & Environ$("APPDATA"), vbMaximizedFocus

OR open one of the PDFs:

Shell Environ$("COMSPEC") & " /c Start C:\SomeFile.pdf", vbMaximizedFocus

Another Option

I couldn't put this in the comments, because there was too much code, but Make an API call to MessageBox instead, but don't set owner of the message box (hWnd) set it at &H0 or &O0. The vbSystemModal should make it pop to the top. I don't know if it will allow you to select the excel application window after the user clicks okay:

MessageBox &O0, "My Message", "My Caption", vbOKOnly + vbSystemModal


#If VBA7 And Win64 Then
Public Declare PtrSafe Function MessageBox _
    Lib "User32" Alias "MessageBoxA" _
       (ByVal hWnd As LongLong, _
        ByVal lpText As String, _
        ByVal lpCaption As String, _
        ByVal wType As LongLong) _
    As Long

#Else
Public Declare Function MessageBox _
    Lib "User32" Alias "MessageBoxA" _
       (ByVal hWnd As Long, _
        ByVal lpText As String, _
        ByVal lpCaption As String, _
        ByVal wType As Long) _
    As Long

#End If
Community
  • 1
  • 1
Charles Byrne
  • 834
  • 2
  • 11
  • 20
  • 1
    If nothing else works, opening the folder is definitely a nice way to get around the problem. Thank you, I will look into your first solution and other solutions Monday. – dan Dec 04 '14 at 20:38
  • 1
    None of all the solutions work, so I'm now trying to get around it and not using MsgBox. Opening the folder does get around the problem and it works. Thanks for that. The only problem is that even with vbMaximizedFocus, the focus doesn't go into the folder, so I have to look in the taskbar to know it's actually there. I will try with forms to see if it's better. – dan Dec 08 '14 at 15:56
  • 1
    The userform does give focus to Excel (orange blinking in the taskbar) and does work nicely, although the Excel charts are blinking in the background. I will play with the ScreenUpdating option to see if it makes a difference but that's probably the workaround that I will stick with. – dan Dec 08 '14 at 16:10
  • 1
    Sounds like you're close. I normally turn the screen updating back on at error handler label (All the code goes to the error handler label and I check for Error Code <> 0 etc.) or you can put it in before and in the Error handler if you exit before the error handler label. – Charles Byrne Dec 08 '14 at 16:30
  • Yeah, it doesn't flicker anymore with ScreeUpdating at false before showing the form. I need it to be on while generating my PDFs thru because I'm using Worksheet_Change() events to edit my chart labels position when they overlap (it happens even with xlLabelPositionBestFit) so that makes it a little bit complicated. Anyway. It works with the Form. That still doesn't fix the original problem of MsgBox going in background, but I think it has something to do with a really specific setting either in Excel or Windows on our business computers. – dan Dec 08 '14 at 17:45
  • You could try calling API MessageBoxA in User32 and set hWnd = &0. It appears to put the dialog on top. I don't have enough room in this comment to provide a code sample so I'll add to my answer as another option. Sorry it will look like I've thrown every option in there (not my intent). – Charles Byrne Dec 08 '14 at 18:15
  • Definitely an interesting take, although it's still a workaround. I saw the problem happening too when clicking on a button displaying a MsgBox (*are you sure to continue etc*). It happens 100% of the times when the application doesn't have focus, but it could happen otherwise too (maybe more like 5% of the time) even if it does indeed have focus on. Might be Windows acting weird too (and removing focus although it should not). – dan Dec 08 '14 at 18:24
3

*oops didn't read the rest of your question, I used AppActivate and worked as expected and Im using MS Office Professional Plus 2010

I couldn't get the method from guitarthrower to work but was able to get this to work as an example. Instead of ThisWorkbook.Activate try AppActivate ("Microsoft excel") then your MsgBox

Sub test()
    If Application.Wait(Now + TimeValue("0:00:10")) Then
        AppActivate ("Microsoft excel")
        MsgBox "Time expired"
    End If
End Sub
mrbungle
  • 1,921
  • 1
  • 16
  • 27
  • 1
    Doesn't work either, whether I use AppActive or ThisWorkbook.Activate. The Application.Wait doesn't seem to help. Note that when I click on Excel, I can see the elements blink continuously in the workbook, waiting for a response to the MsgBox (that is hidden in the background). Pressing Enter doesn't seem to always work, but usually with a combination of Ctrl+Break, Esc and Enter. – dan Dec 08 '14 at 15:32
  • 1
    That's weird, I run this and I can have Excel all minimized and then just the msgbox pops up. Dunno :( – mrbungle Dec 08 '14 at 17:10
  • 1
    It probably has something to do with the way Excel and Windows are configured on our business computers. Although it doesn't quite solve the problem, I will go for a workaround based on Forms. – dan Dec 08 '14 at 17:46
  • 1
    @dnLL: it's AppActivate, not AppActive – 6diegodiego9 Nov 08 '19 at 09:12
2

I did some testing, and found a potential work around for you.

I set up this simple procedure to test your situation:

Sub test()
    If Application.Wait(Now + TimeValue("0:00:10")) Then
        MsgBox "Time expired"
    End If
End Sub

I run this, then minimize all windows, and when the timer is up, nothing happens. If I switch to Excel I can see the Message Box, but nothing otherwise.

So I tried this:

Sub test()
    If Application.Wait(Now + TimeValue("0:00:10")) Then
        ThisWorkbook.Activate
        MsgBox "Time expired"
    End If
End Sub

This time when I run the procedure, then minimize all windows, instead of seeing nothing the Message Box pops up (but not the Excel Window).

I think by adding ThisWorkbook.Activate right before your MsgBox code you can have the same happen in your file.

It doesn't quite get you all the way there, but hopefully is better than where you are at.

guitarthrower
  • 5,624
  • 3
  • 29
  • 37
  • 1
    I tried the ThisWorkbook.Activate but it doesn't help, sadly. I'm not sure what's exactly wrong but I tried with another computer and I get the same problem. But all of our computers in our company are nearly identical both in terms of hardware and software. I will probably go with a completely alternate solution like opening the folder instead of a MsgBox. – dan Dec 04 '14 at 20:56
2

I tried most of all the other answers:

  • ThisWorkbook.Activate
  • AppActivate()
  • Application.Wait()
  • Sleep library

For whatever reason, none of the above works. I believe there is some really specific business setting in our computer environment that could be creating the problem. As others mentionned, all the above solutions should probably work on any brand new formatted Windows 7 install with any version of Office 2010 installed, even in a dual-monitor setup. So +1 to all of these answers. Also, I noticed the problematic only happens in some specific workbooks. Really weird behavior, that might just be an Office 2010 bug related to something I do in my workbook (whether it's VBA or simply Excel).

With that being said, my real solution (which isn't really a solution to the initial problem) is not using MsgBox(). A couple of workarounds do exist and I found out that Forms are the best one for me. So instead of wasting more time in this problem, I came out with the really simple following code to replace my original MsgBox():

Application.ScreenUpdating = False
frmMsgBox.Show
Application.ScreenUpdating = True

I can put whatever text I want in a label in frmMsgBox, and since this is Excel, I can simply pass parameters by using hidden cells.

Thanks for all the help.

dan
  • 3,439
  • 17
  • 54
  • 82