Access For Loop Hide Objects - ms-access

I'm trying to hide/unhide around 30 objects on my form when the user selects certain values from a dropdown menu. I tried the loop below, however I receive the following error: 'Object doesn't support this property or method.' I have this code running on the 'AfterUpdate' of the dropdown menu object.
Dim VisibleVisitFields() As String
Dim VisibleVisitFieldlist As String
Dim varVisibleVisit As Variant
VisibleVisitFieldlist = "VisitDate_Event,VisitTime_Event,VisitSite_Event,VisitStaff_Event,VisitMeet_Event"
VisibleVisitFields = Split(VisibleVisitFieldlist, ",")
If (EventType = 3) Then
For Each varVisibleVisit In VisibleVisitFields
[Forms]![subFRM_TBL_Event-All in One].Controls(varVisibleVisit).visible = True
Exit For
Next
Else
If (EventType <> 3) Then
For Each varVisibleVisit In VisibleVisitFields
[Forms]![subFRM_TBL_Event-All in One].Controls(varVisibleVisit).visible = False
Exit For
Next
End If
End If

Which line triggers the error? Suspect it is reference to the subform that is flawed. Never seen code like that to loop through an array. Suggest naming subform container different from the object it holds, such as ctrEvent. What is EventType - a textbox/field on the form? Consider code:
Dim aryFields As Variant
Dim x As Integer
aryFields = Split("VisitDate_Event,VisitTime_Event,VisitSite_Event,VisitStaff_Event,VisitMeet_Event", ",")
For x = 0 To UBound(aryFields)
Me.ctrEvent.Form.Controls(aryFields(x)).Visible = Me.EventType = 3
Next
Alternative methods not using array:
Set control Tag property then code loops through all controls on form and sets visibility for those that have particular value in Tag.
Dim ctrl As Control
For Each ctrl in Me.ctrEvent.Form.Controls
If ctrl.Tag = "something" Then ctrl.Visibility = Me.EventType = 3
Next
Another is to give controls similar names, like: Visit1, Visit2, etc. Then code:
Dim x As Integer
For x = 1 to 30
Me.ctrEvent.Form.Controls("Visit" & x).Visible = Me.EventType = 3
Next
Advise no spaces or punctuation/special characters (underscore only exception) in naming convention.

You are trying to iterate over an array of strings.
Dim VisibleVisitFields() As String
You need to declare the array to contain variants (which can still contain strings)
Dim VisibleVisitFields() As Variant

Related

How to Autofill Textboxes on a Form using a Loop?

So I have a table that has a list of totals im trying to display on a form, I have 10 totals I need to get from the totals table and display in 10 textboxes on the form.
The 10 textboxes are "A1, A2, A3..." and its using DLookup to find the ID field number.
It seems like its a syntax issue with Me.TEXTX & X1.Value though I'm not sure how else I can type it.
Hope this makes sense. Thanks!
Private Sub UPDATETOTALS()
Dim FORMX As String
FORMX = "GRID"
Dim TEXTX As String
TEXTX = "A"
Dim TABLENAMEx As String, FINDFIELDx As String, GETFIELDx As String
TABLENAMEx = "GRID_TOTALS"
FINDFIELDx = "[ID]="
GETFIELDx = "TODAY"
Dim X1 As Integer
For X1 = 1 To 10
Me.TEXTX & X1.Value = DLookup(GETFIELDx, TABLENAMEx, FINDFIELDx & X1)
Next X1
End Sub
You cannot access an object reference directly using a concatenated string, as such reference is not of string data type.
Instead, you will need to access the object from the relevant collection (in this case, the Controls collection), by supplying the name of the object (as a string) to the Item method of that collection.
Since the Item method is the default method for a collection, the item name can immediately follow the collection as an argument.
For example:
For X1 = 1 To 10
Me.Controls(TEXTX & X1).Value = DLookup(GETFIELDx, TABLENAMEx, FINDFIELDx & X1)
Next X1

Using VBA to extract data from website, but getting run time error '91'

Quite new to VBA, having a problem with this error code.
run time error '91' object variable or With block variable not set
I'm trying to extract data from a website and past to a excel document. My Excel doc is Book2 and my module is called Module1. I'll paste the code below.
Sub WebNavigate()
Dim CreatingObject As Object
Dim WebNavigate As Object
Set objIE = CreatingObject("InternetExplorer.Application")
WebSite = "website link"
With objIE
.Visable = True
.navigate WebSite
Do While .Busy Or .readyState <> 4
DoEvents
Loop
Set elements = .document.getElementByClass("timark")
Sheet1.Cells(i, 8) = element.innerText
End With
End Sub
In the absence of HTML/URL to go with:
1) You are spelling of Visible is incorrect
2) The following:
Set elements = .document.getElementByClass("timark")
Is missing an s as it returns a collection and should be ClassName:
Set elements = .document.getElementsByClassName("timark")
3) You may need a pause or loop to ensure elements is available on the page.
4) This
Sheet1.Cells(i, 8) = element.innerText
You don't yet have element declared and assigned (you also don't have elements declared) . You may use in a For Loop.
e.g.
Dim element As Object, elements As Object
Set elements = .document.getElementsByClassName("timark")
For each element in elements
5) Creating should be Create (also as noted) and you need to declare objIE
Dim objIE As Object
Set objIE = CreateObject("InternetExplorer.Application")
6) i is not declared anywhere and must be greater than 1 when it is as there is no cell with row 0 in the sheet. Also, i would indicate a Loop of which there is no sign and when in a loop should be incremented to avoid overwriting the same cell.
7) Dim WebNavigate As Object is unassigned and not needed at present in the code.
8) To avoid many of the above use Option Explicit at the top of your code (As already mentioned).

loop through listfield in access

I need some help to create a loop through a listfield.
listfield:
lstPlanung
function:
Dim i As Integer
For i = 0 To Me!lstPlanung.ListCount - 1
Me!lstPlanung.Selected(i) = True
Call sendemailKunde
Next i
Is it possible to modify the loop that it selects every entry of my listfield?
It's unclear.
Is it this you are trying to do ?
Dim i As Integer
dim strFoo as string
For i = 0 To Me!lstPlanung.ListCount - 1
' Get the current listbox item in the loop
strFoo = Me.lstPlanung.ItemData(i)
Call sendemailKunde
Next i
With this, strFoo will contain the element of your listbox at each iteration in your loop. But you have to do something with it...

How to create a ListBox with checkboxes in a Microsoft Access Form?

I'm new to Microsoft Access and would like to create a ListBox (or ListView) with checkboxes, however I can't find any native way for doing so.
My intention is to display a list of values and have some of the values checked depending on what value is selected in a ComboBox on the form.
Please note that I'm needing such a control for a form and not a table (for which there's this "multivalued lookup field"). (Besides if there's a way to create a subform with a table with just the multivalue-column that reacts to what's selected in the ComboBox.)
An ordinary list box with the "Multi Select" property set to "Simple" doesn't display checkboxes.
I also can't see the "ListStyle" property described here.
Maybe it's somehow possible to display two columns in the ListBox of which the first is rendered as checkbox?
You can use the ListView control. It is located under ActiveX Controls, the full name is Microsoft ListView Control, version 6.0.
It has a separate set of properties: right-click -> ListViewCtrl object -> Properties, in there is the Checkboxes property.
To fill the listview with data, see e.g. ACC: Sample Function to Fill a ListView Control
More info: Using the ListView Control
Edit
To comfortably work with the Listview object model, set a reference to Microsoft Windows Common Controls 6.0 = C:\Windows\SysWOW64\MSCOMCTL.OCX on my Windows7 64bit.
Edit 2
I use a TreeView with checkboxes. Each Node has a Checked property, that checks or unchecks its checkbox. Where the Treeview has Nodes, the Listview has ListItems, but they have a Checked property too.
Simplified code for Treeview (without hierarchies):
Dim oTree As TreeView
Dim oNode As Node
Dim RS As Recordset
Set oTree = Me.myTreeView.Object
oTree.Nodes.Clear
Set RS = DB.OpenRecordset("My query to fill the treeview")
Do While Not RS.EOF
Set oNode = oTree.Nodes.Add(key:=RS!foo, Text:=RS!bar)
oNode.Checked = (RS!someValue > 0)
RS.MoveNext
Loop
RS.Close
You can't modify a listbox of Access like that, but you can customize a subform in datasheet view to mimic such a listbox.
To display more or less fixed values, create a small local table to be bound by the form and fill it with the values you need.
So got it working now with the help of Andre's answer:
First, as the ListView is dependent on the currently selected item of a table I'm populating it via the Form_Current event of the table. (Simply by Call Forms.Item("MainForm").PopulateListView)
Here's the working PopulateListView method (note that you need to reference Microsoft Windows Common Controls 6.0 first):
Public Sub PopulateListView()
On Error GoTo ErrorHandler
Dim intToCount As Integer
Dim intCount1 As Integer
Dim intCount2 As Integer
Dim intToCount2 As Integer
Dim intCount12 As Integer
Dim intCount22 As Integer
Dim NewLine As Object
Dim db As Database
Dim rs As Recordset
Dim colNew As Object
Dim s As String
' Clear the ListView control.
Forms![MainForm].[SubForm].Form.[ctlListView].ListItems.Clear
Forms![MainForm].[SubForm].Form.[ctlListView].ColumnHeaders.Clear
' Set Variables.
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT A, B, IsChecked . . .")
' Set Column Headers.
Set colNew = Forms![MainForm].[SubForm].Form.[ctlListView].ColumnHeaders.Add(, , "A", 2000)
Set colNew = Forms![MainForm].[SubForm].Form.[ctlListView].ColumnHeaders.Add(, , "B", 4000)
' Set Total Records Counter.
rs.MoveLast
intToCount = rs.RecordCount
rs.MoveFirst
' Loop through recordset and add Items to the control. Twice as a workaround to sort by checkbox.
For intCount1 = 1 To intToCount
If (rs(2).value = 1) Then
If IsNumeric(rs(0)) Then
s = Trim(Str(rs(0).value))
Else
s = Trim(rs(0).value)
End If
Set NewLine = Forms![MainForm].[SubForm].Form.[ctlListView].ListItems.Add(, , s)
If IsNull(rs(1)) Then
NewLine.ListSubItems.Add Text:=""
Else
NewLine.ListSubItems.Add Text:=rs(1).value
End If
NewLine.Checked = True
End If
rs.MoveNext
Next intCount1
' Set Total Records Counter.
rs.MoveLast
intToCount2 = rs.RecordCount
rs.MoveFirst
For intCount12 = 1 To intToCount2
If (rs(2).value = 0) Then
If IsNumeric(rs(0)) Then
s = Trim(Str(rs(0).value))
Else
s = Trim(rs(0).value)
End If
Set NewLine = Forms![MainForm].[SubForm].Form.[ctlListView].ListItems.Add(, , s)
If IsNull(rs(1)) Then
NewLine.ListSubItems.Add Text:=""
Else
NewLine.ListSubItems.Add Text:=rs(1).value
End If
End If
rs.MoveNext
Next intCount12
Exit Sub
ErrorHandler:
' Err 3021 = no current record. Err 2455 = happens at necessary first call of method and couldn't catch in code.
If Err = 91 Or Err = 3021 Or Err = 2455 Then
Resume Next
Else
If Err <> 94 Then
' Otherwise display the error message.
MsgBox "Error: " & Err.Number & Chr(13) & Chr(10) & Err.Description & vbCrLf & "(PopulateListView)"
End If
End If
End Sub
Then for saving I'm using this:
For Each Item In Forms![MainForm].[SubForm].Form.[ctlListView].Object.ListItems
If Item.Checked = True Then
'Use Item here
End If
Next

Get Form Recordsource without opening the form

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 &LT; 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 &LT; 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 &GT;= 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) &LT; 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!