Clear text box on form after a few seconds - ms-access

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

Related

Refer to Form Control Without Specific Naming

I have a form with numerous images, each of which performs a series of actions when clicked. I can create a Private Sub with all of the actions for each button, however I think this is inefficient. Rather, I'd like to record all the actions in one Macro and then call this Macro when each image is clicked. To do so, I'd need the single Macro to refer to the current image selected and not refer to any image by name. Is this possible?
My current code includes the following:
Me.Image001.BorderColor = RGB(1, 1, 1)
Me.Image001.BorderWidth = 2
Me.Image001.BorderStyle = 1
I'd need to amend this so that it amends the border colour/width/style etc of whichever image is selected, and not a specific named image ('Image001').
Thanks!
You should use event sinking.
With event sinking you could bind to an event your own procedures.
You can see an example here http://p2p.wrox.com/access-vba/37472-event-triggered-when-any-control-changed.html
In simple words you create a module where you bind the event to your specific implementation . Then on the form you are interested you create a collection where you register the controls you want to "follow" the event sinking...
My sub sinking for checkboxes (i have alot)
1st a class module name SpecialEventHandler
Option Compare Database
Private WithEvents chkbx As CheckBox
Private m_Form As Form
Private Const mstrEventProcedure = "[Event Procedure]"
Public Function init(chkbox As CheckBox, frm As Form)
Set chkbx = chkbox
Set m_Form = frm
'Debug.Print frm.Name
chkbx.AfterUpdate = mstrEventProcedure
End Function
Private Sub chkbx_AfterUpdate()
'your Code here
End Sub
Private Sub Class_Terminate()
Set chkbx = Nothing
Set m_Form = Nothing
End Sub
Then on the form you want to use event sinking
Option Compare Database
Dim spEventHandler As SpecialEventHandler
Private colcheckBoxes As New Collection
Private Sub Form_Open(Cancel As Integer)
Dim ctl As Control
For Each ctl In Me.Detail.Controls
Select Case ctl.ControlType
Case acCheckBox
Set spEventHandler = New SpecialEventHandler
spEventHandler.init Controls(ctl.NAME), Me
colcheckBoxes.Add spEventHandler
End Select
Next ctl
End Sub
You could always create a Standard Sub, then call it on the click of the button. Something like
Public Sub changeColor(frm As Form, ctrl As Control)
frm.Controls(ctrl.Name).BorderColor = RGB(1, 1, 1)
frm.Controls(ctrl.Name).BorderWidth = 2
frm.Controls(ctrl.Name).BorderStyle = 1
End Sub
So when you click an image all you have to do is,
Private Sub Image001_Click()
changeColor Me, Image001
End Sub

selstart returns position 0 if text is entered in memo field (not clicked)

I have memo field and list. What I want to accomplish is if I am typing something in memo field and then just click on text record in list that the text shows up in memo positioned with the beginning where cursor was.
After research, and googling I succeed to make it. I did it with .selstart property.
But for me it seems that selstart has bug. It works only if I click somewhere in memo (Then everything works great.) But if was typing something, and then click on text in list (without previously clicking in memo field) selstart returns position 0.
This makes me huge problem.
Can anyone help? Thank you.
As you found out, the problem is that the cursor position is lost when you move away from the memo.
This is probably due to the fact that Access form controls are not "real" controls: they are real windows controls only when they have the focus. the rest of the time, they are sort of images of the control pasted onto the form.
So, what you need to do is track the cursor position (and currently selected length of text) during various interractions:
when the user moves the cursor using the keyboard (KeyUp event)
when the user clicks inside the memo (Click event, to position the cursor or select text using the mouse)
when the memo initially gets the focus (GetFocus, the first time, the whole text is selected and the cursor is at position 0)
To test this, I made a small form:
The added the following code to the form:
'----------------------------------------------------------
' Track the position of the cursor in the memo
'----------------------------------------------------------
Private currentPosition As Long
Private currentSelLen As Long
Private Sub txtMemo_Click()
RecordCursorPosition
End Sub
Private Sub txtMemo_GotFocus()
RecordCursorPosition
End Sub
Private Sub txtMemo_KeyUp(KeyCode As Integer, Shift As Integer)
RecordCursorPosition
End Sub
Private Sub RecordCursorPosition()
currentPosition = txtMemo.SelStart
currentSelLen = txtMemo.SelLength
End Sub
'----------------------------------------------------------
' Insert when the user double-click the listbox or press the button
'----------------------------------------------------------
Private Sub listSnippets_DblClick(Cancel As Integer)
InsertText
End Sub
Private Sub btInsert_Click()
InsertText
End Sub
'----------------------------------------------------------
' Do the actual insertion of text
'----------------------------------------------------------
Private Sub InsertText()
If Len(Nz(listSnippets.Value, vbNullString)) = 0 Then Exit Sub
Echo False 'Avoid flickering during update
' Update the Memo content
Dim oldstr As String
oldstr = Nz(txtMemo.Value, vbNullString)
If Len(oldstr) = 0 Then
txtMemo.Value = listSnippets.Value
Else
txtMemo.Value = Left$(oldstr, currentPosition) & _
listSnippets.Value & _
Mid$(oldstr, currentPosition + currentSelLen + 1)
End If
'We will place the cursor after the inserted text
Dim newposition As Long
newposition = currentPosition + Len(listSnippets.Value)
txtMemo.SetFocus
txtMemo.SelStart = newposition
txtMemo.SelLength = 0
currentPosition = newposition
currentSelLen = 0
Echo True
End Sub
I have made a test accdb database that you can download so you can see the details and play around with this.

How can I create a new instance of a form class and then open it as a acDialog box (pausing other code)?

Problem
I have a complex search form that I need to open several times (at the same time) to allow users to do multiple searches at the same time. I have found the ability to open an instance of the form (as it is a class) using:
Option Compare Database
Option Explicit
'Array to hold Form Instances
Private MyFormCollection(1 To 4) As Form_frmTest
Private Sub cmd_CloneMe_Click()
Dim intCounter As Integer
For intCounter = 1 To 4
Set MyFormCollection(intCounter) = New Form_frmTest
MyFormCollection(intCounter).Caption = "Form #" & intCounter
MyFormCollection(intCounter).Visible = True
Next intCounter
Forms!frmTest.SetFocus
End Sub
This is an example to open multiple instances of a form and not what I will use for the final code as I will only need to open one instant per click BUT will likely have other already open!
BUT I can't work out a way to one of those duplicates as an acDialog (so calling function pauses until you hide or close the new dialog form). Here is the way I would normally open a normal form (not a copy):
Call DoCmd.OpenForm("SomeForm", acNormal, , , , acDialog)
Question
How can I open a new instance of a form as an Dialog box (so calling function pauses until new form is closed or hidden)?
Thanks
You can use this code to check if a form is visible:
Private Function IsVisible(intObjType As Integer, strObjName As String) As Boolean
Dim intObjState As Integer
intObjState = SysCmd(acSysCmdGetObjectState, intObjType, strObjName)
IsVisible = intObjState And acObjStateOpen
End Function
If yes, do nothing but wait:
Do While IsVisible(acForm, "frmInfo")
DoEvents
Loop
==================
Ok, another idea: you either have the choice of opening a dialog modal and stop the code in the calling function, or live with more dialogs and have the calling function running.
I think the only way of solving this is a counter of the dialogs open, and refusing to do anything in the calling function when the count is still > 0.
You can write a public function in your called form and until that function returns (by closing the form) the calling method is blocked. In the example below clicking a "Done" button might set the return
Option Explicit
Private m_dlgResult As VbMsgBoxResult
Public Function ShowDialog() As VbMsgBoxResult
m_dlgResult = vbCancel
Me.Show vbModal
ShowDialog = m_dlgResult
End Function
Private Sub Done_Click()
m_dlgResult = vbOK
Unload Me
End Sub

global click event handler (WithEvents)

I am trying to create a class module that will act as a global handler for when ever someone clicks one of the sixty textboxes I have in my form. Th textboxes represent a timecard for the week displaying information as clock in, clock out, lunch start,end,duration, total daily hours under each fo the seven days of the week. When someone clicks anyone of the boxes under a day all the boxes will unlock and enable so that the user can edit the information in them.
After scouring the web for a solutio of a global click event I found that I could create a class module that would handle the event without create a click event for every single text box that calls a seperate function to handle the event. The problem I am having is that my class module doesn't seem to be handling my event and was wondering if someone could suggest a solution to my problem. FYI, All my textboxes and locked and disabled to prevent data corruption. Below is my code:
''# Class module
Option Compare Database
Option Explicit
Public WithEvents TC_txtbox As TextBox
''# Set the textbox so that its events will be handled
Public Property Set TextBox(ByVal m_tcTxtBox As TextBox)
TC_txtbox = m_tcTxtBox
End Property
''# Handle and onClick event of the
Private Sub TC_txtbox_Click()
''# Find out the controls that where clikck
Debug.Print Form_TimeCard.ActiveControl.Name
Dim ctl As Control
For Each ctl In access.Forms.Controls
Debug.Print ctl.Name
Next ctl
End Sub
Form Code
Option Compare Database
Option Explicit
''# Global Variables
Public clk_inout As Boolean
Public settings
Public weekDict
Public weekOf As Variant
Public curDay As Variant
Public txtBxCollection As Collection
''# Event Handler for when the form opens
Private Sub Form_Open(Cancel As Integer)
''# Configure varaibles
Me.TimerInterval = 60000 ''# 10 sec Interval
weekOf = getFirstDayofWeek(Date)
curDay = Date
Set weekDict = CreateObject("Scripting.Dictionary")
Set settings = CreateObject("Scripting.Dictionary")
Set txtBxCollection = New Collection
''# Load Time Card Data
Call initSettings
''# Debug.Print "Work Day Goal " & settings.Item("Work_day_goal_hrs")
Call initDict
Call initTextBoxEventHandler
Debug.Print "Collection count " & txtBxCollection.Count
Call loadDates(Date)
Call clearDay
Call selectDay(Date)
Call loadWeeksData(weekOf)
Dim ctl As Control
Set ctl = weekDict.Item(Weekday(curDay)).Item("In")
If IsDate(ctl.Value) And (Not ctl.Value = "") Then
Me.but_clk_inout.Caption = "Clock Out"
Me.but_lunch.Visible = True
clk_inout = False
Else
Me.but_clk_inout.Caption = "Clock In"
Me.but_lunch.Visible = False
clk_inout = True
End If
''# Debug.Print "Work Day Goal " & settings.Item("Salary")
End Sub
Public Sub initTextBoxEventHandler()
Dim eventHandler As TextBoxEventHandler
Set eventHandler = New TextBoxEventHandler
Debug.Print "Collection count " & txtBxCollection.Count
Set eventHandler.TextBox = Me.txt_F_in
txtBxCollection.Add eventHandler
Debug.Print "Collection count " & txtBxCollection.Count
End Sub
Are you missing a Set? The public property set should be
Public Property Set TextBox(ByVal m_tcTxtBox As TextBox)
Set TC_txtbox = m_tcTxtBox ' dont forget the Set! '
End Property
I figure out My problem in the class module where I am setting the text box I forgot to add "TC_txtbox.OnClick = "[Event Procedure]"" VBA will not fire the custom even handler in my extended textbox if [Event Procedure] is not declared in the property of the event you would like to handle

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