I am providing a multi-dimensional array to a macro in Word. Each element is an array, and each array element has 2 values, the name of an image and a short description. The macro is:
For i = LBound(figures, 1) To UBound(figures, 1)
cgmImage = "C:\path\to\images\" & figures(i, 0) & ".jpeg"
Selection.InlineShapes.AddPicture FileName:=cgmImage, LinkToFile:=False, SaveWithDocument:=True
Selection.TypeParagraph
With Selection
.Font.Size = 14
.Font.Bold = True
.Font.Underline = wdUnderlineSingle
.TypeText Text:=figures(i, 1)
End With
Next i
Sample input array would be:
[
['123','image 1'],
['456','image 2']
]
The macro works, inserting each image and its description. However I now want to add a third element, this element will itself be a 2D array representing a table. So input will look something like this:
[
[ '123','image 1', [['val1','val2'],['val3','val4']] ],
[ '456','image 2', [['val1','val2'],['val3','val4']] ]
]
In the macro I'll use this 3rd element to create a table. However, if I give input data like that into my original macro I now get a 'subscript out of range' error on this line:
cgmImage = "C:\path\to\images\" & figures(i, 0) & ".jpeg"
All I want to iterate is the first level of this whole data structure (e.g. for the sample data I provided above, 2 iterations). Even if I provide the 'dimension' attribute to LBound and UBound I still get the error, which shouldn't happen since the first element of the top level array elements are always just a string (the image file name). How can I achieve this? And will accessing the third element representing the table be along the lines of :figures(i,2,X,X)?
EDIT:
Array creation is in Python, just a standard list type. Calling of the macro is done using the mhammond pywin32 module here's a simplified version of the technique:
import win32com.client as win32
word = win32.gencache.EnsureDispatch("Word.Application")
template = word.Documents.Open("\path\to\file.docm")
word.Run("macroName",imagesArray)
Then I save and close it
if the inner most array is an array of array, then something like figures(i)(j)(k)(l) should be the way to go
you can use UBound(array,index) and LBound(array,index) functions to get the actual lower and upper array bounds of its index dimension
for example, along the lines of data you shown, you could consider the following code:
Option Explicit
Sub main()
Dim figures(1) As Variant
figures(0) = Array("123", "image 1", Array(Array("val1", "val2"), Array("val3", "val4"))) ' see how the innermost array is an array of arrays, too
figures(1) = Array("456", "image 2", Array(Array("val5", "val6"), Array("val7", "val8"))) ' see how the innermost array is an array of arrays, too
Dim i As Long, j As Long, k As Long, l As Long
For i = LBound(figures) To UBound(figures)
For j = LBound(figures(i)) To UBound(figures(i))
If IsArray(figures(i)(j)) Then
For k = LBound(figures(i)(j)) To UBound(figures(i)(j))
For l = LBound(figures(i)(j)(k)) To UBound(figures(i)(j)(k))
Debug.Print figures(i)(j)(k)(l)
Next
Next
Else
Debug.Print figures(i)(j)
End If
Next
Next
End Sub
Related
I am trying to add text as subscript in a Table Cell in a Word-Document using VBA.
I currently have this code, it is a part of the loop in which I insert my values into the table.
ActiveDocument.Tables(ActiveDocument.Tables.Count).Cell(i, j).Range.Font.Subscript = False
wordArray = Split(ws.Cells(i, j), "_")
For k = LBound(wordArray) To UBound(wordArray)
ActiveDocument.Tables(ActiveDocument.Tables.Count).Cell(i, j).Range.InsertAfter wordArray(k)
ActiveDocument.Tables(ActiveDocument.Tables.Count).Cell(i, j).Range.Font.Subscript = wdToggle
Next k
So I split the text that is in ws.Cells(i,j) on "_"
This can become an array of length 1,2 or 3
Only the second element of the array must be subscript.
My current code should do that, however, it writes the value into the cell based on the last value of Font.Subscript, so either fully normal or fully subscript.
So what I actually want in my table cell is the following
If the ws.Cells has for example a_b_c then b should be subscript and a and c normally written in the table cell. How do I need to change my code to accomplish that?
Since you haven't provided a mvce you'll need to adapt the following example to your needs - . It's main purpose is to demonstrate how to insert text and format it, which is done in the For loop.
Please note how to declare and instantiate a Table object - this is more reliable and more efficient than repeating ActiveDocument.Tables[index].
Use a Range object to write the data to the table cell. Important is "collapsing" the range so that the content is appended, rather than over-written. You need to write a separate range object for each formatting variation.
The code below checks whether the second member of the array is being written. If yes, it's formatted as subscript, if it's another member, then no subscript.
Sub CellContentWithSubscript()
Dim tbl As Word.Table
Dim rng As Word.Range
Dim wordArray '() As Variant
Dim data As String, k As Long
Set tbl = ActiveDocument.Tables(1)
data = "PartOne_Part two_Part three"
wordArray = Split(data, "_")
Set rng = tbl.Cell(1, 1).Range
rng.Collapse 0
rng.MoveEnd wdCharacter, -1
For k = LBound(wordArray) To UBound(wordArray)
rng.Text = wordArray(k)
If k = 1 Then
rng.Font.Subscript = True
Else
rng.Font.Subscript = False
End If
rng.Collapse 0
Next k
End Sub
If IsArray(payCsv(pay_id)) = False Then
'create tempArray
lc = 0
Debug.Print "create array"
End If
If IsArray(payCsv(pay_id)) = True Then
Debug.Print " array exists, we should be able to get ubound"
lc = UBound(payCsv(0)) - LBound(payCsv(0))
l = l + 1
End If
I am using the above code to determine whether I can use Ubound on my 2D array (i.e. if the 2nd dimension is created, get length (ubound - lbound).
However, I am getting a compile error, even though condition 2 is false, it does not recognise that the code will not be relevant.
I am testing one array and the result is if I comment out "lc = UBound(payCsv(0)) - LBound(payCsv(0))" is "create array".
If I leave this line in there, I get the error "compile error - expected array"
Is this a bug in VBA?
If you want to access the UBound of the 2nd dimension of an array, the format goes like this:
UBound(payCSV, 2)
The MSDN page on this function may be helpful.
When you access payCSV(0) as you currently are, the code assumes that you want the 1st element within the 1st dimension of the payCSV array.
Perhaps you might want to try this?
If IsArray(payCsv(pay_id)) = False Then
'create tempArray
lc = 0
Debug.Print "create array"
Else
Debug.Print " array exists, we should be able to get ubound"
lc = UBound(payCsv, 2) - LBound(payCsv, 2)
l = l + 1
End If
I am very new to VBA, so I apologize if this is a very simple question. I am trying to pass user input data into an array. Actually, 4 different arrays. All 4 arrays can have up to 3 elements, but could only need one at any given time. They are then sorted a specific way via For Loops and then will output the sendkeys function to the active window (which will not be excel when it is running). I have the for loops figured out and it is sorting the way i need it to. I just need to be able to get the user input into those arrays and then output them to a phantom keyboard (i.e. sendkeys). I appreciate any help or advice!
FYI, I have declared the arrays as strings and the variables as long... the message boxes are there to just test the sort, they are not very important
For i = 0 To UBound(SheetPosition)
If j = UBound(Position) Then
j = 0
End If
For j = 0 To UBound(Position)
If k = UBound(Direction) Then
k = 0
End If
For k = 0 To UBound(Direction)
If l = UBound(Temper) Then
l = 0
End If
For l = 0 To UBound(Temper)
MsgBox(i)
MsgBox(SheetPosition(i))
MsgBox(j)
MsgBox(Position(j))
MsgBox(k)
MsgBox(Direction(k))
MsgBox(l)
MsgBox(Temper(l))
Next
Next
Next
Next
you could use Application.InputBox() method in two ways:
Dim myArray As Variant
myArray = Application.InputBox("List the values in the following format: " & vbCrLf & "{val1, val2, val3, ...}", Type:=64) '<--| this returns an array of 'Variant's
myArray = Split(Application.InputBox("List the values in the following format: " & vbCrLf & "val1, val2, val3, ...", Type:=2), ",") '<--| this returns an array of 'String's
Yes, you could get the input from the user using Input boxes:
myValue = InputBox("Give me some input")
Or forms, which is the preferred method. Unfortunately, forms take some time to develop and are best deployed through Excel add-ins, which also require time to learn how to setup.
Here is a good tutorial on using the SendKeys method:
http://www.contextures.com/excelvbasendkeys.html
The usual way of getting data from cells into an array would be:
Dim SheetPosition As Variant
SheetPosition = Range("A1:A3").Value
or perhaps
Dim SheetPosition As Variant
SheetPosition = Range("A1:A" & Cells(Rows.Count, "A").End(xlUp).Row).Value
A few things to note:
The array needs to be dimensioned as a Variant.
The dimension of the array will be rows x columns, so in the first example above SheetPosition will be dimensioned 1 To 3, 1 To 1, and in the second example it might be dimensioned 1 To 5721, 1 To 1 (if the last non-empty cell in column A was A5721)
If you need to find the dimensions of a multi-dimensioned array, you should use UBound(SheetPosition, 1) to find the upper bound of the first dimension and UBound(SheetPosition, 2) to find the upper bound of the second dimension.
Even if you include Option Base 0 at the start of your code module, the arrays will still be dimensioned with a lower bound of 1.
If you want a single dimensioned array and your user input is in a column, you can use Application.Transpose to achieve this:
Dim SheetPosition As Variant
SheetPosition = Application.Transpose(Range("A1:A3").Value)
In this case SheetPosition will be dimensioned 1 To 3.
If you want a single dimensioned array and your user input is in a row, you can still use Application.Transpose to achieve this, but you have to use it twice:
Dim SheetPosition As Variant
SheetPosition = Application.Transpose(Application.Transpose(Range("A1:C1").Value))
FWIW - Your If statements in the code in the question are not achieving anything - each of the variables that are being set to 0 are going to be set to 0 by the following For statements anyway. So your existing code could be:
For i = LBound(SheetPosition) To UBound(SheetPosition)
For j = LBound(Position) To UBound(Position)
For k = LBound(Direction) To UBound(Direction)
For l = LBound(Temper) To UBound(Temper)
MsgBox i
MsgBox SheetPosition(i)
MsgBox j
MsgBox Position(j)
MsgBox k
MsgBox Direction(k)
MsgBox l
MsgBox Temper(l)
Next
Next
Next
Next
I am struggling with a common problem involving an apparent Excel 255-character-limit. I encounter an error when attempting to return a variant-array from a Function to the selected range on the worksheet. When each of the cells in the Function's returning array are under 255 characters, they post to the sheet just as they should: one element appears in each cell within the selected range. However, if any element in my returning variant array is longer than 255 characters I get a Value! error. These errors are bad because I need my long elements and want to keep the data together!
Versions of this problem appear over and over again in many forums, yet I am able to find a clear simple, all-purpose solution for returning variant arrays to the selected range (not necessarily containing formulas) when the array cells exceed 255 characters. My largest elements are around 1000, but it would be better if the solution could accommodate elements up to 2000 characters.
Preferably, I want this to be implemented with a function, or lines of additional code which can be added to my function (not a subroutine). My reason for wanting to avoid subroutines: I do not want to have to hard-code any ranges. I want this to be flexible and for the output location to be dynamically based on my current selection.
Please, if you can help find a way to produce a function, which takes a Variant Array as input, and which maintains the desired array:cell 1:1 relationship, I'd appreciate it greatly.
So this function with short cells works:
Function WriteUnder255Cells()
Dim myArray(3) As Variant 'this the variant array I will attempt to write
' Here I fill each element with less than 255 characters
' it should output them if you call the function properly.
myArray(0) = "dog"
myArray(1) = "cat"
myArray(2) = "bird"
myArray(3) = "fly"
WriteUnder255Cells = Application.Transpose(myArray())
End Function
But this fuction, with cells exceeding 255 will not output.
Function WriteOver255Cells()
Dim myArray(3) As Variant 'this the variant array I will attempt to write
' Here I fill each element with more than 255 characters
' exceeding the 255-character limit causes the VALUE! errors when you output them
myArray(0) = "ThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelaxydog"
myArray(1) = "ThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydog"
myArray(2) = "ThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydog"
myArray(3) = "ThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydog"
WriteOver255Cells = Application.Transpose(myArray())
End Function
This is how you produce the output and results:
First you need to create the two modules(to insert one function into each module, paste the code from one into the respective module). To run "WriteUnder255Cells()", select an area of 4 rows x 1 column on the sheet (this where you return the module) and type "=WriteUnder255Cells()" into the formula bar (do not enter the quotes). Note these are called like array formulas, so instead of hitting (enter) to create the output, you need to hit (control + shift + enter). Repeat the same process for WriteOver255Cells() to produce the errors.
Here are some documents/forums discussions which address it. The solutions seem to be either overly specific or clunky because they evoke subroutines (which I want to avoid):
https://support.microsoft.com/en-us/kb/213841
http://www.mrexcel.com/forum/excel-questions/852781-visual-basic-applications-evaluate-method-255-character-limit.html
Excel: Use formula longer that 255 characters
VBA code error when array value exceeds 255 characters
http://dailydoseofexcel.com/archives/2005/01/10/entering-long-array-formulas-in-vba/
https://forums.techguy.org/threads/solved-vba-access-to-excel-255-char-limit-issue.996495/
http://www.mrexcel.com/forum/excel-questions/494675-255-character-cell-limit-visual-basic-applications-workaround.html
Array formula with more than 255 characters
http://www.mrexcel.com/forum/excel-questions/388250-size-limit-transferring-variant-range-excel-2007-a.html
This works for me:
Function Over255()
Dim myArray(3) As String '<<<<< not variant
myArray(0) = String(300, "a")
myArray(1) = String(300, "b")
myArray(2) = String(300, "c")
myArray(3) = String(300, "d")
'Over255 = Application.Transpose(myArray())
Over255 = TR(myArray)
End Function
'like Application.Transpose...
Function TR(arrIn) As String()
Dim arrOut() As String, r As Long, ln As Long, i As Long
ln = (UBound(arrIn) - LBound(arrIn)) + 1
ReDim arrOut(1 To ln, 1 To 1)
i = 1
For r = LBound(arrIn) To UBound(arrIn)
arrOut(i, 1) = arrIn(r)
i = i + 1
Next r
TR = arrOut
End Function
Seems like you need to return a string array and Application.Transpose doesn't do that
Well I've been struggling with the little bit of code and can't seem to get around it ...
I'm trying to get an array from a range of cells, the array however is showing up to be 1 element wide.
Well here's the code:
Dim item As Variant
MsgBox Range("D19:H19").Count
item = Range("D19:H19").Value
MsgBox LBound(item) & " " & UBound(item)
as per my understanding item should contain a 2D array... however I'm getting the following result
1st MsgBox prints 5
2nd MsgBox prints 1 1
What's going wrong?
The problem is in LBound and UBound
jtolle was correct about the LBound and UBound.
LBound(item, 2)
UBound(item, 2)
However, item must not be dimmed as an array (you'll get an error).
I think this is what you want
Dim item As Variant
MsgBox Range("D19:H19").Count
item = Range("D19:H19").Value
MsgBox LBound(item, 2) & " " & UBound(item, 2)
For i = LBound(item, 2) To UBound(item, 2)
MsgBox item(1, i)
Next
Your item should contain a 2-D array as expected. If you stick a breakpoint in your code and look at the little "Locals" window in the VBA editor, you should see that. Your calls to LBound and UBound are getting the bounds in the first dimension. If you call Lbound(item,2) and UBound(item,2), you should get 1 and 5 as you expect.
EDIT:
That is, once you've made the assignment, item would look like something you could have declared as such:
Dim item(1 to 1, 1 to 5)
One of the banes of VBA programming is that arrays can have arbitrary lower bounds. So all of your code needs to be aware of that.
That's correct as is. Even if you select an array of cells, you still have the option to select one single cell out of the array (and step for example with tab through the items of this array)
.Value
only gets you the content of the currently single-selected cell.
if you want the enumeration of the array, you may call the .Cells()-method of the Range-object
Assuming that D19 until H19 contain "a" through "e" respectively, calling
Range("D19:H19").Cells(2)
returns you "b". Note that this is a one-based array and can be 2-dimensional. Cells() takes at most 2 parameters to specify the inner offset from the selection's origin.
hope that clarifies... regards
Try this:
Dim item As Variant
MsgBox Range("D19:H19").Count
item = Application.Transpose(Range("D19:H19").Value)
MsgBox LBound(item) & " " & UBound(item)
if you want a 1D array, to join it for an IN clause, for example, you should transpose your range.
I've found you have to transpose twice for a row, once for a column of data like this:
Dim rngRow As Range, rngColumn As Range
Set rngRow = Sheets(1).Range("A1", "Z1")
Set rngColumn = Sheets(1).Range("A1", "A20")
Dim arrRowValues, arrColValues
arrRowValues = WorksheetFunction.Transpose(WorksheetFunction.Transpose(rngRow))
arrColValues = WorksheetFunction.Transpose(rngColumn)
Dim numList As String, stringList As String
numList = Join(arrRowValues, ",")
stringList = "'" & Join(arrColValues, "','") & "'"
worth a play.