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 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.
I have Find Forms that are basically a large list box with some filter, sorting and action buttons and some text boxes to implement the filter.
I have been using these forms for quite a while without problem. The SELECT button is disabled unless an item from the list box is selected. I am trying to unselect any listbox entry when the filtering or sorting is changed.
To do this I set the focus to the listbox (no multi selection option) to -1. There are a number of equivalent actions and I have tried most of them. Once ListIndex is set to -1 I get the '2115' runtime error if I execute a requery action on the listbox or a refresh action on the containing form. In addition, I cannot set the focus to any of the text boxes or buttons on the form getting a variety of runtime errors that say that the field must be saved.
Anybody have an ideas about how this is supposed to function?
In the code snippet the actual code getting the error is the .requery line near the bottom after the Unselect listbox comment
Here is a code snippet of one of the ways I have had this occur:
With Me.lbxFoundItems
If strCurrentSearchText = vbNullString Then 'Nothing to search for
.Visible = False
Call ButtonSettings
Exit Sub
End If
strQry = strSql & strWhere & strOrderBy
If Nz(.RowSource, vbNullString) = vbNullString Then 'First time through
Set qry = Nothing
On Error Resume Next
Set qry = CurrentDb.CreateQueryDef(strQname, strQry)
If qry Is Nothing Then
Set qry = CurrentDb.QueryDefs(strQname)
qry.sql = strQry
End If
colGarbage.Add CurrentDb.QueryDefs(qry.Name), qry.Name
.RowSource = qry.Name
Else
CurrentDb.QueryDefs(qry.Name).sql = strQry
End If
' Unselect the listbox entry
.SetFocus
.Selected(.ListIndex + 1) = False
.Requery
Me.Refresh
Me.tbxListCount = .ListCount - 1
.Visible = True
End With
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
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