I have an access form with a text-box that user can update using a list-box or by editing it directly, I need to make an action if the user starts to delete from this text-box, if he starts to add data no problem only if he starts to delete from it, something like :
Private Sub textbox_AfterUpdate()
If Me.[textbox].SelStart = Len(Me.[textbox].Text) - 1 Then
' do something
else
' do something else
End If
End Sub
You might want to use the KeyDown event instead of AfterUpdate
This captures the Backspace and the Del button.
Private Sub Text0_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = 8 Or KeyCode = 46 Then
MsgBox "Text was deleted"
End If
End Sub
There can be other scenarios where the text is deleted for example Ctrl + X So you can trap all that.
You might like to consider the change event, the text property is the current unsaved content of the control that has focus and the value property is the saved content of the control with focus.
The advantage of Change is that the user cannot simply insert letters into the existing string, for example, the user cannot change 20 meters to 200 meters.
Private Sub AText_Change()
''If the current content is the same as the
''previous content with something added
If Me.AText.Text Like Me.AText.Value & "*" Then
''Ok
Else
''Problem
MsgBox "Not allowed"
Me.AText = Me.AText.Value
End If
End Sub
You could also allow the saved content to be a substring of the current content.
Related
I have a textBox formatted as "Short Date". When I put invalid data in field, for example random "dfsdf", and try to change focus, form throws validation Error 2113.
My goal is to give an opportunity to user to close form by click on "Cancel" button without any problem, because no matter what he entered in Date textbox while form is canceled.
I can handle this error and disable message with Form_Error event, but focus stays set to date textBox anyway, when i try to click Cancel button, and button is never clicked.
I use a setup, probably similar to yours:
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Const InvalidDate As Long = 2113
Select Case DataErr
Case InvalidDate
MsgBox "Invalid date!"
Response = acDataErrContinue
' Cancel invalid entry.
Me.Undo
' DoCmd.Close
Case Else
Response = acDataErrDisplay
End Select
End Sub
Problem is, that this will fire before your button gets focus. This means, that the form doesn't even know what you clicked, only that it was somewhere outside the date textbox.
Thus, the DoCmd.Close would close the form for whatever was clicked.
You may turn it into an advantage, that the user is able to watch the removal of the faulty input ...
You should use the after update event to check if the user entered a valid date,
if not then clear or undo. Without knowing the controls of your form i can't tell if you allow users to change table values or if you are using transactions.
pseudo code:
Private Sub Text1_AfterUpdate()
If Check_If_Date Then
Execute_Code
Else
End If
End Sub
Private Function Check_If_Date() as Boolean
If IsDate(Text1) then
Check_If_Date = True
Else
Check_If_Date = False
Select Case msgbox("Not a valid date", vbOKCancel)
case vbOK
reset_value
case vbCancel
Close_Form
case else
End Select
End If
End Sub
Private Sub Reset_Value()
'need to clear value or undo
End Sub
Public Sub Execute_Code()
'Code to save values or allow Update to complete
End Sub
Private Sub Close_Form()
'Code For Closing Form and logging errors
End Sub
I split up repeatable actions for reuse in case the same thing can be repeated in another text box for example a second date or to get the same behavior for the other controls.
I have an Access database with 10+ text controls. I'd like to have some code to handle the CTRL + A KeyPress event. Normally, when pressing CTRL + A in Access, this selects all records. My end goal is to have CTRL + A only select that control's text (like pressing CTRL + A in your browser's URL bar, it only selects THAT text) so that I can delete only that control's text. I checked this article, as I wanted something that could handle any text box (handling each textbox's KeyPress = 60+ lines of code). Is there any way I could have, say, a for-next loop?
Function HandleKeyPress(frm As Form, KeyAscii As Integer, ByVal e As KeyPressEventArgs) 'should it be a function or a sub?
For Each ctl In Me.Controls
If KeyAscii = 1 Then 'I think this is CTRL + A?
e.Handled = True 'Stop this keypress from doing anything in access
focused_text_box.SelStart = 0
focused_text_box.SelLength = Len(focused_text_box.Text)
End If
Next
End Function
Along with this, how can I pass to this sub/function the text box's name?
Note: In case you haven't noticed yet, I'm still a noob with VBA/Access.
Your current approach will not work, since it contains multiple things that just don't work that way in VBA (as June7 noted), and since form keydown events take priority over textbox keydown events
You can use the following code instead (inspired by this answer on SU):
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyA And Shift = acCtrlMask Then 'Catch Ctrl+A
KeyCode = 0 'Suppress normal effect
On Error GoTo ExitSub 'ActiveControl causes a runtime error if none is active
If TypeOf Me.ActiveControl Is TextBox Then
With Me.ActiveControl
.SelStart = 0
.SelLength = Len(.Text)
End With
End If
End If
ExitSub:
End Sub
It's important to set the Form.KeyPreview property to True, either using VBA or just the property pane, to allow this function to take priority over the default behaviour.
I have a form with a subform. The subform is a continuous form so I can use conditional formatting. Using controls in a Tab Control, the values of the currently selected record on the subform are changed. So, I requery the subform to update the subform continuous form to show the updated data.
I can get the correct record re-selected in the subform, but the position of that record in the list jumps to the top of the subform's list instead of maintaining the position it was in prior to the update.
I have tried playing with the CurrentSectionTop values of the subform, but I am not able to correctly maintain the user's selection position within the subform after requerying the subform.
Is there some way to get the current position of the subform's continuous form's scrollbar position and then set that position in code after the continuous form is requeried? (Stephen Lebans' code for doing this (see: http://www.lebans.com/SelectRow.htm) does not work for me because I'm using Access 2013 and his code won't convert to Access 2013).
Here's a sample of what the subform continuous form display might look like to begin with while Record 7 is the current record selected:
{start of continuous form view}
[ ] Record 3 in continuous form view
[ ] Record 4 in continuous form view
[ ] Record 5 in continuous form view
[ ] Record 6 in continuous form view
[>] Record 7 in continuous form view
[ ] Record 8 in continuous form view
[ ] Record 9 in continuous form view
{end of continuous form view}
{tab control displays down here below the continuous form subform}
After the subform is requeried, here is what the subform continuous form display looks like, but I want the display to look the same as above; the display should not put Record 7 as the top record in the continuous form view since it was the 5th record down in the view originally so I want it to be the 5th record down after the requery:
{start of continuous form view}
[>] Record 7 in continuous form view
[ ] Record 8 in continuous form view
[ ] Record 9 in continuous form view
[ ] Record 10 in continuous form view
[ ] Record 11 in continuous form view
[ ] Record 12 in continuous form view
[ ] Record 13 in continuous form view
{end of continuous form view}
{tab control displays down here below the continuous form subform}
I couldn't get Wayne G Dunn's solution working, but I came up with this alternative. It's not wonderful, but it worked for me.
The basic idea is that each record in the continuous form has a position (ie top record showing on the screen is in position 1, regardless which actual record it is). You have a table that relates those positions, to the Form.currentSectionTop property of each record in the form, so you can figure out what position the current record is in. Then it's relatively straightforward to return to that position after the requery.
Create the table with the positions - this needs to run on startup or somewhere - might need to be more frequent if the user can resize or anything might change the number of records that could be shown in the continuous form.
Public Sub Setup_Positions()
Dim sql As String
Dim Position As Long
Dim currentSectionTop As Long
Dim lastSectionTop As Long
sql = "DELETE FROM tblRecordPosition"
currentdb.execute sql
DoCmd.GoToRecord , , acFirst
Position = 1
Call Set_NoUpdate
With Forms("frmMain").Controls("frmContinuousSubForm").Form
currentSectionTop = .currentSectionTop
Do While currentSectionTop <> lastSectionTop
'record previous sectiontop
lastSectionTop = currentSectionTop
'write it into the table
sql = "INSERT INTO tblRecordPosition (Position, CurrentSectionTop) " & _
"SELECT " & Position & ", " & _
currentSectionTop
CurrentDb.Execute sql
'update to next position and record the 'last' one, move to next record. When we've run out of visible ones, the last and current will be the same.
Position = Position + 1
DoCmd.GoToRecord , , acNext
'get new current sectiontop
currentSectionTop = .currentSectionTop
Loop
End With
Call Set_NoUpdateOff
End Sub
Set up global variables and a couple of functions to maintain them. The 'NoUpdateRequired' variable is optional - I use it to prevent unnecessary stuff running all the time.
Public NoUpdateRequired As Boolean
Public Position As Long
Public Sub Set_NoUpdate()
NoUpdateRequired = True
End Sub
Public Sub Set_NoUpdateOff()
NoUpdateRequired = False
End Sub
Create this function to convert between the property you can measure, and the actual position:
Public Function Get_Position(Optional InputCurrentSectionTop As Long) As Long
Dim currentSectionTop As Long
Dim Position As Long
If InputCurrentSectionTop > 0 Then
currentSectionTop = InputCurrentSectionTop
Else
currentSectionTop = Forms("frmMain").Controls("frmContinuousSubForm").Form.currentSectionTop
End If
Position = Nz(ELookup("Position", "tblRecordPosition", "CurrentSectionTop = " & currentSectionTop), 0)
Get_Position = Position
End Function
In the current event of the continuous form, you need this:
Private Sub Form_Current()
If NoUpdateRequired = False Then
Position = Get_Position
End If
End Sub
And finally, in the bit where you want your refresh to happen, you need this:
Public Sub Refresh_ContinuousSubForm()
'All this problem goes away if you can use Refresh instead of Requery, but if you have a few things editting the underlying table, you must use requery to avoid 'another user has changed the data' errors.
'However, this then causes the form to jump
'back to the first record instead of keeping the current record selected. To get around this, the following has been employed:
'the .seltop property allows you to select the top selected record (in most cases, only one record is selected). This is recorded before the refresh, and
'the form set back to that after the refresh. However, this puts the selected record at the top of the screen - confusing when you're working down a list.
'The .currentSectionTop property measures the number of twips from the selected record to the top of the screen - and correlates to which position in the list
'of 25 records in the bottom pane. tblRecordPosition converts between the twips to the actual position (recorded when the database is opened).
'The key to all this is that going back to the right record using .seltop only puts the record at the top of the screen IF the record wasn't already visible on the screen.
'But GoToRecord, if used when you're already at the top of the screen, will push the records down the screen as you move backward (upward) through them.
'So we go to the right record, and it will probably be at the top of the screen because of the requery. Then we push them down the screen back to the original position
'using GoToRecord, but now we're on the wrong record. Then we return to the right record using .seltop, and because it's already on the screen, it won't move position.
Dim startSeltop As Long
Dim newSectionTop As Long
Dim newPosition As Long
Dim startPosition As Long
Dim recordsToMove As Long
'Also global variable Position (long) which is recorded in the form's current event
Call Set_NoUpdate
startPosition = Position
With Forms("frmMain").Controls("frmContinuousSubForm").Form
.Painting = False 'stops the screen flickering between
startSeltop = .SelTop 'records which record we're on. Position represents where that was showing on the screen.
.Requery 'does the requery
.SelTop = startSeltop 'sets us back to the correct record
newSectionTop = .currentSectionTop 'measures in twips which position it's in (usually 1)
newPosition = Get_Position(newSectionTop) 'converts that to the position
recordsToMove = startPosition - newPosition 'calculates how many records to move - moving records using GoToRecord moves the position as well
If recordsToMove > 0 Then
DoCmd.GoToRecord , , acPrevious, recordsToMove 'moves back enough records to push our record to the right place on the screen
End If
.SelTop = startSeltop 'now sets back to the correct record
.Painting = True 'turns the screen painting back on
End With
Call Set_NoUpdateOff
End Sub
The following code is a subset of the code found on Stephen Lebans' website: http://www.lebans.com/SelectRow.htm . That link has a link to a zipped version of an Access database with all the code to handle multiple scenarios, however the database is an older version and needs to be converted. Mr Leban's code does far more than what is included here, but I am only using this code to solve one specific issue.
(A) Create a Class Module named 'clsSetRow' and paste in the following code:
Option Compare Database
Option Explicit
Private mSelTop As Long
Private mCurrentSectionTop As Long
Public Property Get SelTop() As Long
SelTop = mSelTop
End Property
Public Property Let SelTop(x As Long)
mSelTop = x
End Property
Public Property Get CurrentSectionTop() As Long
CurrentSectionTop = mCurrentSectionTop
End Property
Public Property Let CurrentSectionTop(x As Long)
mCurrentSectionTop = x
End Property
(B) In your module for your form, include the following at the top:
Private SR As clsSetRow
Dim lCurRec As Long
(C) Add the following Event Handlers and code:
Private Sub Form_Load()
Set SR = New clsSetRow
End Sub
Private Sub Form_Current()
' This event can be called during the Form Load event prior to the init of
' our class so we must test for this.
If Not SR Is Nothing Then
SR.SelTop = Me.SelTop
SR.CurrentSectionTop = Me.CurrentSectionTop
End If
End Sub
Private Sub Form_AfterInsert() ' OR JUST USE THE BEFOREINSERT
lCurRec = Me.CurrentRecord
'Debug.Print "After Insert, Current: " & Me.CurrentRecord
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer) ' OR JUST USE THE AFTERINSERT
lCurRec = Me.CurrentRecord
'Debug.Print "Before Insert, Current: " & Me.CurrentRecord
End Sub
(D) Wherever you want to reposition (i.e. after a REQUERY), add the following line of code:
DoCmd.GoToRecord acDataForm, Me.Name, acGoTo, lCurRec
(E) To test this, just add a command button that will 'Requery and then GoToRecord'.
NOTE: Simply scrolling up or down using the scrollbar will NOT save the row of where you are! You need to establish a 'current record' for this to reposition.
Good Luck! And thank you Stephen Lebans for the code!
I have a bound pop up form with a Clear data button. It runs the Undo command.
The Form_Current event sets this button's enable property to False.
The Form_Dirty event sets this button's enable property to True.
The button's property is always set to False even after I enter data into the form. I think this is because my Form_Load event is populating two fields. One is passed from the main form as an OpenArgs, the other is the unique ID which is calculated based on the OpenArgs value.
Any suggestions as to how to get the Dirty event to activate under these circumstances? If not, then an alternative approach perhaps?
Thanks in advance.
Code below:
Private Sub cmdUndoChanges_Click()
CustID_temp = Me!CustID
FacNo_temp = Me!Unique_ID
DoCmd.RunCommand acCmdUndo
Me!CustID = CustID_temp
Me!Unique_ID = FacNo_temp
End Sub
Private Sub Form_Current()
Me!cmdUndoChanges.Enabled = False
End Sub
Private Sub Form_Dirty(Cancel As Integer)
Me!cmdUndoChanges.Enabled = True
End Sub
Private Sub Form_Load()
Dim test As Integer
Me!CustID = OpenArgs
test = DCount("Unique_ID", "tbl_Table2", "CustID = '" & Me!CustID & "'")
If IsNull(Me!UNIQUE_No) Then
test = test + 1
Me!Unique_ID = CustID & "-" & test
MsgBox "No Previous Data"
Else
' these will be turned back on when the user selects the
' modify data button or add new data button.
For Each ctl In Me.Controls
Select Case ctl.ControlType
Case acTextBox, acComboBox, acOptionGroup, acCheckBox
ctl.Locked = True
ctl.BackColor = 15066597
Box40.BackColor = 15066597
End Select
Next ctl
End If
MsgBox Me!Unique_ID
End Sub
Have you tried Form_BeforeInsert() instead of Form_Dirty()?
I found another case where the Form_Dirty event never fires, the hard way. I thought I'd add it here since this is the top search result for the "not firing" question.
If your Form_Load function does anything to change the value of an existing record, the form will be set to dirty immediately, apparently before the logic that fires the event begins to run. Because of that there will never be an event if other data on the form is changed.
We had a "smart" Save button that was disabled until Form_Dirty fired. After I added a fixup to Form_Load for old records with a junk field, editing those records no longer ever enabled the Save button.
Is there any way to clear the textbox on keypress like in excel.
I tried the following code but it clears the text when clicking on the textbox. I want it to clear it when a certain key is pressed.
Private Sub Text10_GotFocus()
Text10.Value = ""
End Sub
You could select the control's entire text content whenever that control gets focus. Then your keypress would replace the selected text.
If you want that to happen for every text box on every form, you can set "Behavior entering field" setting to "Select entire field". (In Access 2007, find that setting from Office Button -> Access Options -> Advanced, then look under the Editing heading of that dialog. For Access 2003, see this page.)
Not only will that setting be applied to form controls, but also to tables and queries in datasheet view. If that's not what you want, you can use VBA in your form's module to select the text for only specific controls:
Private Sub MyTextBox_GotFocus()
Me.MyTextBox.SelStart = 0
Me.MyTextBox.SelLength = Len(Me.MyTextBox)
End Sub
If you want to do that for multiple controls, you could create a general procedure:
Private Sub SelectWholeField()
Me.ActiveControl.SelStart = 0
Me.ActiveControl.SelLength = Len(Me.ActiveControl)
End Sub
Then call that procedure from the got focus event of an individual control like this:
Private Sub MyTextBox_GotFocus()
SelectWholeField
End Sub
Private Sub Field1_KeyPress(KeyAscii As Integer)
If KeyAscii = 65 Then Me.Field1 = ""
'Or: If Chr(KeyAscii) = "A" Then Me.Field1 = ""
End Sub
change the number to the ascii value of whatever key you are wanting to use to clear the field
Declare a Form Level variable
Private CanDelete as Boolean
When the TextBox receives focus, set CanDelete to True
Private Sub txtTest_GotFocus()
CanDelete = True
End Sub
On the KeyPress event, clear the text if CanDelete is True
Private Sub txtTest_KeyPress(KeyAscii As Integer)
If CanDelete Then
Me.txtTest = ""
CanDelete = False
End If
End Sub