How to Absorb a Keypress? - ms-access

On my form I have an edit control. I have set up a KeyDown event to detect when the user pushes enter, but I also want to detect shift+space so the user can clear the contents of the box.
The functionality works - I can detect the keypress. Problem is that the space does not get absorbed and so the space glyph is still typed in the control.
How can I absorb the keypress when I push shift+space?
Private Sub FindBox_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case vbKeyEnter: SearchForIt
Case vbKeySpace:
If Shift = 1 Then
FindBox.Text = ""
'here absorb keypress instead of sending space to control
Exit Sub
End If
End Select
End Sub

You just need to set the value of KeyCode = 0. That will "swallow" the key and not have anything happen.
You also want to use the bit mask to look for the presence of the shift being pressed
Dim intShiftDown As Integer
intShiftDown = (Shift And acShiftMask) > 0
Case vbKeySpace:
If intShiftDown Then
FindBox.Text = ""
'here absorb keypress instead of sending space to control
KeyCode = 0
Exit Sub

Oh, I figured it out.
It was simple.
'here absorb keypress instead of sending space to control
KeyCode = 0

Related

TextBox.SelStart gives different value for mouse and keyboard

I have a TextBox on a Form. I take the cursor position using .SelStart property in that TextBox. It works as required when I click within TextBox. I then use that position to insert certain symbols on cursor position by pressing buttons on the same form which print their captions.
However, if I type some characters with the keyboard in TextBox, the Selstart returns 0. Even though I type several characters and cursor visibly is at the end of text, SelStart remains 0. Now, if I print buttons on other form the new characters get printed always at the start of TextBox which is not what I want. I want the captions to be printed always at cursor location even when I type with keyboard.
This behavior is very puzzling. Can someone help out on this?
Private LastPosition as Long 'declared in form module
Private sub t_LostFocus() 'to obtain last position in `TextBox`
LastPosition = Me!t.SelStart
End Sub
Private Sub Insert()
Dim Text As String
If LastPosition = 0 AND IsNull(Me!t.value) Then
Me!t.Value = " " + Me.ActiveControl.Caption
LastPosition = LastPosition + Len(Me.ActiveControl.Caption) + 1
ElseIf LastPosition >=0 AND Not IsNull(Me!t.Value) Then
Text = Me!t.Value
Me!t.Value = Left(Text, LastPosition) & " " & Me.ActiveControl.Caption & Mid (Text, LastPosition + 1)
LastPosition = LastPosition + Len(Me.ActiveControl.Caption)+1
Else
Me!t.Value = Me!t.Value + " " + Me.ActiveControl.Caption
End If
End Sub
Private Sub button1_Click()
Call Insert
End Sub
First a few facts about data entry on an Access Form. These need to be understood separately to properly explain the behavior described in the question, and especially if one is trying to alter the default behavior of the control.
The TextBox.SelStart, SelLength, and SelText are only available and valid when the control has focus. When a TextBox control receives focus again, the default is for all text to be selected, so that SelStart = 0 and SelLength = length of Text property. When using the mouse and clicking on the TextBox at a particular character position, the default behavior is bypassed and the cursor is placed at the mouse cursor, as expected.
TextBox controls have both a Text property and a Value property. The Text property represents the text string as it is displayed in the control. The displayed text can be different than the underlying value that the control represents, especially if Value is a non-text data type (e.g. an integer is stored as a number, but represented as individual text digits). The Value property returns a VBA variant which itself holds the underlying value of a particular data type.
For a bound control (i.e. the ControlSource property is populated), the Value data type will be the same as the bound source column.
For an unbound control (i.e. ControlSource is blank), the Value data type is dictated by the TextBox.Format property. If Format is blank, then the data type is text and will effectively match the Text property.
Text and Value are not always synced and this is especially true when the control has focus and is being edited. When the text is edited by the form user (i.e. not from code), Value is not updated until the control loses focus or Shift+Enter saves the form (except in cases when the Enter key behavior has been altered). Most events that will update the control will also involve clicking or otherwise moving focus outside the control, like saving the record, changing focus to another control, etc.
When the control’s Value is updated, the displayed text--accessible via the Text property--is interpreted and/or convert into the appropriate data type which is then saved to the Value property. (Sometimes the synchronization continues by reformatting the ‘Value’ back into a representation specified in the Format property. For instance, if Format = Long Date then: Text entered as “4-12-19” --> updated Value: #4/12/19 00:00# → updated Text: “Friday, April 12, 2019”.
One important last fact before I get to the point:
When TextBox.Value property is updated--even if it is also a String data type--the Text property is also refreshed and the cursor position and text selection is reset so that the entire text is selected. In other words, SelStart is set to 0 and SelLength is set to the length of Text, just like the behavior observed when the TextBox newly receives focus (as mentioned in the first point above).
Finally to the crux of all this detail:
When the keyboard is used to alter the text, this will eventually trigger an update, but usually not until the control loses focus. But when such an update occurs, it happens before the LostFocus event and the text selection is reset as described above, so that within the LostFocus event handler, SelStart == 0.
The issue is really not between keyboard and mouse, rather between the control text being altered or unaltered. If one only uses the arrow keys while in the textbox, then the cursor position and text selection are retained in the LostFocus event because a control update has not occurred. Contrariwise, if the mouse is used to alter the text (e.g. right-click Paste), this also triggers an update which will reset the selection. In fact, if one changes the text in any way and then uses the arrow keys or the mouse clicks, an update will still occur and reset the cursor position and text selection.
If the focus is moved outside the textbox and then back in using the mouse, an update may have occurred but the mouse will subsequently set the cursor position. I only mention this to be aware of stray clicks that might unknowing cause an update and still give the illusion that there is unique behavior to the mouse.
For kicks, press Shift+Enter to force an update but retain focus on the control, and observe that all of the text is automatically selected.
It is worth tracing the code by placing some "logging" statements in the various events, so that you can observe when they happen and the order.
Option Explicit
Option Compare Database
Dim LastSelStart As Integer
Dim LastSelLength As Integer
Dim UpdateSelStart As Integer
Dim UpdateSelLength As Integer
Private Sub button1_Click()
Insert
End Sub
Private Sub button2_Click()
Insert
End Sub
Private Sub Form_Load()
LastSelStart = -1
LastSelLength = 0
ResetUpdateSelValues
End Sub
Private Sub ResetUpdateSelValues()
UpdateSelStart = -1
UpdateSelLength = 0
End Sub
Private Sub t_AfterUpdate()
On Error Resume Next
UpdateSelStart = Me.t.SelStart
UpdateSelLength = Me.t.SelLength
If Err.Number <> 0 Then
UpdateSelStart = -1
End If
End Sub
Private Sub t_GotFocus()
On Error Resume Next
If LastSelStart >= 0 Then
Me.t.SelStart = LastSelStart
Me.t.SelLength = LastSelLength
End If
End Sub
Private Sub t_LostFocus()
LastSelStart = Me.t.SelStart
LastSelLength = Me.t.SelLength
If LastSelStart = 0 And UpdateSelStart > 0 Then
LastSelStart = UpdateSelStart
LastSelLength = UpdateSelLength
End If
ResetUpdateSelValues
End Sub
Private Sub Insert()
Dim caption As String
caption = Me.ActiveControl.caption
If IsNull(Me.t.Value) Then
Me.t.Value = caption
LastSelStart = Len(caption)
LastSelLength = 0
Else
Dim Text As String
Text = Me.t.Value
If LastSelStart = 0 Then
'* Don't add extra space at beginning
Text = caption & Mid(Text, LastSelLength + 1)
'Text = caption & Text
LastSelStart = Len(caption)
LastSelLength = 0
ElseIf LastSelStart > 0 Then
Text = Left(Text, LastSelStart) & " " & caption & Mid(Text, LastSelStart + LastSelLength + 1)
'Text = Left(Text, LastSelStart) & " " & caption & Mid(Text, LastSelStart + 0 + 1)
LastSelStart = LastSelStart + 1 + Len(caption)
LastSelLength = 0
Else
'If last cursor position is invalid, append characters
Text = Text & " " & caption
LastSelStart = Len(Text)
LastSelLength = 0
End If
t.Value = Text
End If
Me.t.SetFocus
End Sub

MS Access Multi-control KeyPress CTRL+A Handler

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.

Unable to disable all fields of a ms-access userform on load

I have lots of controls on my userform and I want to disable all of them on load except for "login button" and "Shift Date" textbox, so i am using the code below:
Private Sub Form_Load()
Call GetValues
Me.WlcmLabel.Caption = "Hi " + GetUserName + " ! "
Dim ctrl As Control
For Each ctrl In Me.Controls
ctrl.Enabled = False
Next
Me.ShiftDate.Enabled = True
Me.LoginBtn.Enabled = True
Set ctrl = Nothing
End Sub
but this gives me an error on load saying "object does not support this property or method."
all the controls will get enabled as soon as the user clicks on login button.
What would be the mistake in my code ?
Please ask if any other information required.
Thank You !
Try locking the control instead. I have used the locked property below, but I use the tag property of each control to identify it as lockable. So the controls you don't want to lock will not get anything in the tag property, and therefore will not lock. For your application, you could flip the logic since you only want leave the two controls unlocked.
For Each ctlCurr In Me.Controls
If ctlCurr.Tag = "Lockable" Then
ctlCurr.Locked = True
End If
Next
You can't disable label control that's why that error is coming. You have to check the type of control.
For Each ctrl In Me.Controls
With ctrl
a = TypeName(ctrl)
Select Case .ControlType
Case acLabel
Case acEmptyCell
Case Else
ctrl.Enabled = False
End Select
End With
Next ctrl
Apply disable only when it's not label.

Alternative to SendKeys "{Esc}" for unbound text box Undo

I have an unbound textbox control. The user enters some valid text and leaves the control. The user then returns to the control and enters some invalid text. I want to show the user a message then rollback the value of the control to its previous state and keep the focus in that control.
I've tried the following approaches, none of which gets me exactly what I am looking for:
OPTION A: SendKeys
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
Cancel = DataIsInvalid(Me.MyTextBox.Value)
If Cancel Then SendKeys "{Esc}"
End Sub
This does exactly what I want, but I really want to avoid using SendKeys. There are many problems that come with using SendKeys: Vista+ compatibility, keys being sent to a different application, etc.
OPTION B: Undo method of control
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
Cancel = DataIsInvalid(Me.MyTextBox.Value)
If Cancel Then Me.MyTextBox.Undo
End Sub
This is simply broken for unbound controls (at least as of MS Access 2002/XP). This method does not restore the value of MyTextBox to the valid input. However, it does allow the user to change the focus to a new control while leaving the invalid input in place in Me.MyTextBox! Unbelievable!!!
OPTION C: Undo method of form
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
Cancel = DataIsInvalid(Me.MyTextBox.Value)
If Cancel Then Me.Form.Undo
End Sub
The Undo here does absolutely nothing. But at least it doesn't break the BeforeUpdate Cancel=True code and allow the invalid data to stand.
OPTION D: Explicitly restore old value in BeforeUpdate event
Private mPrevMyTextBoxValue As Variant
Private Sub MyTextBox_AfterUpdate()
mPrevMyTextBoxValue = Me.MyTextBox.Value
End Sub
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
Cancel = DataIsInvalid(Me.MyTextBox.Value)
If Cancel Then Me.MyTextBox.Value = mPrevMyTextBoxValue
'If Cancel Then Me.MyTextBox.Text = mPrevMyTextBoxValue
End Sub
Attempts to assign the previous value to either the .Value or .Text property of the textbox result in the same error message:
The macro or function set to the BeforeUpdate or ValidationRule property for this field is preventing {Program Name} from saving the data in the field.
OPTION E: Explicitly restore old value in AfterUpdate event
Private mPrevMyTextBoxValue As Variant
Private Sub MyTextBox_AfterUpdate()
If DataIsInvalid(Me.MyTextBox.Value) Then
Me.MyTextBox.Value = mPrevMyTextBoxValue
Me.MyTextBox.SetFocus
Else
mPrevMyTextBoxValue = Me.MyTextBox.Value
End If
End Sub
This is really close to the desired behavior. Unfortunately, it's impossible to keep the focus on the control because the AfterUpdate event runs before the Tab/Enter keypress or mouse-click events are processed. So even if we try to force the focus to the proper control (via the .SetFocus statement above), the program will immediately shift the focus to the control the user selected.
It seems to me that the "right" way to do this is to use the .Undo method of the control. That does not work for unbound controls, though. This is an inexplicable shortcoming, especially given the fact that an Escape key-press performs this functionality for an unbound field.
Is there a better way to do this or should I just stick to using SendKeys?
After getting to page 3 on google I decided to just muck around and see what sort of ordering happens when trying your Option E. Below is the best workaround method I was able to use to get focus to "Stay" on the textbox in question.
Private mPrevMyTextBoxValue As Variant
Private Sub Text0_AfterUpdate()
If Me.Text0.Value = 0 Then
Me.Text0.Value = mPrevMyTextBoxValue
Me.Text12.SetFocus
Me.Text0.SetFocus
Else
mPrevMyTextBoxValue = Me.Text0.Value
End If
End Sub
0 simulates that it failed the validation. It seems like if you set focus to something else before setting it back to the textbox you are working with it will stay there. I don't have an answer as to why unfortunately.
Edit: I have decided to try to program my theory as to how this works.
Private blnGoToNextControl as Boolean
Private Function SetFocus(ctrl As control)
blnGoToNextControl = True
If ctrl.HasFocus = True Then
'Do nothing
Else
ctrl.HasFocus = True
blnGoToNextControl = False
End If
End Function
This is my idea of how the SetFocus function works. So then after the AfterUpdate event runs it would check to see if the flag to go to next control is and see that it is set to false and not go to the next control.
Rough coding obviously but hopefully this gets my theory across?
If it is unbound, why not use the AfterUpdate event:
Private Sub MyTextBox_AfterUpdate()
If DataIsInvalid(Me!MyTextBox.Value) Then
Me!MyTextBox.Value = Null
Me!MyTextBox.SetFocus
End If
End Sub
or modify your other solution:
Private Sub MyTextBox_AfterUpdate()
Static mPrevMyTextBoxValue As Variant
If IsEmpty(mPrevMyTextBoxValue) Then
mPrevMyTextBoxValue = Null
End If
If DataIsInvalid(Me!MyTextBox.Value) Then
Me!MyTextBox.Value = mPrevMyTextBoxValue
Me!MyTextBox.SetFocus
Else
mPrevMyTextBoxValue = Me!MyTextBox.Value
End If
End Sub
Try moving the focus off the textbox, then back:
Me!MyTextBox.Value = mPrevMyTextBoxValue
Me!SomeOtherControl.SetFocus
Me!MyTextBox.SetFocus
That someothercontrol can be a tiny nearly hidden textbox.

Do action if user starts to delete data from textbox

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.