Listen to refresh events in excel - 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.

Related

Get command button by using cell address

I want to call/ activate a button at the end of a Sub. I know the cell address of the command button, but I don't know the name/ID of the button.
How do I select/activate the button without looping through each button on active sheet?
https://stackoverflow.com/a/30600479/13049793
I have created my buttons on multiple rows with each assigned to the same macro, from my understanding, I cannot call the macro the button is assigned to because the macro uses the button's relative position, below is a simple example to illustrate the use of the buttons relative position:
Sub ExampleButtonClick()
Dim Cellvalue As String
Cellvalue = ActiveSheet.Buttons(Application.Caller).TopLeftCell.Offset(0, -1).Value
Msgbox (Cellvalue)
End Sub
i assume you want to run one macro that at the end initiates a different macro, i also assume the reason the button is rather not as a sub that is just called is because it has its independent function that can be used without the other sub
assuming that you used a command button as an activeX control, why not just use a private sub that the button performs and place that in an individual module and at the end of your current sub;
call module1 'assuming the private sub exists in module 1
or rather include the code even if duplicated in the first sub, searching for a button seems the circular route unless there is another intention you wish to pursue.
maybe elaborate on the task at hand or post the current code you are working on?

Why does OnTime not execute when a userform is open?

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.

Wait via Do Events works in standard module but not userform

Background/issue: I created a userform so that when a value lower than $250 is entered in a specific cell, the userform prompts the user for a password. The password is then supplied by a supervisor to allow the value. The issue is that the value entered by the user will be a guess. These users mess with values until the value they enter gets them the number they're looking for (separate formula that's dependent upon the low $250 amount). So after the user attempts their first number, they're likely going to need to enter it up to 10ish times. Currently the userform would require the supervisor to enter the password each time the value is changed. I'm hoping to edit my code so that either A) the password can be "retained" if you will for a certain length of time or B) allow the opportunity to enter an amount lower than $250 a set amount of times without the userform launching each time. I tried the number attempt originally and was unsuccessful. Honestly a time option would be best so hopefully someone can help. I've tried multiple ways of waiting (application.wait), sleep using lib kernel32, and a few different do events. From what I've gathered, the do events might be my best option as what I need it to do is allow the user to change values and not have the PC prevent the user from working (change said value though I suppose the user could still perform other actions.)
I've tried putting different the different wait/sleep/loop subs in a standard module and call them but I've also tried putting them directly in the forms module but neither way works. If the sub is in a standard module, Excel doesn't let the user do anything (it acts as application.wait) but if I perform it in the form module, it allows the user to select cells but not edit them until the loop/userform sub is complete. No errors are given in any of this, it just doesn't let users make multiple attempts at the value under $250. One last tidbit...I can run the Wait sub on a standard module and work in the workbook while it continues firing; just can't get it to work with the userform.
Public Sub CommandButton1_Click()
Dim newTime As Date
If tbPassword.Value = "password" Then
me.hide 'Form would stay up so I thought hiding it would fix issue but didn't
call wait 'loop to allow an amount of time before PS has to be reentered
Else
MsgBox "Password is incorrect."
Sheets("Rate Calculator v8").Range("K19") = ""
Application.Goto Sheets("Rate Calculator v8").Range("K19")
End If
Unload me
End Sub
Sub Wait()
Start = Timer
Do While Timer < Start + 60
DoEvents
Loop
End Sub
I got a solution that works but not as you like it to work and I will tell you why it shouldn't work like you want it to work.
First the solution:
Put this code in "ThisWorkbook"-Module:
Option Explicit
Public Endt
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Call Modul1.TimeBeforeReentry
End Sub
Side note here: You may want to use another event then Workbook_SheetChange but it does the job for me here. The disadvantage is that it also triggers with everything else that changes the a sheet.
Then put this code in a module1:
Sub TimeBeforeReentry()
If ThisWorkbook.Endt > Timer Then
Else
UserForm1.Show
If UserForm1.Passwordfield.Value = "password" Then
UserForm1.Passwordfield.Value = ""
ThisWorkbook.Endt = Timer + 60
Else
MsgBox "Password is incorrect."
Worksheets(1).Range("K19") = ""
Application.Goto Worksheets(1).Range("K19")
End If
End If
End Sub
And finally in the userform:
Private Sub CommandButton1_Click()
Me.Hide
End Sub
And now the explanation and why you shouldn't put everything into the userform.
The public variable Endt is in thisworkbook and will be saved there (unless there is an error!). As soon as the event is triggered, in our case workbook_SheetChange, the module will be called. Here is the whole logic and this is good. Because you do not want to make "smartUI" the problem with logic in userforms is that it is nearly impossible to maintain and to adjust to changes. I learned this the hard way believe me.. I am not an expert on this topic but read these links
Smart UI
Model View Presenter as alternative to Smart UI
The Sub TimeBeforeReentry then checks the time and shows the userform accordingly. If the password is entered the Endt is set and if the next event is triggered within Endt then the password does not have to be reentered. Otherwise the password will be needed to reentered.
The userform is just a data interchange device it has nothing to do with logic it just holds data and delivers it. That's what UI is for!

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

VBA textbox changes trigger macro

I am trying to monitor a sheet which has several text boxes on it.
The idea behind it is the user enters text into these boxes and then you can click the submit button to send it all to a sql database.
Now I want to have it so if the user has made changes, and they go to leave the sheet a macro is triggered to tell them that they haven't saved it.
I've already got this triggering on the deactivate worksheet event, but I was wondering if I can monitor all of the textboxes on the sheet (oleobjects) under one change event
I am already assuming this isn't possible with just one but was hoping.
Thanks in advance
Tom
One way to do this would be to write a separate subroutine that is called within the Change event of all the Textboxes. Keep in mind though that this will be raised every time the Textboxes change at all- every keystroke.
In each TextBox_Change Event:
Private Sub TextBox1_Change()
TextChanged TextBox1
End Sub
New subroutine:
Sub TextChanged(fromTextBox As TextBox)
'validation code goes here
End Sub

Resources