Create reference table or array inside of the code - arrays

I am trying to create a function that allows me to put in a location number and the result will give me a unique location code. The problem is I want all of the referencing done inside of the macro code, not to get the information from somewhere in the spreadsheet. (this code is going into an add-in so there is no worksheet to reference from). I basically want to do a vlookup but inside of the code, not in a worksheet.
I haven't been able to find out how to do this, the code below is something like what I am looking for, I am thinking maybe the use of an array but I can't figure out how to use it the way I want.
I know this doesn't work but I am trying to do something like this below so that when I type in =GetCode(415) the result is 001
Function GetCode(LocationNum As String) As String
Dim Result As String
'Built in reference table
'
'{ "415" : "001"
' "500" : "002"
' "605" : "003"
' }
Dim varData(2) As Variant
varData("415") = "001"
varData("500") = "002"
varData("605") = "003"
Result = varData(LocationNum)
GetCode = Result
End Function

As Nathan_Sav has already mentioned, you can use a collection or dictionary instead, which are much more efficient. Here's an example using the dictionary object. Note that it uses early binding, so you'll need to set a reference to the Microsoft Scripting Runtime library (Visual Basic Editor >> Tools >> Reference).
Option Explicit
Sub test()
'set a reference (VBE >> Tools >> Reference) to the Microsoft Scripting Runtime library
'declare and create an instance of the dictionary object
Dim dic As Scripting.Dictionary
Set dic = New Scripting.Dictionary
'set the comparison mode for the dictionary to a case-insensitive match
dic.CompareMode = TextCompare
'add keys and associated items to the dictionary
dic.Add Key:="415", Item:="001"
dic.Add Key:="500", Item:="002"
dic.Add Key:="605", Item:="003"
'print to the immediate window the item associated with the specified key
Debug.Print dic("415")
'clear from memory
Set dic = Nothing
End Sub

Related

Can't copy a value from one worksheet over to an array in another worksheet

In the same workbook, I've got two worksheets: Model and Results.
My goal is to copy the value of a cell in Model (for e.g., F8) over to a cell in an array (c4 to I23) in Results called ResultsArray (see code below).
When I run my module, no error appears, but the code doesnt seem to work either (the value of F8 doesnt get copied over to the specified cell in ResultsArray).
Appreciate any help.
Tried running different variations of the code below
Sub CopyTest()
Dim ResultsArray As Variant
ResultsArray = Worksheets("Results").Range("C4:I23")
ResultsArray(1, 1) = Worksheets("Model").Range("F8").Value
End Sub
I'm using ResultsArray(1,1) because I am hoping to introduce a loop into the code to populate cells in the array based on the loop counter, e.g., ResultsArray(loopcounter,1)
So turns out I just needed to add "Set" in the 2nd line before "ResultsArray" when assigning the range from the worksheet "Model" to it:
Sub CopyTest()
Dim ResultsArray As Variant
Set ResultsArray = Worksheets("Results").Range("C4:I23")
ResultsArray(1, 1) = Worksheets("Model").Range("F8").Value
End Sub
I've tested this addition and it works

How to parse and address a json matrix array in Excel-VBA?

I need to parse JSON text to a JSON object in Excel-VBA. The JSON text includes a matrix/array. Then I need to address it (set a VBA variable to the value).
My code had been working parsing a nested/keyed JSON text with "JsonConverter.parseJSON" method. But I do not know how to address new array object (or technically if the "parse" is working correctly.
Dim jsonResults As String
Dim jsonObj As Dictionary
Set travelDist As Number
Set jsonResults = '{"distances":[[0,97641],[97415,0]],"times":[[0,4189],[4183,0]],"weights":[[0.0,5653.726],[5644.176,0.0]],"info":{"copyrights":["GraphHopper","OpenStreetMap contributors"]}}'
Set jsonObj = JsonConverter.ParseJson(jsonResults) \This worked with the old JSON text keyed value structure.
travelDist = VBA.Val(jsonObj.Item("distances")(1)) \This DOESN'T work. It worked with Keyed Object Values. The goal is to set travelDist to in this example, 97641.
The current code seems to have a type mismatch.
The goal is to set a VBA variable to 97641. Please let me know how to include required files/definitions etc. if the solution is including additional types or methods.
There's no reason to declare jsonObj as a dictionary and unless Number is a well defined user-defined type of some sort, I don't think declaring travelDist as Number will work. Use Double instead. And always use Option Explicit on the very top.
Also the double quotes in the JSON string need to be escaped somehow. You can either double them:
jsonResults = "{""distances"":[[0,97641],[97415,0]],""times"":[[0,4189],[4183,0]],""weights"":[[0.0,5653.726],[5644.176,0.0]],""info"":{""copyrights"":[""GraphHopper"",""OpenStreetMap contributors""]}}"
or replace them with single quotes:
jsonResults = "{'distances':[[0,97641],[97415,0]],'times':[[0,4189],[4183,0]],'weights':[[0.0,5653.726],[5644.176,0.0]],'info':{'copyrights':['GraphHopper','OpenStreetMap contributors']}}"
or you can just store the string in a cell in one of your worksheets and load it from there:
Dim sht As Worksheet
Set sht = ThisWorkbook.Worksheets("Name of your Worksheet")
Set jsonObj = JsonConverter.ParseJson(sht.Range("A1"))
Visualizing the JSON structure might help you understand it better:
So basically what you need to do is access the 2nd item, of the 1st item, of the distances array/collection, keeping in mind that the 1st item of the distances array is also an array/collection itself.
The way to do this would be the following:
Option Explicit
Sub test()
Dim jsonObj As Object
Dim jsonResults As String
Dim travelDist As Double
jsonResults = "{""distances"":[[0,97641],[97415,0]],""times"":[[0,4189],[4183,0]],""weights"":[[0.0,5653.726],[5644.176,0.0]],""info"":{""copyrights"":[""GraphHopper"",""OpenStreetMap contributors""]}}"
Set jsonObj = JsonConverter.ParseJson(jsonResults)
travelDist = jsonObj("distances")(1)(2)
Debug.Print travelDist 'the result is printed in the immediate window
End Sub
Finally, I assume that since you've used this before, you know you need to add this JSON parser to your project, as well as a reference to Microsoft Scripting Runtime (VBE>Tools>References>...)

Visio VBA - How can I distribute shapes with a known, fixed distance

I'd like to place all currently selected shapes into an array. I'd then like to sort that array so I can find either the top most or left most shape in the array. I'd then like to use that shape as my starting point, and then from there align the other shapes a fixed, known distance apart. I've tried to place the shapes into an array like so:
Dim numShapes As Integer, i As Integer
Dim arrShapes As Visio.Selection
numShapes = Visio.ActiveWindow.Selection.Count
For i = 1 To numShapes
arrShapes(i) = Visio.ActiveWindow.Selection(i)
Next i
I have tried to create the array with no type specification, specifying as variant, and as in this example as selection. I don't know if I can put them into a list of some kind either? Obviously I can't get to the point of sorting the array and then distributing my shapes until I can get the array to populate. I'm placing a break point in the code and I have the "Locals" window open and I can see that the array is not being populated.
Update:
Why does this work,
Dim Sel As Visio.Selection
Dim Shp As Visio.Shape
Set Sel = Visio.ActiveWindow.Selection
For Each Shp in Sel
Debug.Print Shp.Name
Next
And this does not?
Dim i As Integer
Dim Shp As Visio.Shape
For i = 1 To Visio.ActiveWindow.Selection.Count
Set Shp = Visio.ActiveWindow.Selection(i)
Debug.Print Shp.Name
Next i
Regards,
Scott
There was a couple of problems in your code - fixing only one would not have got you any further in understanding if you had actually fixed anything.
Your arrShapes is declared as a general object - the Selection
Object is one of those objects that is the Jack of all trades, and
master of none.
You didn't "Set" when assigning to the array.
I don't have Visio on this machine, so cannot directly test the code below. I am also assuming that all items selected are shapes (usually a safe assumption in Visio).
Dim numShapes As Integer, i As Integer
Dim arrShapes() As Shape ' Set this up as an array of shape
If Visio.ActiveWindow.Selection.Count > 0 then ' don't want to cause a problem by setting the array to 0!
ReDim arrShapes(Visio.ActiveWindow.Selection.Count)
numShapes = Visio.ActiveWindow.Selection.Count ' while not really necessary it does help explain the code.
For i = 1 To numShapes
' must Set as we want the reference to the shape, not the default value of the shape.
Set arrShapes(i) = Visio.ActiveWindow.Selection(i)
Next i
Else
MsgBox "No shapes selected. Nothing done." ' soft fail
End If

Excel VBA - How to search array and return value?

I have some data that looks like this (starting in A1):
Batman
Carl Reiner
Ford
Sushi
But I have a two dimensional array, storing those names, and their type:
Dim Names()
ReDim Names(4,1)
Names(0,0) = "Batman"
Names(0,1) = "Superhero"
Names(1,0) = "Carl Reiner"
Names(1,1) = "Comedian"
Names(2,0) = "Ford"
Names(2,1) = "Car Manufacturer"
Names(3,0) = "Sushi"
Names(3,1) = "Food"
Now, I would like to loop through column A and replace those data points with their equivalent type. I thought there would be a relatively simple way to essentially search Names(x,0) for "Batman" then just replace it with Names(x,1). What's the best way to do so?
I found somewhere that you could possibly use Vlookup with this, so I tried this, to no avail:
Evaluate(WorksheetFunction.Vlookup("A1",Names,1,False)
With my thought being it would look for "Batman" and return the value in Names in the "1" dimension...But that keeps giving me an error (Type 13 Mismatch).
Thanks for any ideas/help!
edit: I found this post and seem to be getting somewhere with Application.Match("A1",Application.Index(Names,0,1),0) which returns the position of "A1" in the array. Now I just need to return the offset and get the "1" position of that...
Edit 2: I think I got it. Using MATCH to find the position of "A1" then just use the array to find it:
Names(Application.Match("A1",Application.Index(Names,0,1),0),1)
So, when A1 is Batman, it finds that position in the index, then it finds the equivalent return in the second dimension.
(Sorry for the lack of semantics, I don't have a very good idea of how to refer to this, so please let me know if I can clarify).
It seems like this would be easier using a Dictionary.
Sub testing()
Dim dic As Object
Set dic = CreateObject("scripting.dictionary")
With dic
.Add "Batman", "Superhero"
.Add "Carl Reiner", "Comedian"
.Add "Ford", "Car Manufacturer"
End With
MsgBox "Batman is a " & dic.Item("Batman")
End Sub
I think if you want to use worksheet functions, you have to put the data into a worksheet (e.g. a column or pair of columns) instead of an array. This is pretty easy to do, though, and you can use a different worksheet, or even another workbook, and there are solutions to hide these, so your users don't have to see that data cluttering the other worksheets.

How to fill-up cells within a Excel worksheet from a VBA function?

I simply want to fill-up cells in my spreadsheet from a VBA function. By example, I would like to type =FillHere() in a cell, and in result I will have a few cells filled-up with some data.
I tried with such a function:
Function FillHere()
Dim rngCaller As Range
Set rngCaller = Application.Caller
rngCaller.Cells(1, 1) = "HELLO"
rngCaller.Cells(1, 2) = "WORLD"
End Function
It breaks as soon as I try to modify the range. Then I tried this (even it's not really the behavior I'm looking for):
Function FillHere()
Dim rngCaller As Range
Cells(1, 1) = "HELLO"
Cells(1, 2) = "WORLD"
End Function
This is not working neither. But it works if I start this function from VBA using F5! It seems it's not possible to modify anything on the spreadsheet while calling a function... some libraries do that though...
I also tried (in fact it was my first idea) to return a array from the function. The problem is that I only get the first element in the array (there is a trick that implies to select a whole area with the formula at the top left corner + F2 + CTRL-SHIFT-ENTER, but that means the user needs to know by advance the size of the array).
I'm really stuck with this problem. I'm not the final end-user so I need something very easy to use, with, preferably, no argument at all.
PS: I'm sorry I asked this question already, but I wasn't registered at that time and it seems that I can't participate to the other thread anymore.
You will need to do this in two steps:
Change your module to be something like:
Dim lastCall As Variant
Dim lastOutput() As Variant
Function FillHere()
Dim outputArray() As Variant
ReDim outputArray(1 To 1, 1 To 2)
outputArray(1, 1) = "HELLO"
outputArray(1, 2) = "WORLD"
lastOutput = outputArray
Set lastCall = Application.Caller
FillHere = outputArray(1, 1)
End Function
Public Sub WriteBack()
If IsEmpty(lastCall) Then Exit Sub
If lastCall Is Nothing Then Exit Sub
For i = 1 To UBound(lastOutput, 1)
For j = 1 To UBound(lastOutput, 2)
If (i <> 1 Or j <> 1) Then
lastCall.Cells(i, j).Value = lastOutput(i, j)
End If
Next
Next
Set lastCall = Nothing
End Sub
Then in order to call the Sub go into the ThisWorkbook area in VBA and add something like:
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
Call WriteBack
End Sub
What this does is return the value of the topleft cell and then after calculation completes populates the rest. The way I wrote this it assumes only one FillHere function will be called at a time. If you want to have multiple ones which recalculate at the same time then you will need a more complicated set of global variables.
One word of warning is that this will not care what it overwrites when it populates the other cells.
Edit:
If you want to do this on a Application wide basis in an XLA. The code for the ThisWorkbook area should be something like:
Private WithEvents App As Application
Private Sub App_SheetCalculate(ByVal Sh As Object)
Call WriteBack
End Sub
Private Sub Workbook_Open()
Set App = Application
End Sub
This will wire up the Application Level calculation.
What you're trying to do won't work in Excel - this is by design.
You can do this, though:
Function FillHere()
Redim outputArray(1 To 1, 1 To 2)
outputArray(1, 1) = "HELLO"
outputArray(1, 2) = "WORLD"
FillHere = outputArray
End Function
If you then select two adjacent cells in your worksheet, enter =FillHere() and press Control+Shift+Enter (to apply as an array formula) then you should see your desired output.
Fundamentally, a function can only affect the cell it is called from. It sounds like you may need to look at using the Worksheet_Change or Worksheet_SelectionChange events to trigger the modification of cells in the intended range.
You can do this indirectly using a 2-stage process:
Write your UDF so that it stores data in a sufficiently persistent way (for example global arrrays).
then have an Addin that contains application events that fire after each calculation event, looks at any data stored by the UDFs and then rewrites the neccessary cells (with warning messages about overwrite if appropriate) and reset the stored data.
This way the user does not need to have any code in their workbook.
I think (but do not know for sure) that this is the technique used by Bloomberg etc.

Resources