Pointing at Access controls, Visual Basic - ms-access

My function:
Public Function openRpt(strReportName As String, form as ??, subform as ??)
On Error Resume Next
If (Forms![form]![subform].Form.lock = False) Then
DoCmd.RunCommand acCmdSaveRecord
End If
DoCmd.OpenReport strReportName, acViewPreview, "", _
"[num]=Forms![form]![subform].Form.[num]"
End Function
What it does is to save a record from subform if it isn't locked, and then launch a report. I want to switch from and subform with matching variables that will point at the right forms, and will be given as arguments.
What type of variable should I use?
What is the difference between using '!' and '.' to access properties? I understand that '!' is used to access a controller and '.' is used to access a table record, am I right?

for (1):
First, it may be tempting to call variables form and subform, but watch out for possible errors if names you use match or are close to reserved names.
Also, lock is not a valid property of a form or subform. If you are trying to test if a form has been changed, you would test .Dirty
This is how I would code your function:
Option Explicit
Public Function openRpt(strReportName As String, frm as String, sFrm as string)
If (Forms(frm).Form(sFrm).Form.Dirty = True) Then
DoCmd.RunCommand acCmdSaveRecord
End If
DoCmd.OpenReport strReportName, acViewPreview, "", _
"[num]=Forms(""" & frm & """).Forms(""" & sFrm & """).Form.[num]"
End Function
for (2):
from the Microsoft Blog
The dot gives you early binding and is resolved at compile time, the bang is resolved at runtime.
In the case of Forms, both controls and fields in the underlying query can be referenced via a dot since they are all in the type library for the form.
Also in the case of Forms, if you change the underlying query at runtime - dot references to the old query's fields will fail at runtime since update of the Form's type library cannot happen at runtime.
Because the dot is early bound, IntelliSense happens by default with the dot, not with the bang.
The dot (since it is early bound) is faster than the bang, but no one runs enough code that uses these items enough for the performance difference to actually matter.

Related

MS Access Library Template - Reference Category Value in VBA

Completely new to MS Access - I'm trying to do a simple modification to the MS Access Lending Library template using VBA.
Going into the Asset List form and clicking New Asset pops up the Asset Details form, where I can click the Save and New button. This calls the VBA code cmdSaveandNew_Click().
My question is, within this VBA method, how do I reference the values of the fields in the Asset Details form - For example getting the value that the user entered in the Category field and storing it in a variable.
I've tried several strings of code to try to get the value back, but I keep getting various errors, saying it can't find the referenced form, or Object Required, or this method is supported. I think it's likely just that I don't fully understand the structure of what I'm trying to reference or the syntax for specifying how to reference it.
Right now (after I've been trying many different combinations of things, I have:
text = Forms![Asset List].[Asset Details].Form.Category
which is giving me the "can't find the field '|1' referred to in your expression" error.
Edit - code currently looks like this:
Private Sub cmdSaveandNew_Click()
'On Error GoTo cmdSaveandNew_Click_Err
Dim someVar As String
Dim num As Integer
Dim objtf
'objtf = Forms![Asset List].[Asset Details].Form.Category
objtf = Me.Category
'num = Form.Controls.Count
Msg.Box (TypeName(objtf))
someVar = InputBox("How many?")
On Error Resume Next
If (Form.Dirty) Then
DoCmd.RunCommand acCmdSaveRecord
End If
If (MacroError.Number <> 0) Then
Beep
MsgBox MacroError.Description, vbOKOnly, ""
Exit Sub
End If
On Error GoTo 0
DoCmd.GoToRecord , "", acNewRec
DoCmd.GoToControl "Item"
MsgBox someVar
cmdSaveandNew_Click_Exit:
Exit Sub
cmdSaveandNew_Click_Err:
MsgBox Error$
Resume cmdSaveandNew_Click_Exit
End Sub
The variable must be declared global in a general module if you want to reference from various modules. Then setting it by code behind the Asset Details form would be simply: SomeVariableName = Me.Category.
Don't use reserved words as names - text is a reserved word.

VBA Using Global Variables to open Multiple instances of a form in Access 2010

I have a form Cases and session dates that display the patients that I have seen. I am trying to create a method by which I can double click on any client's name and it opens up a different form with more detail. I want to be able to do this with multiple patients, so for example, I could select and have open the more detailed forms for the clients I am going to see today.
The form that displays the summary detail is called CaseAndSessionDetail and the more detailed form ClientDetails.
I am using Ms-Access 2010 running under windows 8.1
I have written/copied a module which I think should do what I want by using a global variable to create a collection of forms:
Option Explicit
'This holds the collection of forms as they are created
Global mcolFormInstances As New Collection
Function OpenFormInstance(FormName As String, WhereCondition As String)
'Declare for name
Dim frm As form
Select Case FormName
Case "ClientDetails"
Set frm = New Form_ClientDetails
Case Else
Debug.Assert False
End Select
If WhereCondition <> "" Then
frm.Filter = WhereCondition
frm.FilterOn = True
End If
''make the form visible
frm.Visible = True
'Need to add a reference to the form so that it does not
'immediately close when the for variable goes out of scope
mcolFormInstances.Add (frm)
End Function
And the function works and selects and opens/display the correct client details form if you set a breakpoint at the last line mcolFormInstances.Add(frm). Without the breakpoint the form is closed again (I suspect because the variable goes out of scope after the function ends.
For reference the function is called by macro on the form "CaseAndSessionDetail"
If Not ISNull([Screen].[ActiveControl] Then
Function OpenForm("frmContactDetails" = '"& Ltrim([Screen].[ActiveControl]) & "'")
EndIF
I suspect that I am not declaring the collection object as a global variable correctly. I have tried declaring it in both forms in a separate module, using Public and Global and have yet to find success.
I realize I am probably overlooking something very simple but would welcome any help
Your issue is simply an obscure syntax mistake on this line:
mcolFormInstances.Add (frm)
The parentheses around the argument frm are not surrounding an argument list - that is why the VBE inserted a space between .Add and (frm) for you when you tried to type mcolFormInstances.Add(frm). These are completely different semantically. Surrounding an expression with parentheses in this context is described in section 5.6.6 of the VBA language specification:
5.6.6 Parenthesized Expressions
A parenthesized expression consists of an expression enclosed in
parentheses.
Static semantics. A parenthesized expression is classified as a value
expression, and the enclosed expression must able to be evaluated to a
simple data value. The declared type of a parenthesized expression is
that of the enclosed expression.
parenthesized-expression = "(" expression ")"
Runtime semantics. A parenthesized expression evaluates to the simple
data value of its enclosed expression. The value type of a
parenthesized expression is that of the enclosed expression.
What this means is that you are evaluating the run-time expression frm as a simple data type and storing that in your Collection. Regardless of what that value actually is (I'd have to check the evaluation tree to figure out what it evaluates to), you aren't incrementing the reference count on frm, and this allows it to go out of scope and get released as OpenFormInstance is exiting.

Access VBA: Passing Form/Subform name to a Function?

I am attempting to pass a form/subform name to a function without success. The function steps thru each .Control on the subform and performs a simple set of look-ups & actions. My code works as expected with the Form/Subform name hard coded; I am looking for a more generic approach.
Getting a type mismatch error on the function call, with and without quotes.
Example:
'Function Call
call AuditChanges("forms![someForm]![someSubForm]")
'Audit Function
Sub AuditChanges(thisForm as form)
Dim ctl As Control
Dim strTest as string
For each ctl in thisForm.controls
strTest = ctl.Value
'do some stuff
Next ctl
end sub
Any suggestions for proper syntax to pass the form/subform info?
Thanks!
Your sub call is all kinds of weird:
The likely cause of your error is the quotes. You're currently passing a string to your function containing "forms![someForm]![someSubForm]"
Furthermore, you shouldn't use parentheses when you're not receiving a return value (so never when calling a sub).
Also, the Call keyword has been deprecated a long time ago.
And you likely want to pass the form object, and not the subform control object
Try calling your sub like this:
AuditChanges forms![someForm]![someSubForm].Form
(Never had this many comments on one line of code before)

Loop through every control in a form, with the form name defined as a variable

I am trying to create a basic search which looks for a partial keyword match of any control in a specified form. The form name is selected via combo box and is stored as a variable.
How do I use this to loop through the controls of the selected form?
I can easily loop through the controls of the current form with the following:
For Each ctrl In Me.Controls
Debug.Print ctrl.Name
Next ctrl
But I can't figure out how to reference an external form, with the variable essentially replacing Me.
I've tried using:
Dim ctrl as Control
Dim variableName as String
variableName = Me.cmboFormName
For each ctrl in Forms(variableName).Controls
Debug.Print ctrl.Name
Next ctrl
But this just returns error 438 (Object doesn't support this property or method).
You need to Dim ctrl as well, and you may have spaces in the form name:
Dim ctrl As Control
Dim variableName as String
variableName = "[" & Me.cmboFormName & "]"
For Each ctrl In Forms(variableName).Controls
Debug.Print ctrl.Name
Next
Try this:
For Each ctrl In UserForm1.Controls 'or use your form name.
Debug.Print ctrl.Name
Next ctrl
Since the error is occurring on Forms(variableName).Controls, the instruction is doing too many things to be easily debuggable. Split it up.
Declare a variable to contain a Form object - note, I'm no Access dev, I usually work with Excel and MSForms, so for me that would be a UserForm, but I very strongly suspect that your Access forms aren't from the MSForms type library (you'd have to work pretty hard to get a UserForm in Access), so I'm guessing the type to use is called Form, but I could be wrong - will be happy to edit if corrected in a comment.
Dim theForm As Form
Now Set the form object:
Set theForm = Forms(variableName)
If your code didn't blow up yet, then you've successfully retrieved a form instance. That wouldn't surprise me, because if that step failed you'd probably be facing a subscript out of range error instead.
If your library has a Controls collection class, declare a variable of that type:
Dim theControls As Controls
And assign it:
Set theControls = theForm.Controls
That could possibly blow up with run-time error 438, if Form doesn't have a Controls member... which wouldn't really add up, given Me.Controls seems to work.
So go back and declare theForm As Object, and let VBA's late-binding magic query the object's interface, instead of working with the specific Form interface - again I don't know much Access, but it's fairly possible that Me being a specific form type exposes a Controls collection, but not the general-purpose Form type - VBA UI framework internals are that kind of a mess.
So, to speak COM-gibberish, by declaring it as Object you allow the run-time to query IDispatch and locate the Controls member.
If you can get that instruction (the Controls collection assignment) to run without throwing, then you should be able to iterate its content without problems.
Also keep in mind that it seems the Forms collection only includes open forms.
Forms contains collection of opened forms. Unfortunately you cannot access a closed form using a variable name.
Just check whether the form is available. if not open the form either in nomal or design view and continue your code like this.
Dim ctrl as Control
Dim variableName as String
variableName = Me.cmboFormName
If Not (FN_FORM_ISLOADED(variableNAme)) Then
'Open your form in desired view.
DoCmd.OpenForm variableNAme, acNormal
End If
For each ctrl in Forms(variableName).Controls
Debug.Print ctrl.Name
Next ctrl
Place this function in your public module
Public Function FN_FORM_ISLOADED(iFormName As String) As Boolean
Dim I As Integer
FN_FORM_ISLOADED = False
For I = 0 To Forms.count - 1
If Forms(I).name = iFormName Then
FN_FORM_ISLOADED = True
Exit Function
End If
Next I
End Function

Get caption of closed report

I have a custom build print dialog that is used for many reports. Its arguments are the report name, filter string, open args for the report etc. What I would like to do is to display the caption of the report specified for printing on the form. For performance reasons, I would rather not open the report, get the caption and close it again. I would rather get it from the database somehow without actually opening the report itself.
One thing that DOES work is to call the report using it's class name report_some_report.caption but there is no way to do this without with a report name stored in a variable. I would have expected Reports("some_report").caption to work also but it only works for open reports.
Is there a better way to do this or am I going to have to do something like the following? (Which works)
docmd.OpenReport "schedule_simple",acViewDesign,,,acHidden
strCaption = Reports("schedule_simple").Caption
docmd.Close acReport,"schedule_simple"
There's no way to get a report caption from a report without first opening the report or using the report class object (as you know). It's also worth noting that "lightweight" reports (ie, ones whose HasModule property = False) do not have class objects.
You have a couple of options.
You could create a local table with a RptName and RptCaption field and query that. Of course, then you need to keep it updated somehow.
You could write a function that "memoizes" the results, so that you only have to open a given report once each time the program runs. For example:
.
Function GetReportCaption(RptName As String) As String
Static RptCaptions As Collection
If RptCaptions Is Nothing Then Set RptCaptions = New Collection
On Error Resume Next
GetReportCaption = RptCaptions(RptName)
If Err.Number = 0 Then Exit Function
On Error GoTo 0
DoCmd.OpenReport RptName, acViewDesign, , , acHidden
RptCaptions.Add Reports(RptName).Caption, RptName
DoCmd.Close acReport, RptName
GetReportCaption = RptCaptions(RptName)
End Function