Access VBA: Form timer not showing MsgBox at the intended time - ms-access

I am trying to get the DB to close after 2 minutes have passed by (for testing purposes I am just making a MsgBox appear after the 2 minutes). To do this, I have a main form called DTForm and a hidden form called Timer. Both forms open on opening the DB but Timer opens in hidden mode.
AutoExec Macro:
1. Open DTForm (the main form)
2. Open Timer (the hidden form)
Module 1:
Option Compare Database
Option Explicit
Public timer_start As Date
Public timer_end As Date
Public timer_diff As Integer
DTForm (users will only see this form)
Option Compare Database
Option Explicit
Public Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
timer_start = Time
timer_end = DateAdd("n", 2, timer_start)
End Sub
Timer (the hidden form):
Option Compare Database
Option Explicit
Public Sub Form_Open(Cancel As Integer)
timer_start = Time
timer_end = DateAdd("n", 2, Time)
End Sub
Public Sub Form_Load()
timer_start = Time
timer_end = DateAdd("n", 2, Time)
End Sub
Public Sub Form_Timer()
timer_diff = DateDiff("n", timer_end, Time)
If timer_diff >= 0 Then
'Application.Quit
MsgBox "timer reached 0"
timer_start = Time
timer_end = DateAdd("n", 2, Time)
End If
End Sub
Update - the problem: So now the problem is the mouse. It looks like just moving the mouse around the form does nothing. However, moving the mouse from the navigation pane to the form and back (in and out) does trigger the mouse movement. This seems really counter-intuitive - why not just consider all mouse movements?
Notes: Added Option Explicit to the tops of both forms and fixed some missing variable declarations.

This seems to be a variable scope issue to me. By default your variables are local.
Unless timer_end is declared as a global or public variable, it is out of scope as soon as you leave the sub where it is defined. So the timer_end in Form_timer is a totally different variable than the one in your MouseMove event (even though they have the same name).
This is one reason why many people put "option explicit" at the beginning of their code as it forces you to declare your variables.
You could also pursue a design strategy where you pass the variable as a parameter instead of making it global or public

If you want to go through with it, the Form_MouseMove event must be in every form, because only the active form receives the MouseMove event.
Note: you actually don't need the timer_start variable at all. You can remove it and keep only:
Public Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
timer_end = DateAdd("n", 2, Time)
End Sub
Or somewhat cleaner:
Public Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
Call UpdateTimer()
End Sub
and in your Module1:
Public Sub UpdateTimer()
timer_end = DateAdd("n", 2, Time)
End Sub
in case you decide to change the time interval...
Note:
timer_diff should be a local variable in Timer.Form_Timer(), since it's only used there.

DateDiff("s", timer_end, Time) will return a negative value until the 10 seconds of inactivity, then the condition must be changed to >= and the time interval to 1000 (1 second) 1 millisecond is too fast.
Also to make sure the variables are ok add option explicit clause
Option Compare Database
Option Explicit
Dim timer_start as Variant
Dim timer_end as Variant
Public Sub Form_Open(Cancel As Integer)
timer_start = Time
End Sub
Public Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
timer_start = Time
timer_end = DateAdd("s", 10, timer_start)
Me.Label6.Caption = timer_start
Me.Label8.Caption = timer_end
End Sub
Public Sub Form_Timer()
If DateDiff("s", timer_end, Time) >= 0 Then
MsgBox "timer reached 0"
End If
End Sub

Related

Access 2016 set control events at runtime

Is there a way to add an event handler for a control at runtime?
Regard this code (in the form I have only TextBox1):
Option Compare Database
Option Explicit
Dim WithEvents tb As Access.TextBox
Private Sub Form_Load()
set tb = TextBox1
End Sub
Private Sub tb_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
Debug.Print "tb_MouseDown"
End Sub
Private Sub TextBox1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
Debug.Print "TextBox1_MouseDown"
End Sub
With that code, the two handlers are called.
If I remove the handler TextBox1_MouseDown, the tb_MouseDown is not fired.

Access VBA variables don't seem to clear after sub routine, getting procedure declaration does not match

I have a database that is used by a production team for performing reviews. It contains a list of questions, and most questions require an exception be entered if the answer is 'No.' They are currently updating these fields completely with comboboxes. I am trying to program it so that after each answer, the portion of the exception that will always be the same based on that answer doesn't have to be updated. I have started by declaring the variables (both within the sub routine as well as at the top as public variables). My thought process was that every time they answer a question, the variables would be declared for that specific question and the pop-up form would fill based on those variables. My code works as expected until I try to add the same code to a different question with the variables changed. There are a few variables that would always be the same, but I am not concerned with cleaning that up just yet. My code is below:
Option Compare Database
Option Explicit
Public LnNum As Long
Public PCID As Long
Public QCID As String
Public Cat As String
Public ExcDesc As String
Private Sub QCSP15_AfterUpdate()
LnNum = Me.Loan_Number.Value
PCID = Me.PreCloseID.Value
QCID = "QCSP15"
Cat = "Preliminary Application"
ExcDesc = "The initial 1003 was not found in the file."
If Form_frmPreCloseAudit.QCSP15.Value = "No" Then
DoCmd.OpenForm "frmTestExceptions"
DoCmd.GoToRecord acDataForm, "frmTestExceptions", acNewRec
Form_frmTestExceptions.Loan_Number.Value = LnNum
Form_frmTestExceptions.PreClose_ID.Value = PCID
Form_frmTestExceptions.QCSPID = QCID
Form_frmTestExceptions.QCSP_Category.Value = Cat
Form_frmTestExceptions.Exception_Text.Value = ExcDesc
Else
DoCmd.GoToControl "QCSP160"
End If
End Sub
Private Sub QCSP154_AfterUpdate(Cancel As Integer)
LnNum = Me.Loan_Number.Value
PCID = Me.PreCloseID.Value
QCID = "QCSP154"
Cat = "Preliminary Application"
ExcDesc = "The DocuSign Certificate of Completion for the initial disclosure package was not uploaded to Doc V."
If Form_frmPreCloseAudit.QCSP154.Value = "No" Then
DoCmd.OpenForm "frmTestExceptions"
DoCmd.GoToRecord acDataForm, "frmTestExceptions", acNewRec
Form_frmTestExceptions.Loan_Number.Value = LnNum
Form_frmTestExceptions.PreClose_ID.Value = PCID
Form_frmTestExceptions.QCSPID = QCID
Form_frmTestExceptions.QCSP_Category.Value = Cat
Form_frmTestExceptions.Exception_Text.Value = ExcDesc
Else
DoCmd.GoToControl "QCSP161"
End If
End Sub
Once I add the second 'After Update' sub procedure for the next question, I get the procedure declaration does not match error. I have tried naming the variables different things, but nothing seems to work and it might be beyond my ability to figure out as a novice.
Cancel As Integer is not supported for the After Update event. Remove it from the declaration:
'Private Sub QCSP154_AfterUpdate(Cancel As Integer)
Private Sub QCSP154_AfterUpdate()
Avoid that problem in the future by letting Access start you off with a code stub for the event procedure. Do that from the Event tab of the property sheet. Find the event you want and click the 3-dot icon on the far right side of the adjoining input box.

Clear text box on form after a few seconds

I would like to find the best way to have a text box displays a message everytime another control (a button) is pushed. Each time the button is pushed, the message will change and that message should show in my text box. The trick I would like to do is after the user stops pressing the button, that after a certain period (3 seconds) the text box will disappear.. (perhaps the message can be deleted). What is the correct event to use ?
Basically, for each control named 'msgPrincipio' in the code below, i would like that message to appear within the text box for 3 seconds and then disappear:
Private Sub Form_Timer()
Dim intTimerStart As Integer, intTimerUsed As Integer
Dim intCountdown As Integer
On Error GoTo Err_Handle
If Me!msgPrincipio <> "" Then
If intTimerStart > 0 Then
intTimerUsed = CLng((Timer / 60) - intTimerStart)
Else
intTimerStart = CLng(Timer / 60)
End If
If intCountdown > 3 Then
Me!msgPrincipio = ""
End If
intCountdown = intCountdown + 1
End If
Err_Exit: Exit Sub
Err_Handle: Resume Next
End Sub
Dim intTimerStart as Integer, intTimerUsed as Integer
Dim intCountdown as Integer
Sub Form_Timer()
On Error GoTo Err_Handle
If Me!MyBox <> "" Then
If intTimerStart > 0 Then
intTimerUsed = CLng((Timer / 60) - intTimerStart)
Else
intTimerStart = CLng(Timer / 60)
End If
If intCountdown > 3 Then
Me!MyBox = ""
End If
intCountdown = intCountdown + 1
End If
Err_Exit: Exit Sub
Err_Handle: Resume Next
End Sub
You also need to go to the form's design view and set the "Timer Interval" property on the form to an appropriate value. This code assumes 1,000 (1 second).
You almost never want to use Resume Next, but it's good here -- the goal is to pass through this block of code as seamlessly as possible. (Which you can accomplish with simple On Error Resume Next at the start -- but I don't like seeing it in my code that way, not one bit. I do this so I'll easily recognize it's by design, not carelessness.)
New to Access' Form Timer?
Private Sub Form_Timer()
Debug.Print Time ' Update time display.
End Sub
Put this code in the form's VBA module. Return to the form design view and switch to form view. Now go back to VBA and check your Immediate window. You should see evidence the form timer event is kicking. Note the Timer property of the form (found under form properties, design view) must not be blank or zero. It needs an entry to kick.
Using C#
using System.Windows.Forms;
public partial class Form1 : Form
{ private Timer x = new Timer();
public Form1()
{
x.Interval = (6000); //1 second = 1000
x.Tick += new EventHandler(TimerTask);
x.Start();
}
private void TimerTask(object sender, EventArgs e)
{
TextboxName.Text = String.Empty;
}
To set a label content dispear automatically: https://gamespec.tech/how-to-clear-textbox-after-few-seconds-in-c-sharp/#3-set-label-content-and-make-it-disappear-automatically

MouseMove High CPU Usage - Looking for better and elegant solution

I'm working on an Access 2007 application and have some concerns about performance with MouseMove over labels and form.
So far with my solution I'm getting high cpu usage on a dual core I5 3.0ghz.
When I move the mouse cpu usage jumps to about 30-32% of one core.(With hyperthreading on)
For such a trivial task as a MouseMove, I'd like to have something a bit more efficient :)
The code below as been shortened; I have 8 labels with MouseMove event handler.
Here's how it's implemented:
Private moveOverOn As Boolean
Private Property Get isMoveOverOn() As Boolean
isMoveOverOn = moveOverOn
End Property
Private Property Let setMoveOverOn(value As Boolean)
moveOverOn = value
End Property
'label MouseMove detection
Private Sub lbl_projects_completed_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
If Shift = 0 And isMoveOverOn = False Then
Me.lbl_projects_completed.FontBold = True
setMoveOverOn = True
End If
End Sub
'main form MouseMove detection
Private Sub Detail_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
If isMoveOverOn Then
resetBold 'call a sub that reset all labels .FontBold
setMoveOverOn = False
End If
End Sub
I don't know if it's possible, but I think that reducing the speed at which the MouseMove
is refreshed would help for this task, unfortunately I wasn't able to find information about it.
I'm opened to suggestions, thanks for your time! :)
The accdb format has hover and press color properties for buttons, so if you don't mind converting to that format and the labels could be buttons that should work much better than what you have going on.
Okay so this will do what you want with less of an expense but just know mouse move does not update X,Y when over a control so it has intermittent issues with the event.
This is custom implementation of a mouseHover event using mouse move on the detail section so it is only called 1 time. It then loops through the controls (you can change this loop to only look at controls you want) and sees if the cursor is within 5 twips of the control on any side
It also accepts a fuzziness parameter because of the lack of updating when over a control. The default it 50 twips. Also know that the controls should be shrunk to the minimum size possible to fit the data as this function uses the controls height and width to determine if you are inside of the control.
Private Sub Detail_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
mouseHover X, Y
End Sub
Private Sub mouseHover(X As Single, Y As Single)
Dim ctrl As Control
'You may want to make an array of controls here to shorten the loop
'i.e.
' Dim ctrl_array() As Variant
' ctrl_array(0) = Me.lbl_projects_completed
' ctrl_array(1) = Me.some_other_label
' For Each ctrl in ctrl_array
For Each ctrl In Me.Controls
If ctrl.ControlType = acLabel Then
If FuzzyInsideControl(ctrl.top, ctrl.left, ctrl.width, ctrl.height, X, Y) Then
ctrl.FontBold = True
ctrl.ForeColor = RGB(255, 0, 0)
Exit For
Else
ctrl.ForeColor = RGB(0, 0, 0)
ctrl.FontBold = False
End If
End If
Next ctrl
End Sub
Private Function FuzzyInsideControl(top As Long, left As Long, width As Long, height As Long, X As Single, Y As Single, Optional fuzz As Integer = 50) As Boolean
Dim coord_left As Long
Dim coord_right As Long
Dim coord_top As Long
Dim coord_bottom As Long
Dim inside_x As Boolean
Dim inside_y As Boolean
coord_top = top - fuzz
coord_bottom = top + height + fuzz
coord_left = left - fuzz
coord_right = left + width + fuzz
inside_y = Y > coord_top And Y < coord_bottom
inside_x = X > coord_left And X < coord_right
FuzzyInsideControl = inside_x And inside_y
End Function
While I still think that this is unnecessary it was an interesting question and fun to work with but there are some of limitations due to how mouseMove works
Edit
Changed the FuzzyInsideControl function for a cleaner more concise version should be more accurate although I will have to test tomorrow when I get back to a computer with access.
Finally I found what I was looking for to reduce the MouseMove strain on the CPU:
'put this in head of the form code
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
'form MouseMove with sleep timer
Private Sub Detail_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
'placeholder call sub to change the label FontBold Suggested by engineersmnky
Sleep (25)
End Sub

How do I access the selected rows in Access?

I have a form which includes a data sheet. I would like to make it possible for a user to select multiple rows, click on a button and have some sql query run and perform some work on those rows.
Looking through my VBA code, I see how I can access the last selected record using the CurrentRecord property. Yet I don't see how I can know which rows were selected in a multiple selection. (I hope I'm clear...)
What's the standard way of doing this? Access VBA documentation is somewhat obscure on the net...
Thanks!
I used the technique similar to JohnFx
To trap the Selection height before it disappears I used the Exit event of the subform control in the Main form.
So in the Main form:
Private Sub MySubForm_Exit(Cancel As Integer)
With MySubForm.Form
m_SelNumRecs = .SelHeight
m_SelTopRec = .SelTop
m_CurrentRec = .CurrentRecord
End With
End Sub
Here is the code to do it, but there is a catch.
Private Sub Command1_Click()
Dim i As Long
Dim RS As Recordset
Dim F As Form
Set F = Me.sf.Form
Set RS = F.RecordsetClone
If F.SelHeight = 0 Then Exit Sub
' Move to the first selected record.
RS.Move F.SelTop - 1
For i = 1 To F.SelHeight
MsgBox RS![myfield]
RS.MoveNext
Next i
End Sub
Here's the catch:
If the code is added to a button, as soon as the user clicks that button, the selection is lost in the grid (selheight will be zero). So you need to capture that info and save it to a module level variable either with a timer or other events on the form.
Here is an article describing how to work around the catch in some detail.
http://www.mvps.org/access/forms/frm0033.htm
Catch 2: This only works with contiguous selections. They can't select mutliple non-sequential rows in the grid.
Update:
There might be a better event to trap this, but here is a working implementation using the form.timerinterval property that i have tested (at least in Access 2k3, but 2k7 should work just fine)
This code goes in the SUBFORM, use the property to get the selheight value in the master form.
Public m_save_selheight As Integer
Public Property Get save_selheight() As Integer
save_selheight = m_save_selheight
End Property
Private Sub Form_Open(Cancel As Integer)
Me.TimerInterval = 500
End Sub
Private Sub Form_Timer()
m_save_selheight = Me.selheight
End Sub
I've tried doing something like that before, but I never had any success with using a method that required the user to select multiple rows in the same style as a Windows File Dialog box (pressing Ctrl, Shift, etc.).
One method I've used is to use two list boxes. The user can double click on an item in the left list box or click a button when an item is selected, and it will move to the right list box.
Another option is to use a local table that is populated with your source data plus boolean values represented as checkboxes in a subform. After the user selects which data they want by clicking on checkboxes, the user presses a button (or some other event), at which time you go directly to the underlying table of data and query only those rows that were checked. I think this option is the best, though it requires a little bit of code to work properly.
Even in Access, I find sometimes it's easier to work with the tables and queries directly rather than trying to use the built-in tools in Access forms. Sometimes the built-in tools don't do exactly what you want.
A workaround to the selection loss when the sub form loses the focus is to save the selection in the Exit event (as already mentioned by others).
A nice addition is to restore it immediately, using timer, so that the user is still able to see the selection he made.
Note: If you want to use the selection in a button handler, the selection may not be restored already when it executes. Make sure to use the saved values from the variables or add a DoEvents at the beginning of the button handler to let the timer handler execute first.
Dim m_iOperSelLeft As Integer
Dim m_iSelTop As Integer
Dim m_iSelWidth As Integer
Dim m_iSelHeight As Integer
Private Sub MySubForm_Exit(Cancel As Integer)
m_iSelLeft = MySubForm.Form.SelLeft
m_iSelTop = MySubForm.Form.SelTop
m_iSelWidth = MySubForm.Form.SelWidth
m_iSelHeight = MySubForm.Form.SelHeight
TimerInterval = 1
End Sub
Private Sub Form_Timer()
TimerInterval = 0
MySubForm.Form.SelLeft = m_iSelLeft - 1
MySubForm.Form.SelTop = m_iSelTop
MySubForm.Form.SelWidth = m_iSelWidth
MySubForm.Form.SelHeight = m_iSelHeight
End Sub
There is another solution.
The code below will show the number of selected rows as soon as you release the mouse button.
Saving this value will do the trick.
Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
MsgBox Me.SelHeight
End Sub
Use a Global variable in the form, then refer to that in the button code.
Dim g_numSelectedRecords as long
Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
g_numSelectedRecords = Me.SelHeight
End Sub
Dim formRecords As DAO.Recordset
Dim i As Long
Set formRecords = Me.RecordsetClone
' Move to the first record in the recordset.
formRecords.MoveFirst
' Move to the first selected record.
formRecords.Move Me.SelTop - 1
For i = 1 To numSelectedRecords
formRecords.Edit
formRecords.Fields("Archived") = True
formRecords.Update
formRecords.MoveNext
Next i
Why not use an array or recordset and then every time the user clicks on a row (either contiguous or not, save that row or some identifier into the recordset. Then when they click the button on the parent form, simply iterate the recordset that was saved to do what you want. Just don't forget to clear the array or recordset after the button is clicked.?
Another workaround to keeping the selection while attempting to execute a procedure - Instead of leaving the datasheet to activate a button, just use the OnKeyDown event and define a specific keycode and shift combination to execute your code.
The code provided by JohnFx works well. I implemented it without a timer this way (MS-Access 2003):
1- Set the Form's Key Preview to Yes
2- put the code in a function
3- set the event OnKeyUp and OnMouseUp to call the function.
Option Compare Database
Option Explicit
Dim rowSelected() As String
Private Sub Form_Load()
'initialize array
ReDim rowSelected(0, 2)
End Sub
Private Sub Form_Current()
' if cursor place on a different record after a selection was made
' the selection is no longer valid
If "" <> rowSelected(0, 2) Then
If Me.Recordset.AbsolutePosition <> rowSelected(0, 2) Then
rowSelected(0, 0) = ""
rowSelected(0, 1) = ""
rowSelected(0, 2) = ""
End If
End If
End Sub
Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
rowsSelected
If KeyCode = vbKeyDelete And Me.SelHeight > 0 Then
removeRows
End If
End Sub
Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
rowsSelected
End Sub
Sub rowsSelected()
Dim i As Long, rs As DAO.Recordset, selH As Long, selT As Long
selH = Me.SelHeight
selT = Me.SelTop - 1
If selH = 0 Then
ReDim rowSelected(0, 2)
Exit Sub
Else
ReDim rowSelected(selH, 2)
rowSelected(0, 0) = selT
rowSelected(0, 1) = selH
rowSelected(0, 2) = Me.Recordset.AbsolutePosition ' for repositioning
Set rs = Me.RecordsetClone
rs.MoveFirst ' other key touched caused the pointer to shift
rs.Move selT
For i = 1 To selH
rowSelected(i, 0) = rs!PositionNumber
rowSelected(i, 1) = Nz(rs!CurrentMbr)
rowSelected(i, 2) = Nz(rs!FutureMbr)
rs.MoveNext
Next
Set rs = Nothing
Debug.Print selH & " rows selected starting at " & selT
End If
End Sub
Sub removeRows()
' remove rows in underlying table using collected criteria in rowSelected()
Me.Requery
' reposition cursor
End Sub
Private Sub cmdRemRows_Click()
If Val(rowSelected(0, 1)) > 0 Then
removeRows
Else
MsgBox "To remove row(s) select one or more sequential records using the record selector on the left side."
End If
End Sub