Why does OnTime not execute when a userform is open? - excel

I am attempting to use a self-activating OnTime sub to edit some text in a UF control. However, I find that an Application.OnTime event won't start working until the UserForm is closed.
As per this SO thread, I have placed the self-activating sub in a public sub in a regular module. However, this is to no avail.
UF sub
Private Sub UserForm_Initialize
TEST.loadingdots
End Sub
Regular sub
Public Sub loadingdots
Debug.Print 4
Application.OnTime Now + TimeValue("00:00:02"), "loadingdots"
End Sub
The first "4" gets printed, then nothing. When I close the UF, the procedure gets executed as expected.
How can I work around this?

Answering my own question for future reference
When a UF holds a RefEdit control, an OnTime event won't be executed.
I created a new, blank UF, which caused no issues whatsoever.
Seems like RefEdits need to be approached with caution, similarly to how using a RefEdit control in a modeless userform forces the user to close the Excel application through task manager.

Related

UserForm Close button fails after form launched for second time

I have a UserForm, A, with a command button that opens UserForm B using the below code:
Private Sub cmd_click()
Me.Hide
B.Show
End Sub
B, when closed via the X button, runs the following:
Private Sub UserForm_Terminate()
Unload Me
A.Show
End Sub
After:
opening B (from A)
closing B and returning to A
and then opening B again
The close button on B ceases to work.
UserForm A is a menu form that leads to several others, so navigating back and forth between the menus is a pretty basic requirement. This behaviour is reproducible for every form linked from A, as well as for tertiary forms that open from those.
Given my target demographic is of the older generation, I want my UserForms to be intuitive to use, which means preserving regular close button functionality.
Does anyone have any info on this? It may be that my google-fu is lacking, but I can't seem to find anyone who has had the same issue. Any input at this stage is greatly appreciated!
Use a variable to refer to the 2nd Userform in the first as this gives you greater control over the second forms status - you can unset the variable after you have closed the 2nd form and returned to the event handler that instantiated it in the first place. This guarantees that each time you click the button to launch UserForm2 that you are working with a 'fresh copy'.
Also you should use the QueryClose event - see MSDN. This event:
Occurs before a UserForm closes
You need to be able to be able to control the re-activation of UserForm1 before the form is disposed.
The Terminate event is slightly different:
Occurs when all references to an instance of an object are removed from memory by setting all variables that refer to the object to Nothing or when the last reference to the object goes out of scope.
In the sample code below I am getting the correct behaviour using a reference to UserForm2 and handling QueryClose.
In this sample code I am using UserForm1 and UserForm2 but you can substitue for A and B in your app:
Code in UserForm1:
Option Explicit
Private m_frm2 As UserForm2
Private Sub CommandButton1_Click()
Me.Hide
Set m_frm2 = New UserForm2
m_frm2.Show
Set m_frm2 = Nothing
End Sub
Code in UserForm2:
Option Explicit
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
UserForm1.Show
Unload Me
End Sub

Sheet select from a Userform button causes data entered to go on previous sheet

I call a macro called SelectSheet1, from a button on a userform, to select Sheet1.
When data is entered afterwards it is put on the previous sheet.
This is happening on Excel 2013. I confirmed it is not a problem in Excel 2007.
Also it is not a problem if the macro is run directly from the developer tab, keyboard shortcut, quick access toolbar or ribbon customization.
The userform command button code:
Private Sub CommandButton1_Click()
Call SelectSheet1
Unload UserForm1
End Sub
The SelectSheet1 macro selects the sheet:
Sub SelectSheet1()
Sheets("Sheet1").Activate
End Sub
Link to xlsm file in dropbox
Link to youtube video if you want to see with your own eyes
It is a strange error and wondering if it a problem with Excel 2013, something changed in the way they do things and possibly there is a workaround.
Make sure there is only a single sheet Select'ed before you Activate a sheet:
Sub SelectSheet1()
Sheets("Sheet1").Select
Sheets("Sheet1").Activate
End Sub
Remember: "Although only a single sheet may be Active, many may be Selected."
I was able to figure out how to get it to work, but not sure why this solves the issue.
Im unloading the Userform before call macro and I tried using select instead of Activate, those did not help. But after I switched so that the UserForm loads with vbModeless then my problem went away, not sure why this fixed it.
For me the key to fixing this issue was vbModeless, but would love if somebody who understood more could explain why.
Private Sub CommandButton1_Click()
Unload UserForm1
Call SelectSheet1
End Sub
Sub ShowMainMenu()
UserForm1.Show vbModeless
End Sub
Sub SelectSheet1()
Sheets("Sheet1").Select
End Sub

Run macro when user "locks" windows

I'd like to save my spreadsheet every time I lock my windows pc. Is there a built-in trigger in excel (similar to on-close)? Or might I be able to get a macro to run every minute and check if my machine is in use or locked?
If you really need this you can try the following workaround:
Enable auditing for the lock event in the event viewer, so that event 4800 for locking is logged, as described here.
Create a scheduled task triggered on the lock event. Choose Trigger on Event with Protocol: "Security", Source: "Microsoft-Windows-Security-Auditing" and Event-ID: "4800"
Run a vbscript that uses GetObject(, "Excel.Application") to retrieve the currently active excel session and run a macro that saves for you with Application.Run (like in this example)
Not very straight forward, but on the other hand, the easy way would just be to press ctrl+s everytime before you lock your computer.
You could try this and see if this works instead of using a macro:
Click the Microsoft Office Button , and then click Excel Options.
Click Save.
Select the Save AutoRecover information every x minutes check box.
In the minutes list, specify how often you want the program to save your data and the program state.
But if you specifically want a macro I could try to form one for you.
Okay well add this into a standard module:
Option Explicit
Public RunTime
Sub StartTimer()
RunTime = Now + #12:10:00 AM#
Application.OnTime RunTime, "SaveBook", schedule:=True
End Sub
Sub SaveBook()
ActiveWorkbook.Save
StartTimer
End Sub
Sub StopTimer()
On Error Resume Next
Application.OnTime RunTime, "SaveBook", schedule:=False
End Sub
Place this in your workbook class module:
Private Sub Workbook_Open()
StartTimer
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Then add this into your workbook class module:
Private Sub Workbook_Open()
StartTimer
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
StopTimer
End Sub
Have fun with it and fiddle around with it

How to trigger an event after the user lose focus on a?

I am new to visual basic. I have a TextBox, and I want to trigger an event when the user lose focus on the textBox.
I tried writing
Private Sub TextBox_LostFocus()
something
End Sub
and
Private Sub TextBox_Leave()
something
End Sub
I don't really understand how they work, I have
Private Sub TextBox_Change()
something
End Sub
and that works fine, so what am I missing? How do I trigger the event when the user is not writing in the textbox anymore?
This is what you want:
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
End Sub
There's a helper -- when you're in the code editor, you'll see two list boxes above the editor. The list on the left contains the available objects for the current module. Select TextBox from there if it isn't already selected. The list box on the right contains the available Events. You should see Exit in there. Clicking that will paste in the code above.

Listen to refresh events in excel

When the user does click on the "Refresh" / "Refresh All" button, excel seems to just call the refresh method for each (or selected) QueryTable(s) in the Workbook. However, listening to BeforeRefresh and AfterRefresh events for QueryTable does not realy help me here, because I need to execute some stuff after all the QueryTables in the Workbook (respectively after all the selected QueryTables) are updated.
Is there a way to accomplish this? Maybe its possible to somehow to listen to a mouse click on that refresh button?
Actually it's how I intended to accomplish this in the first place. But there is a problem here. Lets say the total number of QueryTables is 10. And lets say the user just selected one QueryTable and then pressed "Refresh All". So, fist my algorithm would check for the number of selected QueryTables which is 1. As a result my calculations would start after just 1 refresh which is wrong.
In the meanwhile I've tried to access that "Refresh" button in Ribbon. But it didn't work out. For some reason my code doesn't do anything...
Public Class ThisAddIn
Private Sub ThisAddIn_Startup() Handles Me.Startup
AddHandler Globals.ThisAddIn.Application.WorkbookActivate, AddressOf OnWorkbookOpened
End Sub
Private refrBtn As Office.CommandBarButton
Private Sub OnClick(ByVal Ctrl As Office.CommandBarButton, _
ByRef CancelDefault As Boolean)
MsgBox("PLS WORK!")
End Sub
Private Sub OnWorkbookOpened(wb As Excel.Workbook)
Try
refrBtn = CType(wb.Application.CommandBars.FindControl(Id:=459), Office.CommandBarButton)
AddHandler refrBtn.Click, AddressOf OnClick
Catch ex As Exception
MsgBox(ex.Message)
MsgBox(ex.GetType)
MsgBox(ex.StackTrace.ToString)
End Try
End Sub
End Class
Edit: I forgot my login data, so I've created a new account
I'm not using VSTO but I would investigate:
Create a global variable (or workbook property) set to 0
In each AfterRefresh event call a procedure
This procedure increments the counter
When the counter reaches the total number of QueryTables (or the total selected number), do what you need to do and reset the counter to 0.
You might also check the Success argument each time, so that your code might only run if the selected QTs were all successfully updated.

Resources