VBA EventHandler firing twice? - vba

I have MS Access 2003 DB.
Is it possible for an event handler for a button on a form to fire twice??
I seem to have evidence of this happening as I have a payroll process
that logs the whole process and process is duplicated in the log.
I didnt think this was possible in VBA???
EDIT:
I discovered that indeed it was firing twice as user was clicking twice and queueing the event twice.
This is the fix I made to the code which shows using a flag m_locked as an example to test with:
[code]
Private m_locked As Boolean
Private m_count As Integer
Private Sub Command0_Click()
On Error GoTo Err_Command0_Click
' wait
If Not m_locked Then
m_locked = True
Dim startTime As Date
startTime = Now()
While DateDiff("s", startTime, Now()) < 3
DoEvents
Wend
' increment counter
m_count = m_count + 1
Command0.Caption = m_count
m_locked = False
End If
Exit_Command0_Click:
Exit Sub
Err_Command0_Click:
MsgBox Err.Description
Resume Exit_Command0_Click
End Sub
[/code]
Malcolm

Seeing your "solution" I'ld recommend to specify the double click event, too. This will allow you to distinguish easily whether the user clicked once or twice by a "debug.print".
To prevent the user to perform an extra click, declare a private boolean variable on form module level, set it to TRUE in your event procedure, set it to FALSE in the timer event, and configure your form's timer to 1000 for example (it's milliseconds).
Option Explicit
Option Compare Database
Private oneClick As Boolean
Private Sub cmdMyButton_Click()
If not oneClick Then
' Perform your actions here
End If
oneClick = True
End Sub
Private Sub Form_Timer()
oneClick = False
End Sub
Oh, and please use variable and control names that tell their meaning :-)

If you do not want your user click the button twice just this simple code:
Private Sub Command0_Click()
Command0.Enabled = False
' Continue with your code here ...
End Sub

Related

How to check if a userform is closed with "X" Windows button?

There is a sub, it creates a CourtForm userform and then takes a data from it. The problem appears when said form is closed prematurely, by pressing "X" window button and I get a runtime error somewhere later. For reference, this is what I'm talking about:
In my code I tried to make a check to exit sub:
Private Sub test()
'Create an exemplar of a form
Dim CourtForm As New FormSelectCourt
CourtForm.Show
'The form is terminated at this point
'Checking if the form is terminated. The check always fails. Form exists but without any data.
If CourtForm Is Nothing Then
Exit Sub
End If
'This code executes when the form proceeds as usual, recieves
'.CourtName and .CourtType variable data and then .hide itself.
CourtName = CourtForm.CourtName
CourtType = CourtForm.CourtType
Unload CourtForm
'Rest of the code, with the form closed a runtime error occurs here
End Sub
Apparently the exemplar of the form exists, but without any data. Here's a screenshot of the watch:
How do I make a proper check for the form if it's closed prematurely?
Add the following code to your userform
Private m_Cancelled As Boolean
' Returns the cancelled value to the calling procedure
Public Property Get Cancelled() As Boolean
Cancelled = m_Cancelled
End Property
Private Sub UserForm_QueryClose(Cancel As Integer _
, CloseMode As Integer)
' Prevent the form being unloaded
If CloseMode = vbFormControlMenu Then Cancel = True
' Hide the Userform and set cancelled to true
Hide
m_Cancelled = True
End Sub
Code taken from here. I would really recommend to have a read there as you will find a pretty good basic explanation how to use a userform.
One of the possible solutions is to pass a dictionary to the user form, and store all entered data into it. Here is the example:
User form module code:
' Add reference to Microsoft Scripting Runtime
' Assumed the userform with 2 listbox and button
Option Explicit
Public data As New Dictionary
Private Sub UserForm_Initialize()
Me.ListBox1.List = Array("item1", "item2", "item3")
Me.ListBox2.List = Array("item1", "item2", "item3")
End Sub
Private Sub CommandButton1_Click()
data("quit") = False
data("courtName") = Me.ListBox1.Value
data("courtType") = Me.ListBox2.Value
Unload Me
End Sub
Standard module code:
Option Explicit
Sub test()
Dim data As New Dictionary
data("quit") = True
Load UserForm1
Set UserForm1.data = data
UserForm1.Show
If data("quit") Then
MsgBox "Ввод данных отменен пользователем"
Exit Sub
End If
MsgBox data("courtName")
MsgBox data("courtType")
End Sub
Note the user form in that case can be closed (i. e. unloaded) right after all data is filled in and action button is clicked by user.
Another way is to check if the user form actually loaded:
Sub test()
UserForm1.Show
If Not isUserFormLoaded("UserForm1") Then
MsgBox "Ввод данных отменен пользователем"
Exit Sub
End If
End Sub
Function isUserFormLoaded(userFormName As String) As Boolean
Dim uf As Object
For Each uf In UserForms
If LCase(uf.Name) = LCase(userFormName) Then
isUserFormLoaded = True
Exit Function
End If
Next
End Function

How do you prevent repeated MsgBox firing in the Form_Delete event?

I have a datasheet form which is making it easy for users to select all records and delete. I thought of writing a VBA code that prevents users from deleting the records if more than 20 records are selected. Then I wrote this code but the problem is that the message fires for every record. Has someone ever come across this? Please see below.
Private Sub Form_Delete(Cancel As Integer)
If Me.SelHeight > 20 Then
Cancel = True
MsgBox ("Bulk deletion is not allowed!")
End If
End Sub
If you get confused about BeforeDelConfirm event not fired, like I got, check if theConfirm Record ChangesOption is set. If not, event is not raised.
UsingBeforeDelConfirmandAfterDelConfirm, this task is a lot simpler.
On top of form module
Option Explicit
Dim deleteCount As Long
Private Const MaxDeleteRows As Long = 20 ' set this constant to the number of rows that can be deleted at once
event code:
Private Sub Form_AfterDelConfirm(Status As Integer)
If deleteCount > MaxDeleteRows Then
MsgBox ("Bulk deletion is not allowed!")
End If
deleteCount = 0
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
If deleteCount > MaxDeleteRows Then
Cancel = True
End If
Response = acDataErrContinue
End Sub
Private Sub Form_Delete(Cancel As Integer)
If deleteCount = 0 Then
deleteCount = Me.SelHeight
End If
End Sub
Private Sub Form_Deactivate()
' Application.SetOption "Confirm Record Changes", False ' needed if you want to have Confirm Record Changes off elsewhere
End Sub
Private Sub Form_Activate()
' Application.SetOption "Confirm Record Changes", True ' needed if you want to have Confirm Record Changes off elsewhere (turns it on here)
End Sub

Excel VBA Keep Userform Timer running when Userform or Excel are closed

Recently I've managed to find some code regarding a timer on a userform, my problem is that I need to keep the timer running even if the userform or excel file is closed... can someone take a look at the code and provide some feedback? My userform is: optionsForm
Dim dteStart As Date, dteFinish As Date
Dim dteStopped As Date, dteElapsed As Date
Dim boolStopPressed As Boolean, boolResetPressed As Boolean
Private Sub Reset_Timer_Click()
dteStopped = 0
dteStart = 0
dteElapsed = 0
Tech_Timer = "00:00:00"
boolResetPressed = True
End Sub
Private Sub Start_Timer_Click()
Start_Timer:
dteStart = Time
boolStopPressed = False
boolResetPressed = False
Timer_Loop:
DoEvents
dteFinish = Time
dteElapsed = dteFinish - dteStart + dteStopped
If Not boolStopPressed = True Then
Tech_Timer = dteElapsed
If boolResetPressed = True Then GoTo Start_Timer
GoTo Timer_Loop
Else
Exit Sub
End If
End Sub
Private Sub Stop_Timer_Click()
boolStopPressed = True
dteStopped = dteElapsed
End Sub
Private Sub optionsForm_Initialize()
Tech_Timer = "00:00:00"
End Sub
The idea of the timer is not that it runs, but that it remembers a point in time and can give you a difference between this point and the current moment. If you ask for this difference every second, then it would look like it is running like a watch.
Something like this would be a good start. In the xl_main write the following:
Option Explicit
Dim dtime As Date
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Cells(1, 1).Value = dtime
End Sub
Private Sub Workbook_Open()
If Cells(1, 1).Value = 0 Then
dtime = Now
Else
dtime = CDate(Cells(1, 1))
End If
End Sub
You may play around it and make it better as you wish. E.g. you may find a way to reset dtime or anything similar.
"Something" needs to be running to handle the timer procedure so if you want to use VBA then Excel can't be "closed" per se, however you could make it appear closed.
An obvious option is to minimize the Excel window (before showing the userform) with the WindowState property:
Application.WindowState = xlMinimized
...or, hide the Excel window completely with the Visible property:
Application.Visible = False
...or if the issue is that you need a "fresh" copy of Excel to work in, you could do so in a new instance by holding Alt while starting Excel.
            
I have posted code and a downloadable example of a countdown timer that displays the time remaining on a userform semi-independent of the Excel window, using the Windows Timer API (instead of Excel's procedure), in another answer here.
                   
That's not possible if the form is unloaded Unload optionsForm. But you can try to 'close' the form with optionsForm.hide() this only hides the form, the timer should keep running then.
The only way I see to calculate the time passed from a start time even if Excel is closed is to not save the start time in a variable dteStart but in an Excel cell.
Actually you can use a code that is placed in a module. The code is:
Option Explicit
Dim T
Sub stopTimer()
On Error Resume Next
Application.OnTime (T), Procedure:="Update", Schedule:=False
End Sub
Sub StartTimer()
T = Now + TimeValue("00:00:01")
Application.OnTime T, "Update"
End Sub
Sub Update()
UserForm1.TextBox1.Value = Format(Now - Sheets("Sheet1").Range("E11").Value,
"hh:mm:ss")
UserForm1.TextBox2.Value = Format(TimeValue("1:00:00") - (Now -
Sheets("Sheet1").Range("E11").Value), "hh:mm:ss")
Call StartTimer
End Sub
Thereafter, you can now reference it in the userform by calling it. Here is a typical example. It is
Private Sub Userform_Activate()
Sheet1.Activate
Sheets("Sheet1").Range("E11").Value = Now
Application.Run "StartTimer"
If Sheets("Sheet1").Range("K27").Value = "K29" Then
Me.CommandButton4.Caption = "Start"
Me.CommandButton2.Visible = False
End If
End Sub

Cannot interrupt running operation in vba when command button is being pressed

I researched net, but I haven`t found a solution yet and I am still grappling with the following problem:
In vba UserForm I have two command buttons:
1st one ('Run Operation') runs an operation that could take around 30 minutes.
2nd one ('Cancel') was created to interrupt the operation that was triggered off by 'Run Operation'
When I press 'Run Operation' button I cannot press neither 'Cancel' nor 'x' to stop the running operation and I have to wait until the operation finishes, thus the userform is freezed for around 30 minutes.
Code looks more or less like this:
private Sub Cancel_Click()
Cancel = True
End Sub
private Sub RunOperation_Click()
RunOperation.Enabled = False
Call Macro()
End Sub
private Sub Macro()
For i = 1 to 100
'do stuff here
If Cancel = True Then
RunOperation.Enabled = True
Exit Sub
Exit If
Next i
End Sub
What`s more both buttons have TakeFocusOnClick set to False.
I`d be grateful for any ideas.
Thanks in advance !
The DoEvents method is your friend here.
What's happening is that since VBA is single-threaded (i.e. only one macro can be running at a time) it's not possible for events (in your case Cancel_Click()) to trigger. The DoEvents method essentially pauses the code wherever it appears to see if any other events have been triggered and resolves them before code execution is resumed.
Try this, it should work:
Private Sub Macro()
For i = 1 To 100
'do stuff here
DoEvents '<~~ Insert this line here
If Cancel = True Then
RunOperation.Enabled = True
Exit Sub
End If
Next i
End Sub

VBA Macro On Timer style to run code every set number of seconds, i.e. 120 seconds

I have a need to run a piece of code every 120 seconds. I am looking for an easy way to do this in VBA. I know that it would be possible to get the timer value from the Auto_Open event to prevent having to use a magic number, but I can't quite get how to fire off a timer to get something to run every 120 seconds.
I don't really want to use an infinite loop with a sleep if I can avoid it.
EDIT:
Cross-post based on an answer provided is at: Excel VBA Application.OnTime. I think its a bad idea to use this... thoughts either way?
When the workbook first opens, execute this code:
alertTime = Now + TimeValue("00:02:00")
Application.OnTime alertTime, "EventMacro"
Then just have a macro in the workbook called "EventMacro" that will repeat it.
Public Sub EventMacro()
'... Execute your actions here'
alertTime = Now + TimeValue("00:02:00")
Application.OnTime alertTime, "EventMacro"
End Sub
Yes, you can use Application.OnTime for this and then put it in a loop. It's sort of like an alarm clock where you keep hittig the snooze button for when you want it to ring again. The following updates Cell A1 every three seconds with the time.
Dim TimerActive As Boolean
Sub StartTimer()
Start_Timer
End Sub
Private Sub Start_Timer()
TimerActive = True
Application.OnTime Now() + TimeValue("00:00:03"), "Timer"
End Sub
Private Sub Stop_Timer()
TimerActive = False
End Sub
Private Sub Timer()
If TimerActive Then
ActiveSheet.Cells(1, 1).Value = Time
Application.OnTime Now() + TimeValue("00:00:03"), "Timer"
End If
End Sub
You can put the StartTimer procedure in your Auto_Open event and change what is done in the Timer proceedure (right now it is just updating the time in A1 with ActiveSheet.Cells(1, 1).Value = Time).
Note: you'll want the code (besides StartTimer) in a module, not a worksheet module. If you have it in a worksheet module, the code requires slight modification.
In Workbook events:
Private Sub Workbook_Open()
RunEveryTwoMinutes
End Sub
In a module:
Sub RunEveryTwoMinutes()
//Add code here for whatever you want to happen
Application.OnTime Now + TimeValue("00:02:00"), "RunEveryTwoMinutes"
End Sub
If you only want the first piece of code to execute after the workbook opens then just add a delay of 2 minutes into the Workbook_Open event
(This is paraphrased from the MS Access help files. I'm sure XL has something similar.) Basically, TimerInterval is a form-level property. Once set, use the sub Form_Timer to carry out your intended action.
Sub Form_Load()
Me.TimerInterval = 1000 '1000 = 1 second
End Sub
Sub Form_Timer()
'Do Stuff
End Sub
I've found that using OnTime can be painful, particularly when:
You're trying to code and the focus on the window gets interrupted
every time the event triggers.
You have multiple workbooks open, you close the one that's supposed to use the timer, and it keeps triggering and reopening the workbook (if you forgot to kill the event properly).
This article by Chip Pearson was very illuminating. I prefer to use the Windows Timer now, instead of OnTime.
My solution:
Option Explicit
Public datHora As Date
Function Cronometro(action As Integer) As Integer
'This return the seconds between two >calls
Cronometro = 0
If action = 1 Then 'Start
datHora = Now
End If
If action = 2 Then 'Time until that moment
Cronometro = DateDiff("s", datHora, Now)
End If
End Function
How to use? Easy...
dummy= Cronometro(1) ' This starts the timer
seconds= Cronometro(2) ' This returns the seconds between the first call and this one