How to refer to with object - vba

How can i refer to the object i use inside With if i want the object itself, not its properties / methods?
With ThisWorkbook.Sheets("MySheet")
Call MySub(ThisWorkbook.Sheets("MySheet")) ' works OK, but duplicated
Call MySub(this) ' does not works
.Range(...).Value2 = 1
...
End With
+ what is the correct terminology here? i don't even know how to compose a google query for this and get some usefull results (since with is a common word)...
UPDATE: to clarify, i was thinking in terms of a handle like with ... as handle from python syntax, not about object-oriented this keyword

How about by not using with in the first place? It makes your code much more readable, uses no more memory (as the with statement has to allocate a temporary variable anyway), and is less confusing.
Dim WS as WorkSheet
WS = ThisWorkBook.Sheets("MySheet")
Call vymaz_obrazky(WS)
WS.Range(...).Value2 = 1
In the code above, the total cost is one additional line of code (the DIM statement), and 9 less keystrokes overall. (The DIM statement is 19 keystrokes, changing to WS in the three lines is 6 keystrokes, but you've saved the with (4) and duplication (30), saving about 9 keystrokes.)

Try this
Sub Sample()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("MySheet")
With ws
MySub ws
'~~> Rest of the code
End With
End Sub
or
Sub Sample()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("MySheet")
MySub ws
With ws
'~~> Rest of the code
End With
End Sub
Edit:
do you have any info about non-existence of "this"? – deathApril 19 mins ago
this is basically a keyword from C# which refers to the current instance of the class. The equivalent of this in VB is Me.
The Me keyword provides a way to refer to the specific instance of a class or structure in which the code is currently executing. For example in a Userform you can use
Me.textBox1.Text = "Blah Blah"
In VBA, Me can also be used for thisworkbook. For example, if you paste this code in the ThisWorkbook code Area then it will give you the name of the workbook
Sub Sample()
Debug.Print Me.Name
End Sub
Similarly when you run the above code from the Sheet Code Area you will get the Sheet Name.
HTH

Use .Cells.Parent. This only works for worksheets, but there are similar things for some other objects (for a workbook you can use .Sheets.Parent)
With ThisWorkbook.Sheets("MySheet")
Call MySub(.Cells.Parent)
.Range(...).Value2 = 1
...
End With

Related

VBA : How best for multiple subroutines to refer to the same worksheet

I am relatively inexperienced in VBA and don't know how to best structure my code.
I have a number of subs that all operate on an particular sheet, let's say Sheet1.
Each of my subs starts by setting the worksheet as follows:
Set ws = Sheets("Sheet1")
but I am conscious that down the track I may change the name of Sheet1 to something else, and this would require me to make changes to all of my subs. Ideally it is better to set this declaration once so that I only have to change it once.
What would be the best way to do this?
Several ways to do so. Btw, when using Sheet, you should always specify the workbook, else VBA will try to access the sheet from the active Workbook, and that is not always the workbook you want to work with. If the sheets and your code are in the same book, best is to refer to ThisWorkbook
o Define a constant at the top of your code. When sheetname is changed, you just need to change the const definition
Const MySheetName = "Sheet1"
Sub MySub1
Dim ws as Worksheet
Set ws = ThisWorkbook.Sheets(MySheetName)
(...)
o Use the Code name. A code name is a technical name of a sheet that can be changed only in the VBA-environment, so usually it never changes. See https://stackoverflow.com/a/41481428/7599798
o If the sheet is always the first (and maybe the only) sheet of a workbook, you can use the index number instead
Set ws = ThisWorkbook.Sheets(1)
o Use a function that returns the sheet:
Function getMySheet() As Worksheet
Set getMySheet = ThisWorkbook.Sheets("Sheet1")
End Function
Sub MySub1
Dim ws as Worksheet
Set ws = getMySheet
(...)

I have failed endlessly trying to write the code in VBA to insert this formula into a cell

The formula:
=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9))," ")
An example of what I've tried in VBA:
Sub inputWorkdays()
Range("h9").Formula = "=IFERROR(IF(OR(E9=0,D9=0),0,NETWORKDAYS(D9,E9)),""Yes"")"
End Sub
I'm trying to add the formula from above into cell H9.
Select the cell with the formula and write the following:
Sub TestMe
debug.print Selection.Formula
debug.print Selection.FormulaR1C1
End sub
In your case it would give:
=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9)),"YES")
=IFERROR(IF(OR(R[-4]C[-1]=0,R[-5]C[-2]=0),0,NETWORKDAYS(R[-5]C[-2],R[-5]C[-1])),"YES")
Take the first one and use it like this:
Range("h9").Formula = "=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9)),""YES"")"
I gather from the comments that there is no error, just "nothing happens". I see nothing wrong with your code. Except...
Range("h9").Formula = "..."
When Range is unqualified like this, you implicitly refer to the ActiveSheet; if the active sheet isn't the sheet you're expecting to write to, then it's easy to conclude that "nothing happens" and that the code doesn't work.
If you have Rubberduck installed (full disclosure: I'm heavily involved with the development of this open-source VBE add-in), you will see that Range in this case is a member of Excel._Global, and an inspection result will tell you that you're implicitly referring to the ActiveSheet:
Range("H9").Formula = "..."
Implicit references to the active sheet make the code frail and harder to debug. Consider making these references explicit when they're intended, and prefer working off object references.
http://rubberduckvba.com/Inspections/Details/ImplicitActiveSheetReferenceInspection
To fix this, qualify the Range call with a Worksheet object - now the Range call is a member of the Excel.Worksheet class:
Dim sheet As Worksheet
Set sheet = ThisWorkbook.Worksheets("Sheet1")
sheet.Range("H9") = "..."
By qualifying Range calls with a worksheet object, you make sure that you're always writing to the worksheet you mean to write to - not the worksheet that happens to be the active one when the code runs.

loop through specified sheets in VBA

I am trying to use a bit of code I found here For Each Function, to loop through specifically named worksheets to loop through specified sheets in a workbook, run a small amount of code and move to next sheet.
Sub LoopThroughSheets()
Dim Assets As Worksheet
Dim Asset As Worksheet
Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For Each Asset In Assets
'my code here
MsgBox ActiveSheet.Name 'test loop
Next Asset
End Sub
This is not looping through the sheets. I tried Dim Assets as Worksheet but this broke the code.
Any help much appreciated,
Cheers
The code you show in your question fails because of:
Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
Assets is a Worksheet which is a type of Object and you must use Set when assigning a value to an Object:
Set Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
This would fail because Array("…") is not a worksheet.
You imply that an earlier version of your code would run but did not loop through the worksheets. The reason is:
MsgBox ActiveSheet.Name
This displays the name of the active worksheet but nothing in this loop changes the active worksheet.
I am not happy with your solution although there is nothing explicitly wrong with it. I have seen too many programs fail because the programmer did too much in a single statement. Firstly, the more complicated a statement, the longer it will take to get right in the first place and the longer it takes to understand during subsequent maintenance. Sometimes the original programmer got the statement slightly wrong; sometimes the maintenance programmer got it wrong when trying to update it. In every case, any saving in runtime was not justified by the extra time spend by the programmers.
Alex K has fixed your code by redefining Assets and Asset as Variants, as required by VBA, and adding Sheets(Asset).Select to change which worksheet is active. I cannot approve of this because Select is a slow statement. In particular, if you do not include Application.ScreenUpdating = False, the duration of your routine can go through the roof as the screen is repainted from each Select.
Before explaining my solutions, some background on Variants.
If I write:
Dim I as Long
I will always be a long integer.
At runtime, the compiler/interpreter does not have to consider what I is when it encounters:
I = I + 5
But suppose I write:
Dim V as Variant
V = 5
V = V + 5
V = "Test"
V = V & " 1"
This is perfectly valid (valid but not sensible) code because a Variant can contain a number, a string or a worksheet. But every time my code accesses V, the interpreter has to check the type of the current contents of V and decide if it is appropriate in the current situation. This is time consuming.
I do not want to discourage you from using Variants when appropriate because they can be incredibly useful but you need to be aware of their overheads.
Next I wish to advocate the use of meaningful and systematic names. I name my variables according to a system that I have used for years. I can look at any of my programs/macros and know what the variables are. This is a real time saver when I need to update a program/macro I wrote 12 or 15 months ago.
I do not like:
Dim Assets As Variant
Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
because "pipe_mat_tables" and so on are not assets; they are the names of worksheets. I would write:
Dim WshtNames As Variant
WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
My first offering is:
Option Explicit
Sub Test1()
Dim WshtNames As Variant
Dim WshtNameCrnt As Variant
WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For Each WshtNameCrnt In WshtNames
With Worksheets(WshtNameCrnt)
Debug.Print "Cell B1 of worksheet " & .Name & " contains " & .Range("B1").Value
End With
Next WshtNameCrnt
End Sub
I could have named WshtNameCrnt as WshtName but I was taught that names should differ by at least three characters to avoid using the wrong one and not noticing.
The Array function returns a variant containing an array. The control variable of a For Each statement must be an object or a variant. This is why I have defined WshtNames and WshtNameCrnt as Variants. Note, your solution worked because a worksheet is an object.
I have used With Worksheets(WshtNameCrnt) which means any code before the matching End With can access a component of this worksheet by having a period at the beginning. So .Name and .Range("B1").Value reference Worksheets(WshtNameCrnt) without selecting the worksheet. This is faster and clearer than any alternative.
I have used Debug.Print rather than MsgBox because it is less bother. My code runs without my having to press Return for every worksheet and I have a tidy list in the Immediate Window which I can examine at my leisure. I often have many Debug.Print statements within my code during development which why I have output a sentence rather than just a worksheet name or cell value.
My second offering is:
Sub Test2()
Dim InxW As Long
Dim WshtNames As Variant
WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For InxW = LBound(WshtNames) To UBound(WshtNames)
With Worksheets(WshtNames(InxW))
Debug.Print "Cell B1 of worksheet " & .Name & " contains " & .Range("B1").Value
End With
Next InxW
End Sub
This macro has the same effect as the first. I sometimes find For more convenient than For Each although I can see no advantage either way in this case. Note that I have written LBound(WshtNames) even though the lower bound of WshtNames will always be zero. This is just me being (over? excessively?) precise.
Hope this helps.
Solved it but always happy to hear other methods
Sub loopsheets()
Dim Sh As Worksheet
For Each Sh In Sheets(Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables"))
MsgBox Sh.Range("b1")
Next
End Sub
Cheers
Use variants instead of worksheets.
Array returns a Variant array of string so cant be cast to Worksheet, the Each variable must be a Variant.
Dim Assets As Variant
Dim Asset As Variant
Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For Each Asset In Assets
'my code here
Sheets(Asset).Select
MsgBox ActiveSheet.Name 'test loop
Next Asset

VBA: Initialize Combo Box in worksheet

I have an error during initialization and I do not get why this is not working:
Private Sub Workbook_Open()
Dim ws As Worksheet
Set ws = Worksheets(1)
ws.ComboC15.AddItem "Technology"
end sub
It is very weird, the code should be correct. I have checked the combo name and it is "ComboC15".
In fact, this is the sub:
Private Sub ComboC15_Change()
Ps: I also checked the sheet and it is the first sheet where I have the Combo
The Worksheet object in VBA doesn't have a property or method called ComboC15, as this is entirely your own invention. However, the individual worksheet object in your workbook, i.e. the sheet itself as a physical sheet and not as a VBA sheet, knows all about ComboC15 since it's been dropped on it by you. Therefore, you need to access the worksheet object (notice the little w) and not the Worksheet object (notice the big W). If it's confusing to read, imagine how confusing it is for me to try to explain this...
To get access to the worksheet Object, you can do any of the following:
' Assuming "Sheet1" is the code name of the object. You can find this code name
' in the VBA editor. In the "Project Explorer" window, look under Microsoft
' Excel Objects. Your sheets are listed there in the form (for a blank, new
' workbook) "Sheet1 (Sheet1)". The bit *outside* the brackets is the code name.
Sheet1.ComboC15.AddItem "Technology"
' You can even call it directly from the "Worksheet" object by using the sheet
' index.
Worksheets(1).ComboC15.AddItem "Technology"
However, if you want to create a variable with it so you don't have to copy/paste the same thing over and over, declare it as an Object and not as a Worksheet. So do this:
Private Sub Workbook_Open()
Dim ws As Object ' Declare ws as "Object"!
Set ws = Worksheets(1)
ws.ComboC15.AddItem "Technology"
End Sub
The following SO Q&A explains a bit more about the different kind of sheet names:
Excel tab sheet names vs. Visual Basic sheet names
and I hope this article might give you a better insight on how to reference different kinds of objects in VBA.

trying to set global named range but local range ends up getting set?

I'm trying to change the address that a named range refers to. There are two ranges in the workbook with the same name, one scoped to the workbook and the other scoped to SheetA. I'm using this code:
Sub changeNamedRangeAddress(bk As Workbook, rangeName As String, newRange As Range)
bk.Names(rangeName).RefersTo = newRange
End Sub
When I look at the value for bk.Names(rangeName) in the Immediate window, it appears to be referencing the global version of that name, because the following returns true:
?typeof bk.Names(rangeName).Parent is Workbook
But after the sub runs, the locally scoped version's address has changed to that of newRange.address and the global one remains the same.
Is there anything else I can do to make sure that .RefersTo targets the global named range?
EDIT: The sheet that the locally scoped named range refers to is active when this script runs.
I solved this by activating another worksheet. So the code now looks like this:
Sub changeNamedRangeAddress(bk As Workbook, rangeName As String, newRange As Range)
bk.Sheets("SheetB").Activate
bk.Names(rangeName).RefersTo = newRange
End Sub
For some reason this allows .RefersTo to modify the global range instead of the one that points to SheetA. This seems like an odd way to solve the problem, though, so I'll wait to see if anyone comes up with anything better before accepting my own answer.
Here is a solution that is more functional if you have a few or several cases like this across your sheets (which I have seen), but won't make much of a difference with 1 or 2, except you'll have a more functional procedure in your library! :)
Option Explicit
Sub changeGlobalNamedRangeAddress(bk As Workbook, rangeName As String, newRange As Range)
'this sub only changes named range scoped to the workbook and ignores any ranges scoped to the worksheet with the same name.
Dim n As Name
For Each n In bk.Names
If InStr(1, n.Name, rangeName) > 0 And InStr(1, n.NameLocal, "!") = 0 Then
n.RefersTo = newRange
Exit For
End If
Next
End Sub