Populate a Multi-column Combobox with a 2D array on Access - arrays

I tried to follow this method:
ComboBox1.ColumnCount = 2
Dim Films(1 To 5, 1 To 2) As String
Dim i As Integer, j As Integer
Films(1, 1) = "Lord of the Rings"
Films(2, 1) = "Speed"
Films(3, 1) = "Star Wars"
Films(4, 1) = "The Godfather"
Films(5, 1) = "Pulp Fiction"
Films(1, 2) = "Adventure"
Films(2, 2) = "Action"
Films(3, 2) = "Sci-Fi"
Films(4, 2) = "Crime"
Films(5, 2) = "Drama"
ComboBox1.List = Films
source
But the .List property does not work on Access. Any ideas ?

As June7 said, use the ComboBox.AddItem() method in a loop. For your purposes, the ComboBox must not be bound to a data source: It's Row Source Type property should be set to "Value List". To add a multi-column string to a ComboBox row, use a semicolon to delimit the the columns. For example:
ComboBox1.AddItem (Films(1, 1) & ";" & Films(1, 2))
or
Dim rowStr As String
rowStr = Films(1, 1) & ";" & Films(1, 2)
ComboBox1.AddItem (rowStr)
AddItem() automatically appends the row to the end of the ComboBox's list, if you do not specify a row index parameter. For more info, see ComboBox.AddItem method at Office Dev Center.
Screenshot: VBA Demonstration Image

A "Form" in Access is not the same kind of element/object as a "UserForm" is in Excel where your "source" link points to (https://www.excel-easy.com/vba/examples/multicolumn-combo-box.html).
In Access it would be a good idea to get the information into your Combo Box (or List Box) from either a table or a query. You can of course code it with VBA, but then you might find yourself adding/editing a hole lot of VBA here and there, as in Access it all goes more naturally by using SQL and the database engine.
This is a larger topic, but basically you should probably have different tables for "Films" and for "Categories"
Table1:
Table2:
Then you should define the relationships since most likely there are different amount of films in your database than there are categories. Saying that we would like to avoid a situation that you would have to add another movie, let's say "Die hard" into your movie list. That would probably fall into the category "Action". In the database we do not want to repeat ourselves. Just we will, by ID, refer to categoryID by it's value.
So, having done that you need to create a form in Access. Create maybe a query that will get the values for you:
After this you can define the source for the combo e.g. by using wizard:
So this way you can maintain each of the lists separately in their own tables.
Here is the query that got created:
On the Data tab you can decide which bound column to use relative to datasource.
On the Format tab you can adjust the widths of the columns in your combobox. Use 0 length to hide a column.
This way no VBA code is needed.
If needed it is also possible to create or edit the queries with VBA but that is another story.
Hope this helps.

Related

how to populate a table of lookups using "Not in list"

Table 1: My general information table
Organization: A lookup/relationship field that defaults to "N/A" but pulls values from...
Table 2: Organization List
No ID field, just the names of the organizations in the order which they were added
My data entry form has a combo box for organization and I would like it to update when I add an organization that hasn't been added before. I know I am supposed to use the "Not in List" event, but I don't know how to update the Organization list using this event. How do I do this?
Make sure the Limit To List property of the combo-box is set to Yes.
Add a On Not In List event to insert the value to your source table when a new value appears:
Private Sub MyComboBox_NotInList(NewData As String, Response As Integer)
With DoCmd
.SetWarnings False
.RunSQL "INSERT INTO [Organization List](Organizations) VALUES ('" & NewData & "')"
Response = acDataErrAdded
.SetWarnings True
End With
End Sub
Edit... nearly forgot... before I answer, what have you tried? :)
Edit 2... the example given is for a string value. Remove the ' from either side of New Data if it's a numeric value (but probably not if it's an organisation name).
Edit 3... The INSERT SQL is just one way of putting data into a table. You may prefer the RecordSet.Add and .Update methods.

VBA to paste only certain values of cell from one sheet to another

Can some one help me with the below code, what I am looking for is, from sheet "Form" certain values of cells mentioned in 2 sets of Array.
1st set of Array should get copied to sheet "Tracker" C3 onward and second set of array from next cell after the 1set of array ends say EF3 onwards.
whereas now first sett is its pasting from A3 and second from A4. Please let me know in case of any question.
Following is the code which I am using now:
Sub AddEntry()
Dim LR As Long, i As Long, cls
Dim LR2 As Long, j As Long, cls2
cls = Array("C2", "C3", "G2", "G3", "C5", "C6", "C7", "C8", "C9", "C10", "C11", "C12", "C13", "A17", "C17", "D17", "F17", "G17", "H17", "A18", "C18", "D18", "F18", "G18", "H18", "A19", "C19", "D19", "F19", "G19", "H19", "A20", "C20", "D20", "F20", "G20", "H20", "A21", "C21", "D21", "F21", "G21", "H21", "A25", "B25", "C25", "D25", "E25", "F25", "G25", "H25", "A26", "B26", "C26", "D26", "E26", "F26", "G26", "H26", "A27", "B27", "C27", "D27", "E27", "F27", "G27", "H27", "A28", "B28", "C28", "D28", "E28", "F28", "G28", "H28", "A32", "C32", "E32", "G32", "H32", "A33", "C33", "E33", "G33", "H33", "A34", "C34", "E34", "G34", "H34", "A35", "C35", "E35", "G35", "H35", "A39", "D39", "F39", "A40", "D40", "F40", "A41", "D41", "F41", "A45", "C45", "E45", "G45", "A46", "C46", "E46", "G46", "A47", "C47", "E47", "G47", "D51", "D52", "D53", "D54", "D55", "D56", "D57", "D58", "D59", "D60", "D61", "D62", "D63", "D64", "D65", "D66", "D67")
With Sheets("Tracker")
LR = WorksheetFunction.Max(3, .Range("C" & Rows.Count).End(xlUp).Row + 1)
For i = LBound(cls) To UBound(cls)
.Cells(LR, i + 1).Value = Sheets("Form").Range(cls(i)).Value
Next i
End With
cls2 = Array("E51", "E52", "E53", "E54", "E55", "E56", "E57", "E58", "G59", "E60", "E61", "E62", "G63", "E64", "E65", "E66", "E67", "C70", "D70", "E70", "F70", "G70", "H70", "C71", "E71", "G71", "C72", "E72", "G72", "C73", "E73", "G73", "C74", "E74", "G74", "C75", "E75", "G75", "C76", "E76", "G76", "C77", "E77", "G77", "C78", "E78", "G78", "C79", "E79", "G79", "C82", "D82", "E82", "F82", "G82", "H82", "C83", "E83", "G83", "C84", "E84", "G84", "B88", "B89", "B90", "B91", "C88", "C89", "C90", "C91", "D88", "D89", "D90", "D91", "E88", "E89", "E90", "E91", "F88", "F89", "F90", "F91", "G88", "G89", "G90", "G91", "H88", "H89", "H90", "H91")
With Sheets("Tracker")
LR2 = WorksheetFunction.Max(3, .Range("EW" & Rows.Count).End(xlUp).Row + 1)
For j = LBound(cls2) To UBound(cls2)
.Cells(LR, j + 1).Value = Sheets("Form").Range(cls2(j)).Value
Next j
End With
End Sub
Assuming that you want to start cell entries in sheet "Tracker" more to the right, you can add the column number instead of +1 (= column A) and write as follows:
Array 1: assigning cell values starting from column C
.Cells(LR, i + [C1].Column).Value = Sheets("Form").Range(cls(i)).Value
Array 2: assigning cell values starting from column EF
' should be LR2 instead of LR :-)
.Cells(LR2, j + [EF1].Column).Value = Sheets("Form").Range(cls2(j)).Value
Note
[C1].column returns the column number (in any worksheet), e.g. column C Counts 3.
I took a look at your file; the first thing I did was flip through the VBA & try to compile it -- which incidentally, I would recommended to anyone as a first step with a downloaded XLSM. (I haven't seen a malicious macro yet and I'd like to keep it that way!)
I can see that this file has been a "work in progress" because there are bits of code here and there that don't compile properly, such as Me statements pointing to a missing userform, and references to mis-named worksheets such as Form (View) instead of View_Form.
Ideally, this project should be moved from Excel to Access. Excel can be used for filling forms and storing data, but if this is potentially going to sizable, you're best off to use "the right tool for the job". Duplicating your form(s) into Access forms instantly removes the need to copy certain cells to certain sheets, not to mention ease of validation, reporting, security, and unlimited room for expansion plus ease of moving data between Excel, Access, Outlook, etc.
(You even called the spreadsheet a database in one spot!) If your concern is that you're unfamiliar with Access, if you designed this workbook, migration to Access will be a breeze once you figure out the basics of table and form design.
Even Outlook has some pretty nifty form capabilities which can autopopulate the data table when an emailed form is received.
If you need to stay in Excel, how about a User Form instead of the sheet-based form? I too often see people forgetting about Office's built-in features and starting from scratch. That being said, I've been a user of MS Office for 25 years and have never used an Excel User Form. When I think "form", I think MS Access.
Another option, if you want to stay with the worksheet-based form, instead of listing all the cells in the array etc, a minor redesign could make it simpler. One way would be to have a hidden row on the form tab so you have a single uninterrupted line of all the data you need to store. For example, you could hide row 1 and 2, make row 1 the headings like Sourced Processed Year Address etc. and then row 2 could be an "interim" place to store the data, so A2 formula is =C2, B2 is =C3', B3 is=C5` etc.
Finally another sneaky option could be to add hidden comments in each cell that has data that needs to be saved, and then when the form is complete, loop through all the cells looking for comments, and each comment would contain a title or cell reference indicating where that cell's data needs to go.
The destination should be a very straightforward table Use as many columns as you need, but it's not a place for formatting or formulas. (Think database!)
For example, C2 (Sourced By) could have a hidden comment like "Tracker:C" then when the form is filled, you could parse the comments and move the data dynamically (instead of hardcoding 250 cell addresses!) with something like:
Option Explicit
Sub moveData() 'untested; example only
Dim cell As Variant, nextBlankRow As Integer
Dim comm As String, sht As String, col As String
nextBlankRow = 5 'calculate this somehow
'loop through cells with comments
For Each cell In ActiveSheet.Cells.SpecialCells(xlCellTypeComments)
If cell.Comment.Text <> "" Then
'get comment
comm = cell.Comment.Text
'extract location for data like "Sheetname:Columnletter"
sht = Left(comm, InStr(comm, ":") - 1)
col = Right(comm, Len(comm) - InStr(comm, ":"))
'populate correct location with data
Sheets(sht).Range(col & nextBlankRow).Value = cell.Value
End If
Next cell
End Sub
As with anything in Excel (or Office in General) there are a dozen ways you could accomplish the same task. Opt for the ones that don't involve repeating the same code over and over, nor hardcoded data. Planning for future (unexpected) growth is very important, as is debugging as-you-go, which is my last suggestion:
Option Explicit
at the top of every module, and Alt+DLcompile often, removing or commenting-out unused code.
Bottom line, best bet: Access, Excel, Outlook all have form capabilities built in. use a form for a form and you'll save yourself a headache now and later.
Hopefully this gives you some ideas.
Good Luck!

Moving a set of names from Excel to VBA in an array, then using it as a wildcard filter parameter

I have a list in Excel. This list is ever-growing, ever-changing, and is currently defined with a
ListbyName =offset(A1,0,0,counta(A1:A200),1)
Ok, great. Now, I get reports where I need to pull only the elements from the list to a second report. Simply dropping all of the data then filtering isn't workable, since it can only be the "essential" data that moves over. Ok, fine:
Dim listbyName As Variant
Set listbyName = Range("ListbyName")
Set CSTab = CSReport.Sheets(1)
LastRowCS = CSTab.Range("A" & Rows.Count).End(xlUp).Row
CSTab.Range("$A$1:$W$100").AutoFilter Field:=4, Criteria1:=listbyNumber, Operator:=xlFilterValues
Set RngCS = CSTab.Range("A1:W" & LastRowCS).SpecialCells(xlCellTypeVisible) RngCS = RngCS.SpecialCells(xlCellTypeVisible)
Set CabReport.Sheets("CS").Range("C1").Resize(RngCS.Rows.Count).Value = RngCS.Value
Now, where I'm struggling for a bit is the next step - I need to wildcard each entry in the array when filtering for it. So instead of the data being "Apples, Oranges, Potatoes", and I'm looking to get just the apples and oranges, it's "This product contains Apples", "May have Oranges in it" "Apple", etc. How can I put wildcards around each entry in the array, so that when it filters for that entry, it finds everything?
A second example would be, in ugly brute-force manner, of what I'm trying to accomplish:
ActiveSheet.Range("$A$1:$W$71").AutoFilter Field:=1, Criteria1:="=*Apples*", Criteria2:="=*Oranges*", Operator:=xlFilterValues
But flexible enough to accommodate additional add-ons.
How can I get wildcards in my filters? And is my method of grabbing data, filtering it, and pasting it workable, or have I made a fundamental mistake somewhere along the line?
Edit: It's also unhappy with my filter method, error 1004.

Import Data from 3 MDBs and 1 CSV, use in DataGrid object

This is eventually to be run on a primitive Windows environment for a PLC, so I'm using Visual Basic .NET 4.0 in Studio 2013.
Anyways here's the challenge I expect to be fairly simple once get going: I've got 3 MDBs with 1 table each and 1 CSV file, which I need to import data from and provide various viewing/editing functionality.
These MDBs are essentially a list of parameters for various "program settings" run by the PLC, called "recipes". There are over 200 parameters, but for some reason they were limited to 100 per file, so they had to create 3 MDBs. But they're essentially representing one long list of parameters. So for instance, the PLC might execute 4 different formats, or recipes, each with a unique "IndexName" key. So in the first MDB, the table has 4 rows and 106 columns, and in the second one 4 rows and 110 columns.
Here's a screenshot of the table in Access
"Export Demo.zip" on this post
http://www.vbforums.com/showthread.php?719287-RESOLVED-mdb-database-to-csv-file
seemed rather promising, but it's in VB6 and it's a bit beyond me to adapt to my environment.
I can create one DataSource/DataSet per source MDB/CSV, but my objective is to populate a single DataGrid object with Data from all those sources. I'm currently looking into how I can consolidate the data into one DataTable.
Pulling a few bits and pieces from various sources together, I managed to get this working. Particularly useful was the DataTable.Merge function, took me a while to find that.
Not sure how useful this code might be for others, or if it goes against stackoverflow etiquette. I'm happy to fix accordingly, but I did want to share that I managed.
Obviously this isn't the complete code, but hopefully the most relevant bits. I'm happy to provide some more pieces if required. Code comments/improvements welcome, I'm just a beginner!
Sub LoadDataIntoDataGrid()
'Loads CSV Table, Transpose & Load 3 MDB tables, Capture each Table Params, & Merge all 3 into 1
'Adjusts the merged table according to the csv file
Dim CSVTable As DataTable, MDB0Table As DataTable, MDB1Table As DataTable, MDB2Table As DataTable, _
CRLTable As DataTable
Dim CSVRow As DataRow
Dim removeRows As New List(Of String)
'Populate CSV Table
CSVTable = ReadFromCSV("CSVFile.csv")
'Populate 3 MDB tables with transposed Data: Rows are parameters
MDB0Table = ReadFromMDB("First.mdb", "SELECT * from Table1")
MDB1Table = ReadFromMDB("Second.mdb", "SELECT * from Table2")
MDB2Table = ReadFromMDB("Third.mdb", "SELECT * from Table3")
'Put each Parameter name into MDBTableParams
For Each ParamName In MDB0Table.Rows
CRLForm.MDBTableParams(0).Add(ParamName(0))
Next
For Each ParamName In MDB1Table.Rows
CRLForm.MDBTableParams(1).Add(ParamName(0))
Next
For Each ParamName In MDB2Table.Rows
CRLForm.MDBTableParams(2).Add(ParamName(0))
Next
'Set Primary Keys
MDB0Table.PrimaryKey = New DataColumn() {MDB0Table.Columns("NamePar")}
MDB1Table.PrimaryKey = New DataColumn() {MDB1Table.Columns("NamePar")}
MDB2Table.PrimaryKey = New DataColumn() {MDB2Table.Columns("NamePar")}
CSVTable.PrimaryKey = New DataColumn() {CSVTable.Columns("NamePar")}
'Merge all 3 MDB Tables into 1
CRLTable = MDB0Table
CRLTable.Merge(MDB1Table)
CRLTable.Merge(MDB2Table)
'For each row in the merged table, either mark for removal or add the description from the CSV file
CRLTable.Columns.Add("Desc", GetType(String))
CRLTable.Columns("Desc").SetOrdinal(1) 'Make Description second column (after "NamePar")
For Each CRLRow In CRLTable.Rows
CSVRow = CSVTable.Rows.Find(CRLRow("NamePar"))
If CSVRow Is Nothing Then
removeRows.Add(CRLRow("NamePar")) 'tag rows to be removed due to missing in CSV
Else
CRLRow("Desc") = CSVRow("DESC_" & CRLForm.ComboBoxLanguage.Text)
End If
Next
'Remove Rows tagged for removal due to absence from CSV file
For Each row In removeRows
CRLTable.Rows.Remove(CRLTable.Rows.Find(row))
Next
'Set DataGridView data source as the cleaned merged table
CRLForm.DataGridView1.SelectionMode = DataGridViewSelectionMode.RowHeaderSelect ' Otherwise DataSource = CRLTable refuses to populate
CRLForm.DataGridView1.DataSource = CRLTable
CRLForm.DataGridView1.SelectionMode = DataGridViewSelectionMode.ColumnHeaderSelect
End Sub

Unique data consolidation across multiple Worksheets

I have six worksheets which I want to take the unique id's from a specific column and consolidate them into 1 master (in order to do some analysis and different data representation).
The data all starts from the same cell C17, but ends at different rows (ie C180, C268, etc). I want to be able to consolidate the unique ids from all six spreadsheets weekly.
Is there a solution that will not use array formulas as that SERIOUSLY causes a problem due to the sheer number of rows and resources needed to calculate the list. VBA automation is preferred where the cell ranges for consolidation can be dynamic and the sheet names are referenced from specific cells in the master sheet (it will never be deleted or altered to the extent of the six others)?
So, I would run a macro which will consolidate all the data based off either a named range or specific cells with the sheet names & ranges in them (using indirect to use those strings) and paste that into a new range.
UDF's would be acceptable as well, I just do not want Excel to "freeze" doing calculations.
BTW, I did read Getting unique values in Excel by using formulas only but those solutions only work if the data is on the same sheet or under very specific conditions. Also the array formulas would not work efficiently since my data is literally thousands of rows long.
Edit:
Here's a test macro I used to get data from one sheet, but the problem is I can't use dynamics nor add the rest of the names from the other sheets because the range I copy it to I can't pull the first blank cell after the id's copied.
Sub ConsolidateDATA()
'yStr = Evaluate("=ADDRESS(MIN(IF($C$10:$C$9999 = "", ROW($C$10:$C$9999))), 3, 1, 1)")
'Attempted dynamic range copy ^ - failed
yStr = "C10"
Range("Sheet1!$B$5:$B$29").AdvancedFilter Action:=xlFilterCopy, CriteriaRange:="", CopyToRange:=Range(yStr), Unique:=True
End Sub
I have also had successful attempts with array formulas, but unfortunately they are resource intensive that they are REALLY bad solutions.
-- Array formula to combine lists into 1 master
=IFERROR(INDEX(INDIRECT($B$6, TRUE), ROWS(B$13:$B14)), IFERROR(INDEX(INDIRECT($B$7, TRUE), ROWS(B$13:$B14) - ROWS(INDIRECT($B$6, TRUE))), IFERROR(INDEX(INDIRECT($B$8, TRUE), ROWS(B$13:$B14) - ROWS(INDIRECT($B$6, TRUE)) - ROWS(INDIRECT($B$7))), IFERROR(INDEX(INDIRECT($B$9, TRUE), ROWS(B$13:$B14) - ROWS(INDIRECT($B$6, TRUE)) - ROWS(INDIRECT($B$7)) - ROWS(INDIRECT($B$8))), IFERROR(INDEX(INDIRECT($B$10, TRUE), ROWS(B$13:$B14) - ROWS(INDIRECT($B$6, TRUE)) - ROWS(INDIRECT($B$7)) - ROWS(INDIRECT($B$8)) - ROWS(INDIRECT($B$9, TRUE))), IFERROR(INDEX(INDIRECT($B$11, TRUE), ROWS(B$13:$B14) - ROWS(INDIRECT($B$6, TRUE)) - ROWS(INDIRECT($B$7)) - ROWS(INDIRECT($B$8)) - ROWS(INDIRECT($B$9, TRUE)) - ROWS(INDIRECT($B$10, TRUE))),IFERROR(INDEX(INDIRECT($B$12, TRUE), ROWS(B$13:$B14) - ROWS(INDIRECT($B$6, TRUE)) - ROWS(INDIRECT($B$7)) - ROWS(INDIRECT($B$8)) - ROWS(INDIRECT($B$9, TRUE)) - ROWS(INDIRECT($B$10, TRUE)) - ROWS(INDIRECT($B$11, TRUE))),"")))))))
-- Array formula to get just unique data
=INDEX(TotalNameListRangeFromFormulaAbove, MATCH(0, COUNTIF($D$16:D16, TotalNameListRangeFromFormulaAbove), 0))
I think a combination of loops and collections might solve your problem :)
http://excelmacromastery.com/Blog/index.php/the-complete-guide-to-collections-in-excel-vba/
for i = 1 to UBound(worksheetcount, 1)
for j = 1 to UBound(cellrangecount, 1)
With CreateObject("scripting.dictionary")
For Each "Key" In cellrangecount(cellrangecount)
If Not .Exists(Key) Then .Add Key, Key & "_content"
Next j
next i
End With
I believe this is good enough to get you started on the correct path. I ended up using a dictionary instead of a collection but you can change that if you'd like. Only minor differences on declarations and adding but essentially the same (anecdotally speaking, there are a couple of huge differences, not that I think it matters here). Give me some time and I'll return with something more polished/finished than just the "basic idea of how it might work."
link for dictionaries from the same guy (i really love the way this guy elaborates on stuff)
http://excelmacromastery.com/Blog/index.php/vba-dictionary/

Resources