I have a form with one ComboBox (YearToBeBuilt) and two textBox fields (Cost and YearofExpenditureCost). All controls are linked to a main table and the table is updated once the selections/entries have been made on the form.
I have written a procedure in VB called ReCalcIt()which performs the following procedure when called:
Private Sub ReCalcIt()
If Me.YearToBeBuilt = "" Then
Me.YearofExpenditureCost = Me.Cost
Else
Me.YearofExpenditureCost = Me.Cost * (1 + 0.031) ^ (Me.YearToBeBuilt - 2010)
End If
End Sub
When I wrote this I thought that this would do the following:
If the ComboBox [YearToBeBuilt] is blank (e.g.-no selection made) then
the textbox [YearOfExpenditureCost] will return the value of the TextBox [Cost]. Else, the calculation for YearofExpenditureCost is performed.
But this is not working the way it should
What am I doing wrong? I am a VBA n00b so perhaps my syntax is incorrect?
Try it with
If Len(Me.YearToBeBuilt & vbNullString) = 0
So the code will look like this:
Private Sub ReCalcIt()
If Len(Me.YearToBeBuilt & vbNullString) = 0 Then
Me.YearofExpenditureCost = Me.Cost
Else
Me.YearofExpenditureCost = Me.Cost * (1 + 0.031) ^ (Me.YearToBeBuilt - 2010)
End If
End Sub
The usual way is to say:
If IsNull(MyControl) Then
See also: Access VBA: If Form Value <> NULL then run query else, end if. - not doing anything
It seems to me that this might be dependent on the format of the combo box? If it's a number, then
If Len(Me.YearToBeBuilt & vbNullString) = 0
doesn't work. Because the Me.YearToBeBuilt has a value of 0, and so the above returns 1.
I'm wondering if there is a more robust method? Maybe
Me.YearToBeBuilt.ListIndex = -1
Related
The problem is that while the front-end allows 4 or more conditions, when I tried to set conditions using VBA, I ran into an error when setting the 4th condition. In other words, if I only tried to set 3 conditions in the code, then the code worked fine.
I am using MS Access 2010. I need to set conditional formatting for two textboxes on a continuous form. I know that older versions of MS Access allowed only 3 conditions on a textbox, but I know that I can get more conditions in Access 2010. My current application has 4 conditions using the user interface. In my research on this question, one person said that later versions of MS Access allow up to 50 conditions. I could not confirm this either way, even when I reviewed the Access 2010 specifications page. But I know I can at least get more than 3 conditions.
Here is the test code that works for up to 3 records:
Function fApplyConditionFormatNow()
Dim objFormatConds As FormatCondition
Dim i As Integer 'index number for specific format conditions
Dim stSQL As String 'query to get list of categories
Dim rs As DAO.Recordset
i = 0
'clear out just in case FormatConditions accidentially got saved
'with the form at some point.
Me.ID.FormatConditions.Delete
'get a recordset containing the formatting information
'(ie, get RGB values for each category type)
stSQL = "SELECT * FROM tblTestConditionalFormatting;"
fRunSQL stSQL, rs 'fRunSQL is custom code that gets runs stSQL and returns the recordset
'loop through recordset to get conditional formatting values
Do Until rs.EOF
'create a condition on textbox named "ID". The condition will be for
'the Category/Type (TypeNm) that's up now in the recordset.
Set objFormatConds = Me.ID.FormatConditions.Add(acExpression, , "[TypeNm] = '" & rs!TypeNm & "'")
'add formatting for the condition just created.
With Me.ID.FormatConditions(i)
.BackColor = RGB(rs!RGB1, rs!RGB2, rs!RGB3)
End With
i = i + 1
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
End Function
When 4 records are included in the category table : ie, tblTestConditionalFormatting, I get the following error:
"Runtime error 7966: The format condition you specified is greater than the number of format conditions."
So, there appears to be a bug in that the front-end can handle more than 3 conditions but the VBA object can only handle 3 conditions? OR maybe I'm doing something wrong. Has anyone else come across this? Do you have an idea for a work around?
Thank you!!
Instead of With Me.ID.FormatConditions(i), use the FormatCondition object that you just created:
Set objFormatConds = Me.ID.FormatConditions.Add(acExpression, , "[TypeNm] = '" & rs!TypeNm & "'")
'add formatting for the condition just created.
With objFormatConds
.BackColor = RGB(rs!RGB1, rs!RGB2, rs!RGB3)
End With
You don't need i anymore.
I have similar code that goes (simplified) like this:
For i = 1 To 6
lColor = DLookup("Color", "someTable", "p_Color_ID = " & i)
Set objFrc = txtFld.FormatConditions.Add(acExpression, , "[ColorGroup] = '" & i & "'")
objFrc.BackColor = lColor
Next i
Edit
Apparently you can edit the FormatConditions >= 3 if you don't touch 0..2:
https://access-programmers.co.uk/forums/showthread.php?t=271679
Maybe I have something like this... :/
I'm a newcomer to Access trying to cobble things together from helpful information I've found here.
I have a form that needs to populate the fields based on a combo box selection in the form header. The form is based on an underlying query with the following criteria for field "StudID" [Forms]![frmStudConsentUpdate]![cmbStud] where cmbStud is my combo box. The combo box pulls in StudID, StudFN, StudLN with StudID as the bound columnn. The after update event requeries the form (Me.Requery). This works beautifully, but only if I first open the form in design view, open the Record Source, and save it. I don't make any changes at all, but once I've done this the form works. Otherwise, nothing happens when I select a student in the combo box. Any thoughts on what I need to do to make this work without having to re-save the underlying query?
This is old bug in MS Access, I have no idea why they still didn't fix it:
If underlying form's query has in criteria form's control and the form was filtered once (at start or manually/using VBA), it doesn't accept new values from form's control and uses old value.
Workaround: create public function, which returns control's value and use it in criteria instead of [Forms]![frmStudConsentUpdate]![cmbStud]. You will need to create function for each control or use this function:
Public Function GetControlValue(strFormName As String, strControlName As String, Optional strSubFormControlName As Variant, Optional varDefault As Variant) As Variant
' Returns : Variant, value of control in form/subform
' Comments:
' Params :
' strFormName
' strControlName
' strSubFormControlName
' varDefault - value returned if control is not accessible
'----------------------------------------------------------------------------
On Error GoTo ErrorHandler
If IsMissing(strSubFormControlName) Or Nz(strSubFormControlName, "") = "" Then
GetControlValue = Forms(strFormName).Controls(strControlName).Value
Else
GetControlValue = Forms(strFormName).Controls(strSubFormControlName).Form.Controls(strControlName).Value
End If
ExitHere:
On Error Resume Next
Exit Function
ErrorHandler:
If Not IsMissing(varDefault) Then
GetControlValue = varDefault
End If
Resume ExitHere
End Function
In criteria use function call GetControlValue("frmStudConsentUpdate","cmbStud") instead of [Forms]![frmStudConsentUpdate]![cmbStud]
In your afterupdate for your cmbStud combobox, create code that refreshes the recordsource to
me.recordsource = "SELECT * FROM {yourQueryName} WHERE StudID = '" & me!cmbStud & "'"
I been working on the following code to when the user click on the button to save and go new record that Access locates the highest client id used by set location and then adds 1 to it. Prior to saving the record and moving on to new record. While work through other errors, but I can not get past error object required on this line. "Me.ClientID = IIf(DMax("[ClientID]", "tClientinfo", "[CorpsName]=" & "'defaultcorps'") Is Null, 0, DMax("[ClientID]", "tClientinfo", "[CorpsName]=" & "'defaultcorps'")) + 1"
The more i look at the similar questions more confused I get as to what is wrong with the code. Thank you in advance for any suggestions David
Private Sub Save_Record_Click()
'declare variables for default values
Dim defaultinterviewr As String
Dim defaultcorps As String
'Variables get their values
defaultinterviewr = Me.Interviewer.Value
defaultcorps = Me.Corps.Value
'Check to see if ClientID field is Blank.
If IsNull(Me.ClientID) Then
'Check that Corps field is filled in
If IsNull(Me.Corps) Then
MsgBox "Corps must be entered before saving record.", vbOKOnly
Me.Corps.SetFocus
'set client id base on corps by finding the highest id and adding 1 to that number
Else
Me.ClientID = IIf(DMax("[ClientID]", "tClientinfo", "[CorpsName]=" & "'defaultcorps'") Is Null, 0, DMax("[ClientID]", "tClientinfo", "[CorpsName]=" & "'defaultcorps'")) + 1
End If
End If
MsgBox "Done", vbOKOnly
'save record
'DoCmd.RunCommand acCmdSaveRecord
'Me.stateidnum1 = ""
'open new record
'DoCmd.GoToRecord , , acNewRec
'set field default value
'Me.Interviewer.Value = defaultinterviewr
'Me.Corps.Value = defaultcorps
'Me.Child_Subform.Form.AllowAdditions = True
End Sub
I think you need to start off by figuring out if your DMAX() statement is correctly producing results. The next thing I see and which is probably your main culprit is the fact that you are using the Expression IIf() inside VBA. The IIf() expression you are using will work inside a query or in a textbox but VBA has it's own If statement block which you are correctly using in the lines preceding it.
I would actually use the Nz Function to simplify it even more as follows:
UPDATED Based off of your comment below I re-looked at your overall code and noticed that "defaultcorps" is a variable and not a value I originally thought you were trying to filter by. You were wrapping the variable in quotes. My updated answer should work for you.
Me.ClientID = (Nz(DMax("[ClientID]", "tClientinfo", "[CorpsName]= '" & defaultcorps & "'"),0)+1)
MS ACCESS 2007
VBA CODE BUILDER
I am able to show a MsgBox in VBA code when a Tab is selected with
Private Sub TabCtl34_Change()
If Me.TabCtl34.Value = 1 Then 'First Page
MsgBox "Hi"
End If
End Sub
But I also want to check if the second tab is selected and a field(Name) on the form IS NULL (not on tab), msgbox or cancel event to require Name to be entered before they see the second tab.
When I add in:
Private Sub TabCtl34_Change()
If Me.TabCtl34.Value = 1 AND [FORMS]![FORMNAME]![NAME] IS NULL Then
MsgBox "Hi"
End If
End Sub
It just goes directly to the second tab even if the name is null. How do i write the vba code so it can do what I want?
IS NULL is for use in query expressions, from VBA use the IsNull() VBA function.
If Me.TabCtl34.Value = 1 AND IsNull([FORMS]![FORMNAME]![NAME]) Then
As you are running this from the form's module you can use Me to refer to the current form-instance:
If Me.TabCtl34.Value = 1 AND IsNull(Me.[NAME]) Then
You should also consider that the value may be an empty string, rather than NULL. You can combine both checks using:
If Me.TabCtl34.Value = 1 And Len(Me.[NAME].Value & "") = 0 Then
Concatenating the empty string "" coerces a NULL value to a string.
(It is preferable to explicitly identify the property Value rather than assume it as the default property.)
Does MS Access allow to get the recordsource value of the form without opening the form itself? I'm trying to optimize my code as of now, what I did is I just hide the form then get the Recordsource form query but it takes time to load since some of the forms trigger a code upon onload.
I'm late to the game here - I sometimes post answers months or years after the original question was posted, as I post my own solutions when a quick search of the 'Stack finds questions relevant to my own problem of the day, but no answers that I can actually use.
[UPDATE, 06 June 2016]
The 'NameMap' property is not available in document objects from Access 2010 onwards. However, 'Stacker Thunderframe has pointed out that this is now available in the 'MsysNameMap' table.
I have amended the code, and this works in Access 2010 and 2013.
[/UPDATE]
Most of a form's properties are only available when the form is open, but some are available in the form's entry in the DAO Documents collection.
The DAO 'document' is a horrible object: it won't persist in memory and you have to refer to it explicitly every time you use it:
FormName = "MyForm"
For i = 0 To Application.CodeDb.Containers("Forms").Documents(FormName).Properties.Count - 1
Debug.Print i & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Name & vbTab & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Value
Next
Run that snippet for your form, and you'll see a 'NameMap' property that contains a list of the form's controls, and some of the form's properties.
...In a truly horrible format which needs a binary parser. You might want to stop reading and take an aspirin, right now, before continuing.
Health Warnings:
The NameMap Property is undocumented. It is therefore unsupported and there is no guarantee that this solution will work in future versions of Microsoft Access.
The solution in my code below will stop working if the NameMap's two-byte binary label for a Record Source ever changes, or if it's locale-specific.
This is a horrible hack: I accept no liability for any effects on your sanity.
OK, here's the code:
A VBA function to return the Record Source from a closed MS-Access form:
Private Function FormRecordSource_FromNameMap(FormName As String) As String
' Reads the Record Source from the NameMap Property of the Document object for the form.
' WARNING: there is a potential error here: if the form's RecordSource property is blank
' and it has one or more list controls with a .RecordSource property populating
' the list, this function will return the first list control's Record Source.
' This won't work if you're using non-ASCII characters (Char > 255) in your form name.
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim arrByte() As Byte
Dim strOut As String
If Application.Version < 12 Then
arrByte = Application.CodeDb.Containers("Forms").Documents(FormName).Properties("NameMap").Value
For i = 1 To UBound(arrByte) - 2 Step 2
' 2-byte marker for a querydef in the NameMap:
If (arrByte(i) = 228 And arrByte(i + 1) = 64) Then
j = i + 2
Do While arrByte(j) = 0 And arrByte(j + 1) = 0 And j < UBound(arrByte)
' loop through the null chars between the marker and the start of the string
j = j + 2
Loop
strOut = ""
Do Until (arrByte(j) = 0 And arrByte(j + 1) = 0) Or j >= UBound(arrByte) - 2
If arrByte(j) = 0 Then j = j + 1
' loop until we reach the null char which terminates this string
' appending the Bchars (not unicode Wchars!) of the table or query
strOut = strOut & Chr(arrByte(j))
j = j + 2
Loop
Exit For ' we only want the first datasource
End If
Next i
Else
arrByte = Nz(DLookup("[NameMap]", "[MSYSNameMap]", "[Name] = '" & FormName & "'"), vbNullChar)
If UBound(arrByte) < 4 Then Exit Function
strOut = ""
For j = 60 To UBound(arrByte) - 2 Step 2
If arrByte(j) = 0 And arrByte(j + 1) = 0 Then Exit For
strOut = strOut & Chr(arrByte(j))
Next j
End If
frmRecordSource_FromNameMap = strOut
Erase arrByte
End Function
If you use the RecordSource in (say) OpenRecordset or a DCOUNT function, I would advise you to encapsulate it in square brackets: you might get the name of a hidden query object saved from a 'SELECT' statement in the RecordSource, and that name will contain '~' tilde characters which need special handling.
And now, something extra that you didn't ask for, but other people will be looking for if they Googled their way here for 'MS Access RecordSource for a closed form':
Getting an MS-Access form's RecordSource, whether it's open or not
Most times, your form will be open. Problem is, you don't know that... And if it's a subform, it might not be visible in the Forms() collection. Worse, a form that's hosted as a subform might exist as multiple instances in several open forms.
Good luck with that, if you're looking to extract dynamic properties... Like filters, or the Record Source if it's set 'on the fly' by VBA.
Public Function GetForm(FormName As String, Optional ParentName As String = "") As Form
' Returns a form object, if a form with a name like FormName is open
' FormName can include wildcards.
' Returns Nothing if no matching form is open.
' Enumerates subforms in open forms, and returns the subform .form object if
' it has a matching name. Note that a form may be open as multiple instances
' if more than one subform hosts it; the function returns the first matching
' instance. Specify the named parent form (or the subform control's name) if
' you need to avoid an error arising from multiple instances of the form.
Dim objForm As Access.Form
If ParentName = "" Then
For Each objForm In Forms
If objForm.Name Like FormName Then
Set GetForm = objForm
Exit Function
End If
Next
End If
If GetForm Is Nothing Then
For Each objForm In Forms
Set GetForm = SearchSubForms(objForm, FormName, ParentName)
If Not GetForm Is Nothing Then
Exit For
End If
Next
End If
End Function
Private Function SearchSubForms(objForm As Access.Form, SubFormName As String, Optional ParentName As String = "") As Form
' Returns a Form object with a name like SubFormName, if the named object SubFormName is subform
' of an open form , or can be recursively enumerated as the subform of an open subform.
' This function returns the first matching Form: note that a form can be instantiated in multiple
' instances if it is used by more than one subform control.
Dim objCtrl As Control
For Each objCtrl In objForm
If TypeName(objCtrl) = "SubForm" Then
If objCtrl.Form.Name Like SubFormName Then
If ParentName = "" Or objForm.Name Like ParentName Or objCtrl.Name Like ParentName Then
Set SearchSubForms = objCtrl.Form
Exit For
End If
Else
Set SearchSubForms = SearchSubForms(objCtrl.Form, SubFormName, ParentName)
If Not SearchSubForms Is Nothing Then
Exit For
End If
End If
End If
Next objCtrl
End Function
Public Function FormRecordSource(FormName As String, Optional ParentName As String = "") As String
' Returns the Recordsource for a form, even if it isn't open in the Forms() collection
' This will look for open forms first. If you're looking for a subform, you may need a
' parent name for the form which hosts the subform: your named form might be open as a
' subform instance in more than one parent form.
' WARNING: there is a potential error here: if the form isn't open, and it has a blank
' RecordSource property, and it has one or more controls with a .RecordSource
' property populating a list, a list control's RecordSource could be returned
Dim objForm As Form
If FormName = "" Then
Exit Function
End If
Set objForm = GetForm(FormName, ParentName)
If objForm Is Nothing Then
FormRecordSource = FormRecordSource_FromNameMap(FormName)
Else
FormRecordSource = objForm.RecordSource
Set objForm = Nothing
End If
End Function
Share and enjoy: and please accept my apologies for any unwanted line breaks in the code sample.
One option would be to save the Record Source of the form as a Query. Say you have a form named [AgentForm] whose Record Source is
SELECT ID, AgentName FROM Agents
In your development .accdb copy of the database, open the form in Design View and open the Record Source in the Query Builder. Click the "Save As" button ...
and save the query as "AgentForm_RecordSource". Now the Record Source property of the form is just a reference to the saved query, and the query itself can be accessed directly through a QueryDef object. So, you could retrieve the SQL statement for the form's Record Source with
Dim cdb As DAO.Database, qdf As DAO.QueryDef, sql As String
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
sql = qdf.SQL
or you could go ahead and open a Recordset with
Dim cdb As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
Set rst = qdf.OpenRecordset
If the form's Record Source is a SELECT statement rather than the name of a table or saved query, you can check the QueryDefs collection for the hidden QueryDef which Access created for that Record Source statement.
If it exists, you can check its .SQL property.
strFormName = "Form15"
? CurrentDb.QueryDefs("~sq_f" & strFormName).SQL
SELECT DISTINCTROW *
FROM [DB Audits];
You can trap error #3265, "Item not found in this collection", which will be thrown if that QueryDef does not exist.
Since you can't open your form in design view and opening your form regularly is causing performance issues, there are but a few more workarounds:
Depending on how you want to check for the closed form's recordsource, you can set a global variable in the following way, in a separate module:
Public glb_getrecordsource As String
Afterwards, depending on how you call the code, you can do the following:
Private Sub Command1_Click()
glb_getrecordsource = "Yes"
DoCmd.OpenForm "Form1"
'... Do something
End Sub
Then, as the final step, put the following at the beginning of your form's OnLoad event:
Private Sub Form_Load()
If glb_getrecordsource = "Yes" Then
glb_getrecordsource = Me.Form.RecordSource
DoCmd.Close acForm, "Form1", acSaveYes
Exit Sub
End If
'... Usual OnLoad events
End Sub
This will at least solve the performance issues, since you will not trigger any of the time consuming events, in the form's load event.
Another workaround:
You can export your form to a .txt file and then search the text file for the recordsource. The following code will export your forms to .txt files in a specified folder:
Dim db As Database
Dim d As Document
Dim c As Container
Dim sExportLocation As String
Set db = CurrentDb()
sExportLocation = "C:\AD\" 'Do not forget the closing back slash! ie: C:\Temp\
Set c = db.Containers("Forms")
For Each d In c.Documents
Application.SaveAsText acForm, d.Name, sExportLocation & "Form_" & d.Name & ".txt"
Next d
Code partly borrowed from this forum. Afterwards, you only have to open the file and search for the recordsource. If the recordsource is empty it will not be exported, so keep that in mind. Also, I doubt this will improve perfomance, but who knows!