Create table with variable number of columns - vba

I am hoping to get a few ideas for something to push me in the right direction. I have a custom class that stores data from a table based on criteria. The raw data (consisting of over 100 columns and varying between 10-1000 rows) is on a worksheet. My code does the following:
1 - Creates an object from the custom class
2 - Adds a value to the properties of the object
3 - Adds the object to a collection
4 - Returns the collection to the controller which sends it to the view to build the table
The following will build a collection of column ranges from the raw data, at least:
Private mcolColumnAddresses As Collection
Private Sub Class_Initialize()
Set mcolColumnAddresses = New Collection
Dim vHeader As Variant
For Each vHeader In mwksReport.Range(mwksReport.Cells(1, 1), mwksReport.Cells(1, mlLastColumn))
mcolColumnAddresses.Add vHeader.Offset(1, 0).Resize(mlLastRow - 1), vHeader.value
Next vHeader
End Sub
The end users want the ability to choose the columns they want for building the new table. But a typical class for a table would use a row as an object with the column headers as the properties. How would I build a table using class properties when the columns are not known until run-time? I hope that makes sense.
Note: I am not asking for code but for suggestions. Has anybody else had this requirement? If so, how did you approach it? An example is welcome, too.

But a typical class for a table would use a row as an object with the column headers as the properties
If your table has really more than 100 columns, or if the column names are only known a runtime, you should probably approach this different. One object per row is fine, but your class could provide a method for accessing all column values by their name. In VBA syntax:
Function GetValue(byval columnName as string) as Variant
'...
As you see, you have to sacrifice some type safety here, but that is typically a small price to pay for getting this solved in a sensible manner.
Internally, your objects can store the values in some Dictionary (in VBA available through the MS Scripting Runtime), indexed by the column names. This leads to
Function GetValue(byval columnName as string) as Variant
if valueDict.ContainsKey(columnName) then
GetValue = valueDict(columnName)
else
'... add some error handling here
end if
End Function
For populating the dictionary, any database has possibilities to determine column names for a table, just google for " get column names programmatically" to find some example code.

Related

How to get specific index of a Dictionary?

I'm working on a code that returns a query result like MySqlCommand, all working well but what I'm trying to do is insert the result inside a ComboBox. The way for achieve this is the following:
Form load event execute the GetAvailableCategories function
The function executed download all the values and insert it into a dictionary
Now the dictionary returned need an iteration for each Items to insert in the ComboBox
Practice example:
1,3. Event that fire the function
Private Sub Service_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For Each categoria In Categories.GetAvailableCategories()
service_category.Items.Add(categoria)
Next
End Sub
GetAvailableCategories function
Dim dic As New Dictionary(Of Integer, String)
For Each row In table.Rows
dic.Add(row(0), row(1))
Next
Return dic
How you can see in the 1,3 points I call the function that return the result. What I want to do is insert the row(0) as value of the item and row(1) as Item name. But Actually I get this result in the ComboBox:
[1, Hair cut]
and also I can't access to a specific position of the current item in the iteration. Maybe the dictionary isn't a good choice for this operation?
Sorry if the question could be stupid, but it's a long time since I don't program in vb.net and now I need to brush up a bit.
UPDATE
I've understood that I can assign the value access to the .key of my dictionary, so the result that I want achieve is correct if I do:
cateogoria.key (return the id of record taken from the db)
categoria.value (is the item name that'll display in the ComboBox)
now the problem's that: How to assign the value of the current item without create any other new class? For example:
service_category.Items.Add(categoria.key, categoria.value)
But I can't do this, any idea?
A List as a DataSource sounds like what you are really after. Relying on relative indices in different arrays is sort of flaky. There is not a lot about what these are, but a class would keep the related info together:
Public Class Service
Public Property Name As String
Public Property Category As String
Public Property Id As Int32
End Class
This will keep the different bits of information together. Use them to store the info read from the db and use a List to store all of them:\
Private Services As New List(of Service)
...
For Each row In table.Rows
Dim s As New Service
s.Name = row(0).ToString() '???
s.Category =...
s.Id = ...
Services.Add(s) ' add this item to list
Next
Finally, bind the List to the CBO:
myCbo.DataSource = Services
myCbo.DisplayMember = "Name" ' what to show in cbo
myCbo.ValueMember = "Id" ' what to use for SelectedValue
I dont really know what you want to show or what the db fields read are, so I am guessing. But the larger point is that a Class will keep the different bits of info together better than an array. The List can be the DataSource so that you dont even have to populate the CBO directly. The List can also be Sorted, searched, Filtered and so forth with linq.
When the user picks something, myCbo.SelectedItem should be that item (though it will need to be cast), or you can use SelectedIndex to find it in the list:
thisOne = Services(myCbo.SelectedIndex)
It is also usually a good idea to override ToString in the item/service class. This will determine what shows when a DisplayMember mapping is not available. Without this, WindowsApp2.Service might show for your items:
Public Overrides ToString() As String
Return String.Format("{0} ({1})", Name, Price)
End Sub
This would show something like
Haircut ($12.30)

How do I assign an Excel VBA variable to a Defined Name

How do I return a value from a VBA Function variable to a Defined Name in Excel 2010?
In the example below, I want i to be returned to Excel as a Defined Name Value_Count that can be reused in other formulas, such that if MsgBox shows 7, then typing =Value_Count in a cell would also return 7.
Everything else below is about what I've tried, and why I'd like to do it. If it's inadvisable, I'd be happy to know why, and if there's a better method.
Function process_control_F(raw_data As Variant)
Dim i As Integer
i = 0
For Each cell In raw_data
i = i + 1
Next cell
MsgBox i
End Function
My goal is to have the value returned by the MsgBox be returned instead to a Defined Name that can be reused in other forumulas. However, I cannot get the value to show. I have tried a variety of forms (too numerous to recall, let alone type here) similar to
Names.Add Name:="Value_Count", RefersTo:=i
I am trying to accomplish this without returning a ton of extra info to cells, just to recall it, hence the desire to return straight to a Defined Name.
I'm using a Function rather than Sub to streamline my use, but if that's the problem, I can definitely change types.
I am creating a version of a Statistical Control Chart. My desired end result is to capture a data range (generally about 336 values) and apply a series of control rules to them (via VBA), then return any values that fall outside of the control parameters to Defined Names that can then be charted or otherwise manipulated.
I've seen (and have versions of) spreadsheets that accomplish this with digital acres of helper columns leading to a chart and summary statistics. I'm trying to accomplish it mostly in the background of VBA, to be called via Defined Names to Charts — I just can't get the values from VBA to the Charts.
The interest in using a Function rather than a Sub was to streamline access to it. I'd rather not design a user interface (or use one), if I can just keystroke the function into a cell and access the results directly. However, as pointed out by Jean-François Corbett, this is quickly turning into a circuitous route to my goal. However, I still think it is worthwhile, because in the long-term I have a lot of iterations of this analysis to perform, so some setup time is worth it for future time savings.
With minor changes to your function, you can use its return value to accomplish what you want:
Function process_control_F(raw_data As Variant) As Integer ' <~~ explicit return type
Dim i As Integer
Dim cell As Variant ' <~~~~ declare variable "cell"
i = 0
For Each cell In raw_data
i = i + 1
Next cell
process_control_F = i ' <~~~~ returns the value i
End Function
You can then use that function in formulas. For example:

Defining objects from data in an excel table

So I have data that is supplier by country where the supplier information changes by country.
600 suppliers and will be upwards of 35 countries(15 attributes per country per supplier). The excel data sheet looks similar to this:
SupplierID SupplierName USsupplierCategory USsupplierCategoryCode UKSupplierCategory ...
1 Sup1 Beverages 1 Ropes
3 Sup5 Ladders 46 Small Ladders
If I could figure out a simple way to get this excel data into an array(even copying and pasting works if the formatting goes quickly since I only have to do this once a month at the most) I can then loop through it an build the needed objects off of the array. But I can't find a simple way to build an array with the excel data without going through a lot of formatting for an array assignment.
I'm pretty new to VB.net and still an amateur programmer and I just can't seem to envision a simple solution to this.
Is an array the way to go? Should I instead loop through each row as a string and break the data by tabs and assign the data that way?
If I am too vague I apologize, let me know and I will provide more specific detail and some code if needed.
The high level approach I'd use is as follows:
1) Define your supplier object
2) Create a supplier object for each row of your datatable and add it to a collection, e.g. a List of supplier objects
Once you have done this, you can easily iterate through your collection of supplier objects to do whatever you like.
Detailed approach:
1) Define your supplier object:
Public Class Supplier
Public property SupplierID as integer
Public Property SupplierName as string
Public Property USsupplierCategory as string
Public property USsuppliercategorycode as integer
Public property UKSupplierCategory as string
End Class
2) Read your excel object into a datatable
This gets a bit tricker. As Tim said, it is easier to save your excel files as csv and then read them in. This is because it is easy to read in csv, but it is also complicated to read in excel files. They can be either xls or xlsx format, and deciding on which one can be a problem. We can also get into COM object issues etc.
If you are to stick with reading in excel, I recommend using Linq2Excel, as this library handles the xls / xlsx issue for you. Here is some example code I knocked up based on your example spreadsheet that returns a list of supplier objects:
Imports LinqToExcel
Imports Remotion.Data.Linq
Imports System.Data
Imports System.Linq
Public Class ReadInExcelData
Public Shared Function GetSupplierListFromSupplierExcel() As List(Of Supplier)
Dim excel As ExcelQueryFactory = New ExcelQueryFactory("C:\Users\YourUserName\Desktop\ExampleData.xls")
Dim suppliers = From c In excel.Worksheet
Select c
Dim list As New List(Of Supplier)
For Each supplier In suppliers
list.Add(New Supplier(supplier.Item(0),
supplier.Item(1),
supplier.Item(2),
supplier.Item(3),
supplier.Item(4)))
Next
Return list
End Function
End Class
You can get the linqtoexcel library here: http://code.google.com/p/linqtoexcel/

Dynamically create variables in VB.NET

I have been trying to figure this out for some time now and can't seem to figure out an answer to it. I don't see why this would be impossible. I am coding in VB.NET.
Here is my problem:
I need to dynamically create variables and be able to reference them later on in the code.
More Details:
The number of variables comes from some math run against user defined values. In this specific case I would like to just create integers, although I foresee down the road needing to be able to do this with any type of variable. It seems that my biggest problem is being able to name them in a unique way so that I would be able to reference them later on.
Simple Example:
Let's say I have a value of 10, of which I need to make variables for. I would like to run a loop to create these 10 integers. Later on in the code I will be referencing these 10 integers.
It seems simple to me, and yet I can't figure it out. Any help would be greatly appreciated. Thanks in advance.
The best way to do something like this is with the Dictionary(T) class. It is generic, so you can use it to store any type of objects. It allows you to easily store and retrieve code/value pairs. In your case, the "key" would be the variable name and the "value" would be the variable value. So for instance:
Dim variables As New Dictionary(Of String, Integer)()
variables("MyDynamicVariable") = 10 ' Set the value of the "variable"
Dim value As Integer = variables("MyDynamicVariable") ' Retrieve the value of the variable
You want to use a List
Dim Numbers As New List(Of Integer)
For i As Integer = 0 To 9
Numbers.Add(0)
Next
The idea of creating a bunch of named variables on the fly is not something you are likely to see in any VB.Net program. If you have multiple items, you just store them in a list, array, or some other type of collection.
'Dim an Array
Dim xCount as Integer
Dim myVar(xCount) as String
AddButton Event . . .
xCount += 1
myVar(xCount) = "String Value"
'You will have to keep Track of what xCount Value is equal to to use.
'Typically could be an ID in A DataTable, with a string Meaning

Setting the Item property of a Collection in VBA

I'm surprised at how hard this has been to do but I imagine it's a quick fix so I will ask here (searched google and documentation but neither helped). I have some code that adds items to a collection using keys. When I come across a key that already exists in the collection, I simply want to set it by adding a number to the current value.
Here is the code:
If CollectionItemExists(aKey, aColl) Then 'If key already has a value
'add value to existing item
aColl(aKey).Item = aColl(aKey) + someValue
Else
'add a new item to the collection (aka a new key/value pair)
mwTable_ISO_DA.Add someValue, aKey
End If
The first time I add the key/value pair into the collection, I am adding an integer as the value. When I come across the key again, I try to add another integer to the value, but this doesn't work. I don't think the problem lies in any kind of object mis-match or something similar. The error message I currently get is
Runtime Error 424: Object Required
You can't edit values once they've been added to a collection. So this is not possible:
aColl.Item(aKey) = aColl.Item(aKey) + someValue
Instead, you can take the object out of the collection, edit its value, and add it back.
temp = aColl.Item(aKey)
aColl.Remove aKey
aColl.Add temp + someValue, aKey
This is a bit tedious, but place these three lines in a Sub and you're all set.
Collections are more friendly when they are used as containers for objects (as opposed to containers for "primitive" variables like integer, double, etc.). You can't change the object reference contained in the collection, but you can manipulate the object attached to that reference.
On a side note, I think you've misunderstood the syntax related to Item. You can't say: aColl(aKey).Item. The right syntax is aColl.Item(aKey), or, for short, aColl(aKey) since Item is the default method of the Collection object. However, I prefer to use the full, explicit form...
Dictionaries are more versatile and more time efficient than Collections. If you went this route you could run an simple Exists test on the Dictionary directly below, and then update the key value
Patrick Matthews has written an excellent article on dictionaries v collections
Sub Test()
Dim MyDict
Set MyDict = CreateObject("scripting.dictionary")
MyDict.Add "apples", 10
If MyDict.exists("apples") Then MyDict.Item("apples") = MyDict.Item("apples") + 20
MsgBox MyDict.Item("apples")
End Sub
I think you need to remove the existing key-value pair and then add the key to the collection again but with the new value