how to write thusands of sub vba code quickly changing references - vba

I want a module in my workbook to write about a thousand combinations of the below
Sub trade0001open()
Sheets("TRADEDIARY").Range("AO2").Value = 1
Sheets("TRADEDIARY").Range("AD3").Value = _
Sheets("TRADEDIARY").Range("AJ2").Value
Sheets("TRADEDIARY").Range("AD4").Value = _
Sheets("Sheet8").Range("HA1").Value + 1
Sheets("TRADEDIARY").Range("AO3").Value = 0
End Sub
Sub trade0001close()
Application.ScreenUpdating = False
Sheets("TRADEDIARY").Range("AI3").Value = Sheets("TRADEDIARY").Range("AI3").Value + 1
Sheets("TRADEDIARY").Range("AO3").Value = 1
Application.Wait (Now + 0.000001)
Sheets("TRADEDIARY").Range("AO3").Value = 0
Sheets("TRADEDIARY").Range("AO2").Value = 0
Sheets("TRADEDIARY").Range("AI2").Value = Sheets("TRADEDIARY").Range("AI2").Value + 1
Application.ScreenUpdating = True
End Sub
Changing changing the cell references by a cumulator of four rows down each time. So every AO2 would become AO6 in the next every AD3 becomes AD7. Everything apart from HA1 would change so that includes AO2,AD3,AJ2,AD4,AO3 for the first sub and then that includes AI3, AO3, AO2, AI2 for the second sub.
So since my code above contains the two subs I'd like copied a thousand times - each copy will add 4 rows to each cel reference in each sub.
I am quite new to vba so I guess I am after a similar autofill function like in excel except for my code to do this quickly instead of typing thousands of times unless of course I guess somebody could suggest how to do this differently. hint hint. bare in mind I obviously want all the values pasted without a clipboard so that when those values from where they are copied change, the destination doesn't change. Which is what my above code achieves.
Then I'd like to asign each individual sub within each of the two separately to a developer button control in the spread sheet ( again asigned to change four rows down each time)
#matteo to clarify ''well I envisioned the only way was to have 1000 trade0001open() and 1000 trade001close() possibly defined as trade0001open() , trade0002open() etc ditto close etc etc in order to right click for each one on a vba developer for control button alligned to each cell AK4 for open button and AM4 for close button so AK8 and AM8 etc etc which is long winded again and Im refraining from assuming a developer button could be alligned to each of those cells frm within vba and assigned to each of the sub at the moment. I guess one workaround might be to configure the j somehow into the sub name .''
matteo's reply: ''What you ask is complex to answer here, i will give you a tip to get started: use always the same macro but intercept the reference of the cell from which the call starts in order to add dinamically the 4 rows as I showed you above. You don't need 2000 macros, only 2 that are readapting themselves depending on the caller parent''
me : ''so I guess this is more complicated than it seems if I could somehow make form button's alligned to cells to reference the j value within the 2 macros without need for making thousands of sub macros. As far as I know form buttons in excel can only reference sub functions without reference and not UDFs or anything else or even cell references although I probably am wrong about this. ''
a form button to call the sub based on a cell's reference that is what I need right?
http://www.mrexcel.com/forum/excel-questions/843078-loop-visual-basic-applications-sub-call-form-button-each-nth-row-based-cell-value-row-reference.html#post4105072
I don't know for the life of me where to begin Trying to call a Sub with a String - VBA

You just need to make every string dinamically redefined and loop 1000 times the same macro. This is an example to get started:
For j = 1 To 1000 '<-- do this 1000 times
'...
Sheets("TRADEDIARY").Range("AO" & 2 + (j-1)*4).Value = 1 '<-- if j=1 then row = 2, if j=2 then row = 2+4 = 6 etc.
'...
Next j

Related

in VBA Why does the Selection Object act differently when indexed?

hopefully you can help me with this!
I am writing a code in VBA and I am having issues with the Selection object acting differently than I expect.
When I write a for loop as in
For Each Cell in Selection
MsgBox Cell.Value
Next Cell
It works as expected, but then I try to index it and it acts differently. Especially it is a non-contiguous cell selection.
Like this;
For i = 0 to 5
MsgBox Selection(i).Value
Next i
It gives pretty random values. any insight would be great!
Edit:
Thanks for the input everyone, it seems I need to find another way of doing the following. I have a piece of code that takes a user's selected cells and uses those values for calculations. Right now, I have been trying to make it so they can select non-contiguous cells. Basically, I need to make an array of these values, and my thoughts were to make a for loop as follows
For I = 0 To 5
Array(i) = Selection(i).Value
Next I
I'm not sure if there is another way of doing this. If anyone has some suggestions, I am interested!
As others have stated, you should not use Selection this way. But to answer your question...I did some quick testing based on your example use-case.
It appears that when you loop cell in Selection it goes left to right, then top to bottom by range area. So for example if you have two non-contiguous ranges selected then if will look something like this:
When you index it, it does not seem to make the jump between range areas. instead it will continue iterating left to right top to bottom within the column bounds of the first range area:
So if you slected 2 columns, iterating on the index would just continue down the first column, even if it is outside of the selection.
My test code:
Private Sub testing()
Dim i As Integer
For i = 1 To Selection.Cells.Count
Selection(i).Value = i
Next i
End Sub
Private Sub testing2()
Dim c As Range
For Each c In Selection.Cells
c.Value = c.Column
Next c
End Sub

VBA macro not triggering when target cell changes via form control option buttons

I literally just got my feet wet with VBA as this is my first macro. After many hours of searching, I couldn't seem to find an answer that had a solution that worked for me so here I am.
On Sheet3 I have 3 option buttons in a group box that are linked to cell "B18" on Sheet4 (Sheet4 is hidden to the user, a backstage if you will). When any of the three option buttons are selected, 'Sheet4!B18' gets updated as it should (e.g. 1, 2, or 3). What I want to happen is to have 'Sheet3!B17' changed based upon the value in 'Sheet4!B18', or effectively: IF('Sheet4!B18'=2,SET('Sheet3!B17'="Some Text Here:"),SET('Sheet3!B17'="0%")), but still allow user input in 'Sheet3!B17'. I have one VBA macro on Sheet4 with the following code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Worksheet.Range("B18") = 2 Then
Worksheets("Sheet3").Range("B17") = "Some Text Here:"
Else
Worksheets("Sheet3").Range("B17") = "0%"
End If
End Sub
If I manually update 'Sheet4!B18' then the macro gets triggered with the desired results. If I use any of the 3 option buttons on Sheet3, the macro does not get triggered even though the target cell is getting updated.
In my searching I couldn't seem to find anything concrete, but from what I could tell the "Worksheet_Change" function doesn't see changes to cells from form control as changes to the linked cell are considered a "recalculation" as if it were from a formula. I don't know how correct that is, but my searching led me to believe that I would need another macro assigned on the 3 buttons and/or group box that when either of those get selected/changed, it would somehow trigger the working macro on Sheet4.
I thought that perhaps I could create a new macro that I would assign to the group box or option buttons themselves so I tried that and could not get anything to work. I tried adding the above macro code to another sub, Sub mode() and assigning to only the group box, then only the buttons, but nothing happened in either case. I proceeded to try tweaking the code just in case the references were not correct, but saw no change regardless of how I specified the reference. I am not getting any error messages, but nothing gets triggered unless I manually change the value in 'Sheet4!B18'.
Is there a way to get the first macro that I have working on Sheet4 to trigger off of the option buttons changing the target cell value, something like forcing it to look only at that one specific cell for changes? Am I stuck making another macro for the buttons and/or group box to trigger the macro on Sheet4? Am I over-complicating this and there is some built in Excel sheets function(s) that I can use?
IF/THEN is a fine way to do it. VBA also supports ternary logic with the IIF function, like this:
Worksheets("Sheet3").Range("B17") = IIF(Worksheets("Sheet4").Range("B18") = 2, "Some Text Here:", "0%")
That may seem a little difficult to read, but it's a good concept to understand, since it's present in many languages, and usually with a more simplified implementation that makes it very useful and concise.
Also, I would suggest making a couple of other alterations that may make your code easier to write, read and maintain (especially as it becomes more complex).
First, alias the worksheets, something like this:
Dim this as Worksheet: Set this = Worksheets("Sheet3")
Dim that as Worksheet: Set that = Worksheets("Sheet4")
Now you would be able to rewrite your code like this:
If that.Range("B18") = 2 Then
this.Range("B17") = "Some Text Here:"
Else
this.Range("B17") = "0%"
End If
And the ternary approach would now be:
this.Range("B17") = IIF(that.Range("B18") = 2, "Some Text Here:", "0%")
And you can get as specific as you like with the aliases. For instance, you could realias the ranges, instead of just the worksheets, like this:
Dim this as range: Set this = Worksheets("Sheet3").Range("B17")
Dim that as range: Set that = Worksheets("Sheet4").Range("B18")
this = IIf(that = 2, "Some Text Here:", "0%")
Also, I find it easier to use the cells property than the range property, especially when you start having to do cell math. In that case, Range("B17") becomes Cells(17, 2).
You can also change the way the cells are referenced in the spreadsheet by typing Application.ReferenceStyle = xlR1C1 into the immediate window. That way you don't have to mentally convert between A2 style ranges to Cartesian style (2,1).
Sometimes you just have to go through your entire thought process and type everything out before you have an "ah-hah!" moment because that is exactly what I had happen. I said to myself, "Why can't I have just one macro that gets triggered by the option buttons that checks my linked cell then proceeds to update the cell I want?" Well, eventually I was able to find the right code and this is what worked perfectly:
Sub mode() ' mode is the name of this macro
If Worksheets("Sheet4").Range("B18") = 2 Then
Worksheets("Sheet3").Range("B17") = "Some Text Here:"
Else
Worksheets("Sheet3").Range("B17") = "0%"
End If
End Sub
As it turns out, I was overlooking the simple solution and the above macro is all I need once I assigned it to the 3 option buttons in my group box, but not the group box itself. Since users will not have access to the hidden Sheet4 and therefore 'Sheet4!B18' will never have manual user input, the macro I first had on Sheet4 could be removed safely. Due to the fact that the option buttons being chosen is the trigger for the assigned macro, it executes each time the option is changed and only when the option is changed. Perfect!
EDIT:
Thanks to Chris Strickland for some tips for better code! I went on to modify the above into what you see below for slightly better performance (using Cells() instead of Range()), to save the original value to another cell and restore it if option 1 or 3 were selected, used aliases, and finally the IIf operator.
Sub mode() ' mode is the name of this macro
Dim S3 As Worksheet: Set S3 = Worksheets("Sheet3")
Dim S4 As Worksheet: Set S4 = Worksheets("Sheet4")
If IsNumeric(Cells(17, 2)) = True Then
S3.Activate
S4.Cells(18, 3) = Cells(17, 2).Value
End If
S3.Cells(17, 2) = IIf(S4.Cells(18, 2) = 2, "Some Text Here:", S4.Cells(18, 3))
End Sub

VBA MS Project. How to move entries of lookuptable programmatically?

I have an issue. I'm working on VBA macros in MS Project 2013, that can automatically fill and change lookup table, which linked with local custom field in project professional. I have these code sections on VBA for:
-adding entries
Set objStateEntry = objOutlineCode.LookupTable.AddChild(entryName)
-changing descrption of entries
objStateEntry.Description = "some description"
-changing level of entires
objStateEntry.level = entryLevel
But I can't find how to programmatically move entries up/down in lookup table. In other words I need to use marked in the screenshot buttons programmatically. Please help me. Thank you!
Try something like this:
Private Sub SpinButton1_SpinDown()
On Error Resume Next
If ListBox1.ListIndex = ListBox1.ListCount - 1 Then Exit Sub
With Me.ListBox1
.ListIndex = .ListIndex + 1
End With
End Sub
Private Sub SpinButton1_SpinUp()
On Error Resume Next
If ListBox1.ListIndex = 0 Then Exit Sub
With Me.ListBox1
.ListIndex = .ListIndex - 1
End With
End Sub
goodluck
I tried recording a macro while using the spin button you highlighted, but this indicates that VBA can't control the spin button...
So it seems the only way to change the index of a lookup table entry by VBA is to first delete the entry then add it back, for example moving entry at index=4 up to index=2 (after saving the name and description of entry index=4):
Dim lteName As String
Dim lteDesc As String
lteName = Application.CustomFieldValueListGetItem(pjCustomResourceOutlineCode2, pjValueListValue, 4)
lteDesc = Application.CustomFieldValueListGetItem(pjCustomResourceOutlineCode2, pjValueListDescription, 4)
Application.CustomFieldValueListDelete FieldID:=pjCustomResourceOutlineCode2, Index:=4
Application.CustomFieldValueListAdd FieldID:=pjCustomResourceOutlineCode2, Value:=lteName, Description:=lteDesc, Index:=2
Two caveats:
1: It appears you can't do the above in the same macro that you are adding the lookup table entries. It only works in another macro run after adding the entries.
2: At least in my version of MS Project, the index number is flaky (it should be consecutive but sometimes index numbers repeat or there are gaps but then it corrects itself!), no doubt due to deleting and adding entries like I am suggesting. The code won't work if the index numbers that VBA is looking for don't match what is displayed in the lookuptable window. Oh dear... wish MSProject was perfect!
Noted same behaviour on another SO thread here.

Excel running slowly when Excel the active window

I couldn't find the answer to this issue anywhere, so I do hope you guys can help me. My excel macro goes through a couple iterations of data. It autofilters a source file, takes out information, works with the data, and does so again for about 50 times - once per person. Here's some code of what I mean, all the individual submethods work just fine and are pretty damn fast:
For j = 1 To names.Count
'filter the source by name, generate sheet
FilterName names(j)
'prepare data with the necessary dates
FillMasterDates dates(), j
Dim i As Long
Dim ending As Long
ending = Sheets("Daten").Rows.End(xlDown).Row
Dim cellvalue As String
'check dates, etc
For i = 1 To ending
cellvalue = Sheets("Daten").Cells(i, 1)
If cellvalue = "" Then
Exit For
End If
ColorCell (i)
FilterDate CStr(dates(i)), names(j)
Next i
'user data has been successfully gathered, copy over to final sheet
FillColumns j
Next j
The whole code takes about 4~ seconds to run (given that I have about 2000 rows and I create a new sheet for 50~ people), which is fine. The baffling thing is that when Excel stays my active window despite using Application.ScreenUpdating = False (earlier in the macro, but still active at this point), the necessary time to run the macro goes up to a staggering 25~ seconds. Same input, same output. So to put it simply - run macro, tab out of excel - macro needs about 4-5 seconds to run. run macro but stay in excel and wait - 25 seconds.
I've tried Application.WindowState = Application.WindowState, ActiveWindow.SmallScroll, DoEvents, Application.CalculateFull(). I tried different calculation settings, but I do not really use any of the formula calculations innate to Excel - I have to use Excel as an interface because the source file is an *.xls file and the final output has to remain in this format.
If you need me to provide more code snippets to make sense of it, ask away. I've been stumped for a good two days now.
You could always try a couple more lines to disable the calculations and alerts etc.
Application.ScreenUpdating = false
Application.Calculations = xlManual
Application.DisplayAlerts = False
However if you really want to bypass all the background nonsense excel seems to go through dont access the sheet directly through a loop, this concept maybe tricky if your not used to it but its worth every bit, and will speed up your code so fast you will wonder why you never did it in the first place.
I dont have your code so ill just give an example of how it works
Dim RangeArray as Variant 'This will store your range as a values array
RangeArray = Sheet1.Range("A1:G100000").Value 'this will put the entire ranges values into the array
If Not IsArray(RangeArray) Then ExitSub 'If your range is only 1 cell it will not create an array so be careful, handle this as needed
'This Array always starts lowerbound 1, RangeArray(1,1) = First Cell
Now with this you can loop through your data and manipulate and modify the array just like you would with a cell or a range except there is no overhead, its just values and not objects .
Once you have done what you need all you need to do then is put the values back into the sheets range
Sheet1.Range("A1:G100000").value = RangeArray
And thats it, very simple and very effective, and this transfer from array to range is immediate no matter how big it is.
Just let me know if this helps
Thanks
Paul S
---------------NEW MESSAGE-------------------
You could try something which maybe a little excessive and risky, if your only getting this problem while the window is active and displayed how about making it invisible, the problem is if your code fails and you fail to trap an error it will remain invisible until you goto taskmanager and close it there.
Application.Visible = false
This should deactivate the window too (although i have never tested that)
this should simulate you hiding the window and just bring it back when your code has finished..
---------------NEW MESSAGE-------------------
Application.Windowstate = xlMinimized
This should do the trick :D, should have mentioned this first haha
I also just saw that you tried something similar, but the code is incorrect there, try this one

Freeze on close after ListObject Resize

I have an Excel file that takes data from outside and writes it in a ListObject.
As adding rows one by one through ListRows.Add is very slow, I add the right number of empty rows to the sheet and resize the ListObject.
This works really well in Excel 2010.
With Excel 2007, it works but when the user closes the workbook or Excel, it freezes and Windows displays its crash window (asking if you want to close, restart or debug the application).
This is really annoying and doesn't look very good :).
Any idea of what I could do to prevent that?
Maybe you have a better idea to quicky ladd thousands of rows in a ListObject?
Moreover randomly (I reopen the file change nothing and execute the macro), Resize fails with an error message and Excel crashes if I stop the execution.
Here is the function that adds the empty rows, if I follow it step by step it all the ranges are correct and it does what I need.
I'm pretty sure this is this function that causes the problem as it disappears when I comment the call to that function.
Sub AddRowsToListObject(sheetName As String, myTable As ListObject, addRows As Long)
Dim i As Long
If addRows > 0 Then
Sheets(sheetName).Activate
'Add empty rows at the end
i = myTable.DataBodyRange.row + myTable.ListRows.Count
Sheets(sheetName).Range(Cells(i, 1), Cells(i + addRows - 2, 1)).EntireRow.Insert shift:=xlDown
'Offset -1 as you need to include the headers again
myTable.Resize myTable.DataBodyRange.Offset(-1, 0).Resize(myTable.ListRows.Count + addRows, myTable.ListColumns.Count)
End If
End Sub
Unfortunately I don't have Excel 2007 and cannot replicate the error described in the question. However and assuming that:
The code is not trying to add rows beyond the capacity of Excel 2007
The error is caused by the method used to add new lines to the existing ListObject
And since you are asking for an alternative method to add thousands of rows to an existing ListObject
Try the code below
Sub ListObjects_AddRows(myTable As ListObject, addRows As Long)
If addRows > 0 Then
With myTable.DataBodyRange
.Offset(.Rows.Count, 0).Resize(addRows, 1).EntireRow.Insert
With .Offset(.Rows.Count, 0).Resize(addRows, 1)
.Value = "X"
.ClearContents
End With: End With: End If
End Sub
add Application.ScreenUpdating = False right after the start of the sub
add Application.ScreenUpdating = True right before the end
If you are doing 1000s then you definitely don't need the screen refreshing each time a new line gets drawn. Change that and it will only redraw it once it is finished.
After a lot of painful testing, it looks like the problem is not in this method but in the deleting of the rows just before that :
Sub ResetListObject(myTable As ListObject)
myTable.DataBodyRange.ClearContents
If myTable.DataBodyRange.Rows.Count > 1 Then
myTable.DataBodyRange.Offset(1, 0).Resize(myTable.DataBodyRange.Rows.Count - 1, myTable.DataBodyRange.Columns.Count).EntireRow.Delete shift:=xlUp
End If
End Sub
Excel 2010 requires you to always keep 1 row when you empty the ListObject.
But Excel 2007 requires 2 rows !!
I don't know why and I can't find any information on that.
I changed my script to delete all rows except 2 and changed the function in the OP to manage that fact.