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

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:-)

Related

populating multiple fields in subform using combobox and AfterUpdate Event Prodcedure

I am creating a set of Access database form for entering vegetation data into a linked SQL Server data base. For one protocol, I have created a form 'frmLPI' for entering data from a vegetation monitoring method called Line-Point-Intercept. It is a form with a subform within it called 'frmLPIDetail' where individual counts of plant species get recorded. The main form has three unbound controls: [TransectOID], [DataRec], and [DataObs]. TransectOID is a unique id for each time we ran protocol. For each TransectOID, there are 30 locations where we sampled vegetation these have a hidden unique id in the subform called LPI_OID. The subform is linked to the main form by TransectOID. I want my users to be able to click the unbound [DataRec] and [DataObs] comboboxes in the main form, and have the corresponding fields in the subform autopopulate for all 30 records. I have figure out how to accomplish this for record in the subform but can't figure out how to do it for 30 records for each value of TransectOID in the Main form. Below is a screenshot of my form to help you visualize what I am after:
And here is the code I have come up with to get one record to autopopulate
Private Sub Form_Load()
Me.TransectOID = Me.OpenArgs
End Sub
Private Sub Form_Deactivate()
DoCmd.RunCommand acCmdSaveRecord
End Sub
Private Sub DataObs_AfterUpdate()
Me!frmLPIDetail.Form!Data_observer = Me!DataObs
Me.Dirty = False
End Sub
Private Sub DataRec_AfterUpdate()
Me!frmLPIDetail.Form!Data_recorder = Me!DataRec
Me.Dirty = False
End Sub
Any suggestions would be much appreciated
Since inserting multiple records at a time is desirable your question has been asked before but I couldn't find an answer that was particularly helpful so I will provide a more general answer than you asked for.
Access doesn't provide default forms for inserting multiple records. You have to code that yourself but the process is always pretty much the same.
figure out a normalized table structure for your data
figure what data you need to collect from the user for the multiple insert
add a button to the form and put the vba for the multiple insert in the click event
so here is 1 normalized table structure that might fit your data:
Since I don't know where TransectionOID is coming from we let Access provide TransectionID as the primary key and assume TransectionOID is entered on another form. All the other information of interest is in the TransectionDetails table and there is no need to write a query to gather all the variables we will need into our forms record source to finish step 2. To get a jumpstart I selected the TransactionDetails table and used the create form wizard to make a tabular style form.
To finish step 2 we put controls in the header to collect the information from the user we will need and the start editing the form for user friendliness. For instance I delete the checkbox for TransectionDetailID in the details section and replace every other control with comboboxes. I normally replace the circled record selectors with comboboxes as well but here that may be confusing so I leave the record selectors to provide some search functionality. The final form looks like:
Finally, for step 3 we add the vba for the click event
Private Sub cmdInsert_Click()
Dim db As Database
Dim rs As Recordset 'using recordset because lower error rate than using sql strings
Set db = CurrentDb
Set rs = db.OpenRecordset("TransectionDetails")
Dim L As Integer
Dim S As Integer
If Not Me.lstLocations.ListCount = 0 Then 'if no locations are selected no records can be inserted
For L = 0 To Me.lstLocations.ListCount 'simple multiselect listbox version matters for the vba code
If Me.lstLocations.Selected(L) = True Then
For S = 0 To Me.lstSpecies.ListCount
If Me.lstSpecies.Selected(S) = True Then
rs.AddNew
rs!TransectionID = Me.cmbTransectionID
rs!Data_observer = Me.cmbData_observer
rs!Data_recorder = Me.cmbData_recorder
rs!TransectLocation = Me.lstLocations.Column(0, L) 'column gives you access to values in the listbox
rs!SpeciesID = Me.lstSpecies.Column(0, S)
If Not IsNull(Me.chkDead) Then 'chkDead is a triple value checkbox, this both avoids setting Dead to null and shows how to handle when the user doesn't set all controls
rs!Dead = Me.chkDead
End If 'chkdead
rs.Update
End If 'lstspecies selected
Next S
End If
Next L
End If
Me.Detail.Visible = True 'quick and dirty bit of style (detail starts invisible)
Me.Filter = "TransectionID = " & Me.cmbTransectionID 'more quick and dirty style filter to focus on inserted records
Me.FilterOn = True
'clean up
rs.Close
Set rs = Nothing
Set db = Nothing
End Sub
Private Sub cmdSelectAllLocations_Click()
Dim i As Integer
For i = 0 To Me.lstLocations.ListCount
Me.lstLocations.Selected(i) = True
Next
End Sub
Private Sub cmdSelectAllSpecies_Click()
Dim i As Integer
For i = 0 To Me.lstSpecies.ListCount
Me.lstSpecies.Selected(i) = True
Next
End Sub
Private Sub cmdSelectNoLocations_Click()
Dim i As Integer
For i = 0 To Me.lstLocations.ListCount
Me.lstLocations.Selected(i) = False
Next
End Sub
Private Sub cmdSelectNoSpecies_Click()
Dim i As Integer
For i = 0 To Me.lstSpecies.ListCount
Me.lstSpecies.Selected(i) = False
Next
End Sub
While Mazoula's answer is far more elegant, I discovered a quick and dirty way to accomplish what I was after using a While loop. Below is my code:
Private Sub Form_Load()
Me.TransectOID = Me.OpenArgs
End Sub
Private Sub Form_Deactivate()
DoCmd.RunCommand acCmdSaveRecord
End Sub
Private Sub DataObs_AfterUpdate()
Dim rs As DAO.Recordset
Set rs = Me!frmLPIDetail.Form.RecordsetClone
rs.MoveLast
rs.MoveFirst
While Not rs.EOF
rs.Edit
rs!Data_observer.Value = Me.DataObs.Value
rs.Update
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
Me.Dirty = False
End Sub
Private Sub DataRec_AfterUpdate()
Dim rs As DAO.Recordset
Set rs = Me!frmLPIDetail.Form.RecordsetClone
rs.MoveLast
rs.MoveFirst
While Not rs.EOF
rs.Edit
rs!Data_recorder.Value = Me.DataRec.Value
rs.Update
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
Me.Dirty = False
End Sub

Disable Controls on Access Forms on Load

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

Checking and Unchecking Checkboxes in Access

I have a form in MS Access with multiple checkboxes which I want to use to fill up one textbox. If one of the checkboxes gets unchecked, I want its value to be deleted from the textbox without deleting other values. I'm new at using Access and coding in VBA (been reading ebooks for the past 3 weeks) and although I've tried to do research online it's been difficult for me to find the right code.
This is what I have so far:
First code found
Private Sub cb_click()
If Me.cb1 = True Then
Me.txtComentarios.Value = "INACTIVO;"
Else
Me.txtComentarios.Value = Null
End If
End Sub
Second code found
Private Sub cb2_Click()
If Me.cb2 = -1 Then
Me.[txtComentarios] = [txtComentarios] & "DISCREPANCIA"
Else
Me.[txtComentarios] = ""
End If
Exit Sub
End Sub
Also I would like for the checkboxes to fill the textbox in the same order the chechboxes are displayed.
Ex.
cb1; cb2; cb3
If, cb2 gets unchecked and its value gets deleted, I should have "cb1; cb3" but if I re-check cb2 I should get "cb1; cb2; cb3" again.
I just hope someone could guide me in. Thank you in advance.
Luz
You don't need events for each checkbox. Just create one procedure, which creates full text depending on checkboxes state and puts this text to the textbox. To call this function after each click on checkbox set After Update property of all checkboxes to =MyFunctionToUpdateTextbox instead of [Event Procedure]
Private Function MyFunctionToUpdateTextbox()
Dim strText As String
If Me.cb1 = True Then
strText = strText & "INACTIVO;"
End If
If Me.cb2 = True Then
strText = strText & "DISCREPANCIA;"
End If
If Me.cb3 = True Then
strText = strText & "Text for cb3"
End If
Me.txtComentarios = strText
End Function

Run subprocedure under button -

I have this sub/macro that works if I run it as BeforeRightClick. However, I would like to change it so I can actually use my rightclick and put the macro on a button instead.
So I have tried to change the name from BeforeRightClick.
I have tried with both a normal form button and an ActiveX.
All this + some more code is posted under Sheet1 and not modules
Dim tabA As Variant, tabM As Variant
Dim adrA As String, adrM As String
' Set columns (MDS tabel) where data should be copied to (APFtabel)
'Post to
'P1-6 divisions ' Name adress, etc
Const APFtabel = "P1;P2;P3;P4;P5;P6;E9;E10;E13;E14;E23;N9;N10;N11;N12;N20"
'Load data from
Const MDStabel = "N;O;P;Q;R;S;H;Y;Z;AB;W;AF;T;D;AA;V;"
Dim APF As Workbook
' APFilNavn is the name of the AP form
Const APFilNavn = "APForm_macro_pdf - test.xlsm"
' Const APFsti As String = ActiveWorkbook.Path
Const APFarkNavn = "Disposition of new supplier"
' APsti is the path of the folder
Dim sysXls As Object, APFSti As String
Dim ræk As Integer
Private Sub CommandButton1_Click()
APFormRun
End Sub
' Here I changed it from BeforeRightClick
Private Sub APFormRun(ByVal Target As Range, Cancel As Boolean)
Dim cc As Object
If Target.Column = 8 Then
APFSti = ActiveWorkbook.Path & "\"
If Target.Address <> "" Then
For Each cc In Selection.Rows
Cancel = True
ræk = cc.Row
Set sysXls = ActiveWorkbook
åbnAPF
overførData
opretFiler
APF.Save
APF.Close
Set APF = Nothing
Set sysXls = Nothing
Next cc
End If
End If
End Sub
Private Sub overførData()
Dim ix As Integer
tabA = Split(APFtabel, ";")
tabM = Split(MDStabel, ";")
Application.ScreenUpdating = False
For ix = 0 To UBound(tabM) - 1
If Trim(tabM(ix)) <> "" Then
adrM = tabM(ix) & ræk
If tabA(ix) <> "" Then
adrA = tabA(ix)
End If
With APF.ActiveSheet
.Range(adrA).Value = sysXls.Sheets(1).Range(adrM).Value
End With
End If
Next ix
End Sub
Private Sub opretFiler()
' Here I run some other macro exporting the files to Excel and PDF
btnExcel
btnExportPDF
End Sub
if you put this code in Sheet1, then to access it from a button you need to define its name (in the button) as Sheet1.APFormRun (and I think you need to make it Public).
If you move the sub and everything it calls to a Module (after doing an Insert->Module), then you do not need the Excel Object Name prefix.
A very detailed write-up about scoping is at the link below. Scroll down to the "Placement of Macros/ Sub procedures in appropriate Modules" section: http://www.globaliconnect.com/excel/index.php?option=com_content&view=article&id=162:excel-vba-calling-sub-procedures-a-functions-placement-in-modules&catid=79&Itemid=475
In your code above, I had to comment out all the subs you didn't include just to get it to compile for debugging.
To make a sub accessible to the Macros button or to "Assign Macro..." you have to make it Public
Also to make a sub accessible, it cannot have any passed parameters.
So you will have to remove the passed parameters from the Public Sub APFormRun() definition
Therefore you will have to re-write the initial portion of APFormRun ... currently your APFormRun relies upon getting a passed parameter (Target) of the selected cell that you right-clicked upon. When you press a button, there is no cell that you are right-clicking upon. It is not a cell-identifying Excel event. You will have to obtain the selected cell via the Selection excel object. There are a lot of StackOverflow answers on how to do that.

have record loaded when access gets focus

I have access 2007 as my DB engine for a PLC system. I use RSLinx as my DDE. Access is always loaded, Batch_Setup form as Popup and access minimized.
What I am trying to do is have the form Batch_Setup display the current batch information everytime access is maximized. I do a DDE request to get the current Formula_Number but I can not figure out how to make the form goto the Formula_Number.
Here is the VBA code:
Private Sub Form_Activate()
Dim BATCH_NAME As String, STRX As String, LOOPX As Integer
Dim TEMP_X As Integer
abddeinitiate = DDEInitiate("RSLinx", "GCT")
BATCH_NAME = DDERequest(abddeinitiate, "BATCH_NAME")
STRX = 1
LOOPX = 1
While Not (STRX = "-")
STRX = Mid(BATCH_NAME, LOOPX, 1)
LOOPX = LOOPX + 1
Wend
LOOPX = LOOPX - 2
STRX = Left(BATCH_NAME, LOOPX)
TEMP_X = MsgBox(STRX, vbOKCancel)
With Me.Recordset
.FindFirst "FORMULA_NUMBER = " & CInt(STRX)
End With
End Sub
I have tried Activate, GotFocus, Load, .... but I can not get it to goto the current batch setup. I can Find it thru the Find but I want it to goto it when Access is restored with the Batch_Setup form always loaded as Popup.