Disable Controls on Access Forms on Load - vba

I am trying to create a form that allows me to control Access for people inputting data. The idea is to have all the controls disabled when the form loads and when they select different topics various fields would be selected accordingly. I know the long-hand way on how to do this field by field but I was hoping to do this as a batch but everything I try fails.
This is what I currently have
Dim control As control
Dim formName As String
Dim fieldName As String
Dim fieldParse As String
strFormName = "frm_Outfalls_Profile_Events"
For Each control In Forms(strFormName)
fieldName = control.Name
fieldParse = Left(fieldName, 5)
If fieldParse = "event" Then
Me.fieldName.Enabled = False
End If
Next

A simplified version of your code which suspends errors to avoid cases where the Enabled property does not exist.
On Error Resume Next
For Each ctl In Forms.frm_Outfalls_Profile_Events.Controls
ctl.Enabled = Not Left(ctl.Name, 5) = "event"
Next ctl
On Error GoTo 0

Related

How do I efficiently pair Toggle Buttons and Textboxes in Access Form?

I know the title is a bit confusing so let me make myself as clear as possible.
In an Access form (2010), I have a set of text fields meant to contain dates. Those fields are all optional, the user can fill in any number of dates.
To make it more user-friendly, I want to associate each field to a toggle button. Then I want 2 things to happen :
On CURRENT event :
If a text field has a value, then it is visible and the toggle button
associated with it is pressed.
If a text field is empty, then it is not visible and the toggle button isn't pressed.
On CLICK of a toggle button :
If the text field associated with it has a value, then this field gets cleared (and invisible) and the toggle button gets de-pressed ;
If the text field associated with it is empty, then the focus is set on it and the toggle button gets pressed (and stay that way if a value is entered in the text field, otherwise everything gets back the way it was, invisible and unpressed).
So far I've achieved the first step by setting two collections of controls based on some examples I found online.
So, when the form is loaded, I call this :
Private mcolGroupTxToggle As New Collection
Private mcolGroupBuToggle As New Collection
Private Sub InitializeCollections()
Dim ctl As Control
If mcolGroupTxToggle.Count = 0 Then
For Each ctl In Me.Controls
If ctl.Tag = "txtoggle" Then
mcolGroupTxToggle.Add ctl, ctl.Name
End If
Next ctl
Set ctl = Nothing
End If
If mcolGroupBuToggle.Count = 0 Then
For Each ctl In Me.Controls
If ctl.Tag = "butoggle" Then
mcolGroupBuToggle.Add ctl, ctl.Name
End If
Next ctl
Set ctl = Nothing
End If
End Sub
And on Form_Current event, I call that :
Private Sub OnLoadToggles(mcol As Collection, pcol As Collection)
Dim ctl As Control
Dim btn As Control
Dim strBtn As String
For Each ctl In mcol
'Every button has the same name than the textbox + "b"
strBtn = ctl.Name & "b"
For Each btn In pcol
If btn.Name = strBtn Then
If IsNull(ctl) Then
ctl.Visible = False
btn.Value = False
Else
ctl.Visible = True
btn.Value = True
End If
End If
Next btn
Next ctl
Set ctl = Nothing
End Sub
Everything works well so far, but I'm not sure that's the best way to do it and I figured I would need to repeat some lines in step 2.
Making the distinction between text boxes and buttons in the procedure seems weird, I feel like it should be done prior so that I don't have to do it in every procedure. I also feel like it would be better to loop through each pair of controls (text + button) instead of each control in both collections.
Basically, I'm wondering if it would be (1) better and (2) possible to have something as simple as this :
Private Sub OnLoadToggles(Collection of pairs)
for each [pair of txt and btn]
if isnull(txt) Then
txt.visible = false
btn.value = false
else
...
end if
...
My guess is that I would need to make a public sub where I set a collection of pairs of buttons and text fields based on their tags (there are other controls in my form that need to be left alone) and names, but I'm not sure how, I'm a beginner in VBA.
Any suggestions please ?
-- Edit step 2 --
Thanks to Andre's answer the second part was easier than I thought. I've updated my sample database. So on click events I call this :
Private Sub ClickToggles()
Dim ctl As Control
Dim btn As Control
Dim strBtn As String
Dim strCtl As String
strBtn = Me.ActiveControl.Name
strCtl = Left(strBtn, Len(strBtn) - 1)
Set ctl = Me(strCtl)
Set btn = Me(strBtn)
If IsNull(ctl) Then
btn.Value = True
ctl.Visible = True
ctl.Enabled = True
ctl.SetFocus
Else
ctl.Value = ""
btn.Value = False
ctl.Visible = False
End If
End Sub
It's not perfect but it works. Probably not a good idea to clear the data at this point because misclicks may happen. It would be better to loop through the textboxes right before saving the form and clear values of invisible and/or disabled controls from the collection. I might to that later.
I had to add the .enabled property next to the .visible one because on lostfocus events I was getting an error saying the control was still active so it couldn't make it not visible.
Right now I'm more concerned about the amount of click and lost focus events. I'd rather have some public functions and event handlers dealing with it but it's getting too complicated for me. I'll get back to it when I know more about... everything.
Suggestions are still welcome anyway =) !
Since your control pairs are "paired" by their name anyway, you don't need any fancy constructs, or even the second collection. Just address the matching control directly by its name.
Instead of using If/Else for setting boolean properties, it is usually easier to assign a variable to the property.
Private Sub OnLoadToggles(mcol As Collection)
Dim ctl As Control
Dim btn As Control
Dim strBtn As String
Dim bShowIt As Boolean
For Each ctl In mcol
'Every button has the same name than the textbox + "b"
strBtn = ctl.Name & "b"
' If you have the control name, you can get the control directly:
Set btn = Me(strBtn)
' Using a variable you don't need If/Else
bShowIt = Not IsNull(ctl.Value)
ctl.Visible = bShowIt
btn.Value = bShowIt
Next ctl
' Note: this is not necessary for local variables - they go automatically out of scope
Set ctl = Nothing
End Sub

check if textbox exists vba (using name)

I am using Ms-Access and I created a userform which has a number of Textboxes on it. The boxes are named: Box1, Box2, Box3 ...
I need to loop through all boxes, but I don't know which is the last one. To avoid looping through all userform controls I thought of trying the following:
For i =1 To 20
If Me.Controls("Box" & i).value = MyCondition Then
'do stuff
End If
Next i
This errors at Box6, which is the first box not found. Is there a way to capture this error and exit the loop when it happens.
I know I could use On Error but I 'd rather capture this specific instance with code instead.
Thanks,
George
A Controls collection is a simplified collection of controls (obviously) and share a same order as a placement order of controls.
First of all, even a creatable collection object lacks methods such as Exists or Contains , hence you need a function with error handling to checking/pulling widget from a collection.
Public Function ExistsWidget(ByVal Name As String) As Boolean
On Error Resume Next
ExistsWidget = Not Me.Controls(Name) Is Nothing
On Error GoTo 0
End Function
If you really doesnt like "ask forgiveness not permission" option you can pull entire ordered collection of your textboxes (and/or check existance by name in another loop with similar logic).
Public Function PullBoxes() As Collection
Dim Control As MSForms.Control
Set PullBoxes = New Collection
For Each Control In Me.Controls
If TypeOf Control Is MSForms.TextBox And _
Left(Control.Name, 3) = "Box" Then
Call PullBoxes.Add(Control)
End If
Next
End Function
Since names of widgets are unique - you can return a Dictionary from that function with (Control.Name, Control) pairs inside and able to check existance of widget by name properly w/o an error suppression.
There's a good guide to Dictionary if it's a new information for you.
Anyway, no matter what object you choose, if user (or code) is unable to create more of thoose textboxes - you can convert this Function above to a Static Property Get or just to a Property Get with Static collection inside, so you iterate over all controls only once (e.g. on UserForm_Initialize event)!
Public Property Get Boxes() As Collection
Static PreservedBoxes As Collection
'There's no loop, but call to PullBoxes to reduce duplicate code in answer
If PreservedBoxes Is Nothing Then _
Set PreservedBoxes = PullBoxes
Set Boxes = PreservedBoxes
End Property
After all, the last created TextBox with name Box* will be:
Public Function LastCreatedBox() As MSForms.TextBox
Dim Boxes As Collection
Set Boxes = PullBoxes
With Boxes
If .Count <> 0 Then _
Set LastCreatedBox = Boxes(.Count)
End With
End Function
I think that now things are clearer to you! Cheers!
Note: All code are definitely a bunch of methods/properties of your form, hence all stuff should be placed inside of form module.
Long story short - you cannot do what you want with VBA.
However, there is a good way to go around it - make a boolean formula, that checks whether the object exists, using the On Error. Thus, your code will not be spoiled with it.
Function ControlExists(ControlName As String, FormCheck As Form) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
ControlExists = (Err.Number = 0)
End Function
Taken from here:http://www.tek-tips.com/viewthread.cfm?qid=1029435
To see the whole code working, check it like this:
Option Explicit
Sub TestMe()
Dim i As Long
For i = 1 To 20
If fnBlnExists("Label" & i, UserForm1) Then
Debug.Print UserForm1.Controls(CStr("Label" & i)).Name & " EXISTS"
Else
Debug.Print "Does Not exist!"
End If
Next i
End Sub
Public Function fnBlnExists(ControlName As String, ByRef FormCheck As UserForm) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
fnBlnExists = (Err.Number = 0)
End Function
I would suggest testing the existence in another procedure per below: -
Private Sub Command1_Click()
Dim i As Long
i = 1
Do Until Not BoxExists(i)
If Me.Conrtols("Box" & i).Value = MyCondition Then
'Do stuff
End If
i = i + 1
Next
End Sub
Private Function BoxExists(ByVal LngID As Long) As Boolean
Dim Ctrl As Control
On Error GoTo ErrorHandle
Set Ctrl = Me.Controls("BoX" & LngID)
Set Ctrl = Nothing
BoxExists = True
Exit Function
ErrorHandle:
Err.Clear
End Function
In the above, BoxExists only returns true if the box does exists.
You have taken an incorrect approach here.
If you want to limit the loop, you can loop only in the section your controls reside e.g. Detail. You can use the ControlType property to limit controls to TextBox.
Dim ctl As Control
For Each ctl In Me.Detail.Controls
If ctl.ControlType = acTextBox Then
If ctl.Value = MyCondition Then
'do stuff
End If
End If
Next ctl
I believe the loop will be faster than checking if the control name exists through a helper function and an On Error Resume Next.
But this only a personal opinion.

Excel Checkbox DoubleClick event

I have a spreadsheet, basically a to-do list that has checkpoints marked by checkboxes. Rules for the checkboxes are:
1) Only the first checkbox (named checkbox_1) can be checked first.
2) Checkboxes can only be checked sequentially (i.e. checkbox_1, checkbox_2, checkbox_3 etc.)
Initially all checkboxes except for checkbox_1 are disabled. When an enabled checkbox is clicked, the next checkbox becomes enabled and disables the previous one.
The trouble I am having now is that double-clicking bypasses the enabled status of the checkbox and will go ahead and check the checkbox via the event handler.
How can I prevent this? Do I need to write something in the _BeforeDoubleClick event handler?
My event handler for the checkbox_click event is here:
Public Sub CheckBoxClick(Optional checkboxName As String = "")
'This calls the checkbox function to create the timestamp and enable the next visible checkbox
Dim assocTime As Range
Dim callerName As String
Dim aCaller As Variant
Dim check_num As String
Dim aCheckboxes As Variant
Set aCheckboxes = common.GetSheet(1).CheckBoxes
If checkboxName <> "" Then
callerName = checkboxName
Else
callerName = Application.Caller
End If
aCaller = Split(callerName, "_")
check_num = aCaller(1)
Set assocTime = common.GetRange("time_" & check_num)
If assocTime.value = "" Then
Call obj.ClickCheckBox(callerName, Constants.mypw)
End If
If check_num = aCheckboxes.count Then
If MainSheetFunctions.HasRedCells Then
common.MessageBox ("This batch record has cells with errors. Please address all red cells before continuing.")
Exit Sub
End If
End If
End Sub
The obj.ClickCheckBox sub will write the current time in an adjacent cell, and enable the next checkbox.
Thank you for your time,

Access 2010 - VBA - Editing underlying table data using text boxes on a form

I'm using too many boolean indicators and I'm sure its very inefficient/stupid...
Currently in the Access database I have numerous forms which are used to edit underlying records. Text boxes on these forms are not bound to the underlying table. I do not wish to bind the form or any of its controls directly to the underlying tables, if the data is editable by the user (less human error from users). Instead I've a Boolean for every control which contains editable information.
Users enter 'edit mode', change information (Boolean now equals true), click 'save changes', review the changes and accept and then the relevant queries are run to reflect these changes. I like this order of events however, I'm creating increasingly complex forms with 40 or so editable controls and hence 40 Boolean variables.
Anyone think of an nicer alternative? Is there a property of the controls (mainly text boxes) I can use?
CODE:
Private Sub CommentsText_AfterUpdate()
If Nz(Me.CommentsText) = "" Then
CommentsEdit = False
Else
CommentsEdit = True
End If
End Sub
'Within the 'save changes' method
If CommentsEdit Then
CommentsEdit = False
sql = "Update [General-CN] Set [Comments] = '" & Left(Me.CommentsText, 250) & "' Where [ID ( G )] = " & Me.[GeneralPK] & ";"
DoCmd.RunSQL (sql)
End If
normally data validation during data entry is one the things that normally, in my experience, before or later put the developer on his knees.
From your question, I can feel that the core is to preserve database integrity. Unfortunately there is no way to ABSOLUTELY preserve your database. If you give access to it to users you can only use some tips:
1 - If possible use combo-box with defined entries instead of using textboxes in which user is free to digit anything (e.g. think an expert system that collect data about the same problem written in many different ways!!!)
2 - Check data integrity and coherence (e.g. type) before writing it to database
3 - When the data is just a boolean (flag) you can use switches, radio button or checkboxes.
These tips help in developing a user interface more friendly and faster from the point of view of data entry.
After this I can give you another way to validate your data.
If you want to show data to user before saving you can create the mask in which you enter your data with unbounded textboxes.
Then, when he clicks the Save Button, you can show a 2nd form, opened in append mode, in which you show data with textboxes bounded to your db.
If he accepts the data, you can save otherwise you can cancel the data entry preserving your database. I post you here some lines of code with an example taken from an application of mine. It's the part in which I manage contacts
The form allows to input contact data
'------------------------------------------------------
' Temp variables to store entries before saving /cancelling
'------------------------------------------------------
Dim bolValidationErr As Boolean
Dim m_vntLastName As Variant
Dim m_vntFirstName As Variant
Dim m_vntFC As Variant
Dim m_vntVATNum As Variant
Dim m_vntAddress As Variant
Dim m_vntCity As Variant
Dim m_vntZIP As Variant
Dim m_vntCountry As Variant
Dim m_vntPhone As Variant
Dim m_vntFAX As Variant
Dim m_vntEMail As Variant
Dim m_vntNote As Variant
Dim m_vntContactType As Variant
'------------------------------------------------------
' Suppress error "Impossible to save the record...
'------------------------------------------------------
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 2169 Then
Response = True
End If
End Sub
'------------------------------------------------------
' W/o customer last name, cancel saving
'------------------------------------------------------
Private Sub Form_Beforeupdate(Cancel As Integer)
On Error GoTo Err_Form_BeforeUpdate
Dim strType As String
Dim bolNewContact As Boolean
Dim intErrFC As Integer
Dim intErrVATNum As Integer
Dim intErrFullName As Integer
Dim strErrMsg As String
Dim intAnswer As Integer
bolValidationErr = False
'------------------------------------------------------
' If LastName is missing, cancel data saving
' Cancel = True cause the raise of the error
' "You can't save record at this time..."
' SetWarnings doesn't work. It's needed to intercept with Form_Error event
'------------------------------------------------------
If HasNoValue(Me.txtLastName) Then
strErrMsg = "Put here your error msg"
intAnswer = MsgBox(strErrMsg, vbOKOnly + vbExclamation, "Entry Error")
Cancel = True ' Cancel db update
Call BackupCurrentData ' Store data input until now
bolValidationErr = True ' Unvalid data
Exit Sub
End If
Exit_Form_BeforeUpdate:
Exit Sub
Err_Form_BeforeUpdate:
GoTo Exit_Form_BeforeUpdate
End Sub
'------------------------------------------------------
' Store the content of textboxes for restoring them in case
' of cancelling
'------------------------------------------------------
' If the record is new, if the BeforeUpdate event is cancelled,
' NULL values are restored (in fact there were no data!!!)
'------------------------------------------------------
Private Sub BackupCurrentData()
m_vntLastName = Me.txtLastName
m_vntFirstName = Me.txtFirstName
m_vntFC = Me.txtFC
m_vntVATNum = Me.txtVATNum
m_vntAddress = Me.txtAddress
m_vntCity = Me.txtCity
m_vntZIP = Me.txtZIP
m_vntCountry = Me.txtCountry
m_vntPhone = Me.txtTelNum
m_vntFAX = Me.txtFax
m_vntEMail = Me.txtEmail
m_vntNote = Me.txtNotes
m_vntContactType = Me.cmbContactType
End Sub
'------------------------------------------------------
' Restore contents of textboxes before cancelling operation
'------------------------------------------------------
Private Sub RestoreCurrentData()
Me.txtLastName = m_vntLastName
Me.txtFirstName = m_vntFirstName
Me.txtFC = m_vntFC
Me.txtVATNum = m_vntVATNum
Me.txtAddress = m_vntAddress
Me.txtCity = m_vntCity
Me.txtZIP = m_vntZIP
Me.txtCountry = m_vntCountry
Me.txtTelNum = m_vntPhone
Me.txtFax = m_vntFAX
Me.txtEmail = m_vntEMail
Me.txtNotes = m_vntNote
Me.cmbContactType = m_vntContactType
End Sub
I think that this code can be adapted to your needs.
The variable names are enough self-describing
Anyway feel free to contact me if you need further help.
Bye,
Wiz:-)

Forcing the unloading forms from memory

I am writing a solution in Excel that uses a number of linked data entry forms. To move between he sequence of forms, the user can click a "Previous" or "Next button. The current form is unloaded and the new one loaded and opened.
Sub NextForm(curForm As MSForms.UserForm, strFormName As String)
Dim intCurPos As Integer
Dim strNewForm As String
Dim newForm As Object
intCurPos = WorksheetFunction.Match(strFormName, Range("SYS.formlist"), 0)
If intCurPos = WorksheetFunction.CountA(Range("SYS.formlist")) Then
Debug.Print "No"
Else
Unload curForm
strNewForm = WorksheetFunction.Index(Range("SYS.formlist"), intCurPos + 1)
Set newForm = VBA.UserForms.Add(strNewForm)
newForm.Show
End Sub
The code as is allows new forms to be added into the sequence at any time through the editing of the range "SYS.formlist".
One problem I have noticed is that even after the current form is unloaded, it still remains in the VBA.Userforms collection. I would presume this is because this code has been called from that userform.
Is there a way to force the removal of that form from the VBA.Userforms collection? What is occuring is that if the user moves forward and then back, two copies of the form appear in memory and and excel throws exceptions about two modal forms being open.
Cheers,
Nick
The answer was (sadly) quite simple and inspired by bugtussle's answer.
The subroutine was passing the curForm variable as an MSForms.Userform object, but the form is held in memory as its own object type. (As an example, you can access a form through Set form = new formName)
So by changing the curForm paramater type to Variant, it will pass the actual object through rather than a copy of the object. Unload was only unloading the copy, not the actual object.
Thanks bugtussle!
So, corrected code is:
Sub NextForm(curForm As Variant, strFormName As String)
Dim intCurPos As Integer
Dim strNewForm As String
Dim newForm As Object
intCurPos = WorksheetFunction.Match(strFormName, Range("SYS.formlist"), 0)
If intCurPos = WorksheetFunction.CountA(Range("SYS.formlist")) Then
Debug.Print "No"
Else
Unload curForm
strNewForm = WorksheetFunction.Index(Range("SYS.formlist"), intCurPos + 1)
Set newForm = VBA.UserForms.Add(strNewForm)
newForm.Show
End Sub
I'm thinking that unloading from the collection object instead of the variable will really get rid of it. Try something like this:
For i = VBA.UserForms.Count - 1 To 0 Step -1
if VBA.UserForms(i).Name = curForm.name
Unload VBA.UserForms(i)
end if
Next i