See end for an edit in response to PatricK's comment.
Apologies if this is a basic question, but I have been looking around here and other sites and cannot find the answer.
This is my first foray into VBA arrays (I'm new to VBA in general) and I'm struggling with some syntax. I am trying to read some data (part of a single column) from a worksheet into an array, do some processing on it, write it to a different sheet and then overwrite the array with some more data read from the first sheet. One key point is that the number of data points read per iteration will vary (e.g. 4 data points on the first loop, 3 on the second perhaps...) and so the array size should change too.
My attempt at the code works for the first iteration, but it does not seem to pick up the data on the second loop, instead leaving the array blank. Here's my code (sorry if this doesn't format properly, I only currently have internet access via my phone!):
Looping...
Dim DataSubSet
With ActiveWorkbook.Worksheets("Sheet1").Range("A" & CStr(I) & ":A" & CStr(io))
ReDim DataSubSet(1 To .Rows.Count, 1 To .Columns.Count)
DataSubSet = .Range ("A" & CStr(I) & ":A" & CStr(io))
End With
**Do processing**
End loop
Here i and io are variables that determine the cells that I'm interested in; they each change with the overarching loop.
As I mentioned, this works for the first iteration only. Any pointers on where I've gone wrong would be appreciated!
Thanks in advance,
Sam
-----------------------------------------------------------------------------------
PatricK, for further clarification, here's a quick routine that I knocked up to illustrate your point.
** formatting check failed (still writing on phone) so wouldn't let me post. Here's a dropbox link to the code in a text file:
https://db.tt/E0btoI5f
One possible reason it worked once may be because the "Do processing" codes Activated another workbook. Also not sure why you use 2D array to store 1D array values.
But please try:
Dim oWB as Workbook
' Loop Start
Set oWB = ActiveWorkbook
'Or Set oWB = Workbooks("<workbook name you are trying to get the data from>")
With oWB.Worksheets("Sheet1").Range("A" & i & ":A" & io)
ReDim DataSubSet(1 To .Rows.Count, 1 To .Columns.Count)
DataSubSet = .Value
End With
' Do Processing and finish loop above this line
Set oWB = Nothing
Test data in Sheet1:
Watching the variable DataSubSet (I have set i = 3 and io = 10):
Related
I was wondering if there is a way to keep some values stored in an array after the code ends and the excel file is closed.
The idea would be to have an array containing some values. When running the code, I might or might not modify some of its values and when I close the file I'd like this array to subsist and keep its values.
What I mean by that is that when I would re-open the file and run the code, all the other variables would be re-initialised (and obviously would get default values again), but this special array would still have all its values stored (so I would of course not re-declare it in that code).
One way to avoid this trouble would be to store all the values contained in the array in a worksheet and whenever the code is run again, put them back into a new array.
But I am wondering if there is a way to do this without using a worksheet.
One tends to think of a worksheet as the heart and soul of Excel and is, therefore, reluctant to make "such a big effort" to store just one array. From Excel's point of view a sheet is nothing more than an array, taking up just a few bytes of memory while it is empty. I would argue that a VeryHidden worksheet is precisely what you are asking for, in fact very similar to Word's Document Variables.
As an alternative I could suggest using one of the built-in properties, such as Keywords or Comments, to store a string which can be Split into an array. This would probably be more to your liking but I think it is inferior because the user can edit it, unless that is something you can use to your advantage.
Technically, and for all practical purposes, a VeryHidden worksheet is precisely what you are asking for. Your resistance is based in psychology, not reason - at least, that is how I convinced myself, and I can say being more liberal with the number of worksheets I create has given me more liberties at no perceptible cost.
Either store information, as you mentioned, within a (hidden) sheet, or use Document Variables.
From the documentation:
Sub AddDocumentVariable()
ActiveDocument.Variables.Add Name:="Age", Value:=12
End Sub
These variables will be available even if your code has finished or the workbook was closed and opened again.
Edit: (for excel, code found here)
Sub test()
Dim wb As Workbook
Dim docProps As DocumentProperties
Dim docProp As DocumentProperty
Set wb = ActiveWorkbook
Set docProps = wb.CustomDocumentProperties
With docProps
.Add Name:="CustomNumber", _
LinkToContent:=False, _
Type:=msoPropertyTypeNumber, _
Value:=1000
.Add Name:="CustomString", _
LinkToContent:=False, _
Type:=msoPropertyTypeString, _
Value:="This is a custom property."
.Add Name:="CustomDate", _
LinkToContent:=False, _
Type:=msoPropertyTypeDate, _
Value:=Date
End With
For Each docProp In docProps
Debug.Print docProp.Name, docProp.Value
Next
End Sub
I've only been using VBA for a couple of weeks now so bear with me.
I'm trying to change a macro so that it reads from an array instead of a range. One of the sections I need to change uses .formulaR1C1 to run a vlookup, but im having a hard time trying to work out how this can be done with the array.
An example of this is the below line:
.Range("M2:L" & lastrow).FormulaR1C1 = "=VLOOKUP(RC[-1], Sheet2!R1C1:R4C10, 3, 0)"
I'm not too sure on whether I can set the value of an array to a formula, as I've done above, or if I have to maybe store the value as a String and then edit the value of the cell later when printing the column back on to the worksheet.
What I have so far is below:
For i = 2 To lastrow
arr(i, 13).FormulaR1C1 = "=VLOOKUP(RC[-1],Sheet2!R1C1:R4C10,3,0)"
Next
Thanks in advance!
Storing the value as a string would certainly be a valid option. Simply write the formula in your array, presuming it is of Variant / String type, and when you put the data back into a worksheet you can use .FormulaR1C1 on a Cell (Range) object to apply it as a formula.
arr(i, 13) = "=VLOOKUP(RC[-1],WMSPAM!R1C1:R4C10,3,0)"
...
Range("M2").FormulaR1C1 = Arr(1,13)
I believe this approach is likely the most effective and most easily maintained for you. As you are learning and appear to be curious of what is possible, here are a couple more examples of how you could approach this, with some further explanation.
.FormulaR1C1 is a Range object method and so the only way it could be called on an Array item would be if that item was a Range object.
Dim arr(0 To 10) As Range
Set arr(0) = Range("A1")
arr(0).FormulaR1C1 = "=2+2"
Note that as Ranges are an Object (of reference type), this operation will directly effect the Range specified in the array. In the above example, the formula "=2+2" will be placed into cell A1. You can learn more about the difference between Reference and Value types here.
If your array contains only values, the other way to achieve what you need is to use the WorksheetFunction object. With this object you can access the formula functions, that you would use in worksheet, within VBA.
WorksheetFunction.VLookup(Arg1, Arg2, Arg3, Arg4)
As with anything in writing code the WorksheetFunction methods take some trial and error to get them to work how you would expect, but in my experience these specifically can be a tricky to implement, there are though some cases where they can be very useful!
You can read more about the VLookup method here.
You may try to use .Offset():
Sub Test()
lastrow = 11
With Sheets(1)
Set Rng = .Range("M2:L" & lastrow)
End With
For i = 0 To Rng.Rows.Count - 1
Rng.Offset(i, 12).FormulaR1C1 = "=VLOOKUP(RC[-1],Sheet2!R1C1:R4C10,3,0)"
Next
End Sub
I'm not sure what you are asking. Your first code line writes a formula to a two-column range in the most efficient way there is already, your second snippet shows the less efficient way of doing it one cell at the time. If the goal is to use the vlookup to fill cells and then dispose of the formula, one efficient way is this:
.Range("M2:L" & lastrow).FormulaR1C1 = "=VLOOKUP(RC[-1], Sheet2!R1C1:R4C10, 3, 0)"
.Range("M2:L" & lastrow).Value2 = .Range("M2:L" & lastrow).Value2
When I am programming in VBA, I want to initialise an array as
Dim Testarray(10) As String
Now: What are the initial values of this array without further ado?
Or asked in another way: I want to use this Testarray in order to find out certain values. These certain values are then changed to "Missing Line" or whatsoever. By that, I am able to check all the array values later on in order to find the number of my missing values by going through the respective array via, e.g., a for-loop or something like that.
(Bonus question: Right now, I am learning how to write VBA code. Does an array always have to have a name beginning with a capital letter? Or is it also allowed to begin it with a small letter?)
The default value of a String is vbNullString.
This constant evaluates to "" in comparisons, and the difference between "" and vbNullString seems to be very small and pertaining mostly to memory economics on a tiny scale.
Take this example:
Sub test()
Dim Testarray(10) As String
Debug.Print Testarray(1) = ""
Debug.Print Testarray(1) = vbNullString
End Sub
Output:
True
True
You would use the keyword vbNullString to check for empty or uninitialized elements of your newly created string array.
Dim Testarray(10) As String
Testarray(5) = "Test"
For i = 0 To UBound(Testarray)
If Testarray(i) = vbNullString Then
' skip
Else
Cells(i + 1, 1).Value = Testarray(i)
End If
Next
Will print "Test" in row 6 column 1 (A), and skip all uninitialized elements. As others have mentioned, in VBA testing for a blank string ("") will produce the same results in the about code snippet. This does not translate to the rest of the .NET framework however, where strings are a little more complex. For that you would use the String.IsNullOrEmpty function.
The default value for for a string is the empty string "".
As for the bonus question, short answer - no it doesn't matter. Long answer - vba itself is case insensitive, ie you can define names in whatever case you want and then refer to them in the same or a different case. However, most development environments (including the one in excel) will automatically change all further references to a name to the case in which you defined it.
I have a technical question:
My issue:
I create a
Dim arrTemp as Variant
Dim wbSource as workbook
Dim wbTarget as workbook
because I need to export multiple ranges from multiple worksheets (not fixed range) to another workbook. My code looks like:
' Worksheet 1
arrTemp = wbSource(1).Range("A1:B2").value
wbTarget(1).Range("A1:B2").value = arrTemp
If Not(IsArrayEmpty(arrTemp)) Then Erase arrTemp
' Worksheet 2
arrTemp = wbSource(2).Range("A1:B2").value
wbTarget(2).Range("A1:B2").value = arrTemp
If Not(IsArrayEmpty(arrTemp)) Then Erase arrTemp
' Worksheet 3
arrTemp = wbSource(3).Range("A1:B2").value
wbTarget(3).Range("A1:B2").value = arrTemp
If Not(IsArrayEmpty(arrTemp)) Then Erase arrTemp
(worksheet can be empty in the first place, that's why empty arr handler)
(worksheets can contain int/str/double/... and the size is not that big to define specific arr type)
My question is:
Does it make sense to erase the array every time? or It will be overwritten automatically?
I did a test to check the properties of the array (Lbound & UBound) before and after defining the array with a new range. I can see that It automatically Redim the array. Does it means that I only need to clear it in the end of the procedure?
Or it is a good practice to clear it in between?
Last but not least, do you see any problem in my code? Better way to perform this task?
Many thanks in advance!
Edit:
Bear in mind
The code is not correct for this task, no need to transfer to an array!
In Short (thanks to rory!):
No need to erase the array every time in between.
Only before leaving the procedure.
I am redesigning a part of a webpage to make it easier to update in the future. Currently, it is a series of tables, that are hard-coded. To redesign the table (for example, to alphabetize it like I want to), requires manually swapping around a lot of values in the html.
This is what I'd like to do:
Create a url_Link object with a title and link variable, to hold the display name and the url respectively.
Create an array of url_Link objects, and populate it at the top of the .asp file for the page.
Perform a for each loop on those arrays to build and populate the table
This itself isn't so bad, but I run into two problems.
First, I'd like to not have to define the array size, as this makes a second place that has to be changed when changes are made to the number of links.
There will be some logic to prevent certain url_Link objects from being displayed (for example, some users can't access certain pages, so they will not see links), and this would cause issues when sizing the arrays.
I know that I could just make the arrays of a large size, but this seems wasteful to me (and I don't know how for each functions and do not want a bunch of empty rows to show up).
What can I do to resolve these problems? I'm not very knowledgeable in vbscript, and most of the code that I have been working with does not take advantage of arrays or objects.
UPDATE:
I've tried using a redim PRESERVE to trim the excess fat of an oversized array. The problem is that in some cases, my array is populated by smaller amounts of objects than its max size because of if conditions. This is causing problems later when I use a for loop (tried to get a for each to work and that is not happening at the moment). I get the error "This array is fixed or temporarily locked" on the redim line
Code:
dim systemSettingsArray(1)
arrayCounter = 0
if ADMIN = "Y" then
set systemSettingsArray(arrayCounter) = (new url_Link).Init("Account Administration","Maintenance/Account_Admin.asp")
arrayCounter = arrayCounter + 1
end if
set systemSettingsArray(arrayCounter) = (new url_Link).Init("Time Approval","Maintenance/system_Time_Approval.asp")
redim Preserve systemSettingsArray(arrayCounter)
To show the correct way to use dynamic arrays in VBScript and to prove Matt's comment wrong:
Option Explicit
ReDim a(-1)
Dim b : b = Array()
Dim c()
Dim i
For i = 0 To 1
ReDim Preserve a(UBound(a) + 1) : a(UBound(a)) = i
ReDim Preserve b(UBound(b) + 1) : b(UBound(b)) = i
On Error Resume Next
ReDim Preserve c(UBound(c) + 1) : c(UBound(c)) = i
WScript.Echo Err.Description, "- caused by Dim c()"
On Error GoTo 0
Next
WScript.Echo "a:", Join(a)
WScript.Echo "b:", Join(b)
output:
Subscript out of range - caused by Dim c()
Subscript out of range - caused by Dim c()
a: 0 1
b: 0 1
Update wrt comment:
Both the a and the b way are correct - you get an one dimensional dynamic array to which UBound() can be applied from the start. Some people may prefer b, because they don't like ReDim v without a previous Dim v; other may feel that b is clumsy or errorprone.
If you look at this problem about a two-dimensional array, you may come to the conclusion, that the a way scales better.
Use redim preserve on the array. You can use UBound to find the current number of elements and do something like
ReDim Preserve myArrayName (UBound(myArrayName) + 1)
http://msdn.microsoft.com/en-us/library/c850dt17%28v=vs.84%29.aspx