If I write this in the VBA editor:
Dim ws As Worksheet: set ws = ActiveSheet
ws.Columns(
IntelliSense shows me a seemingly unrelated tooltip:
_Default([RowIndex], [ColumnIndex])
The Worksheet.Columns property only accepts the index (column) number as far as I can see in the documentation.
So why am I asked for a RowIndex? Why does it refers to _Default (and what is it)?
The Worksheet.Columns property only accepts the index (column) number as far as I can see in the documentation.
Nowhere in the documentation does it say the Columns property takes a parameter, and indeed, it would be wrong to mention that, because it doesn't have any:
Like Worksheet.Rows, Worksheet.Columns yields a Range object. So when you "parameterize" it, what's really happening is this:
Set foo = ws.Columns.[_Default](value)
Any argument you provide, get interpreted as arguments to an implicit default member call against the Range object that was returned by the call to Columns.
You may have read somewhere that the default member of a Range is its Value - and that is not true. The default member of a Range is a hidden property named [_Default] (square brackets are required in VBA if you want to invoke it explicitly, because no legal VBA identifier can begin with an underscore), that takes two optional parameters:
When you read ("get") this default property without providing any arguments, this default property does get you the Range.Value (i.e. a single Variant value for a single cell, or a 2D Variant array for multiple cells). When you assign to this default property, you are assigning the Range.Value.
But when any arguments are provided when reading ("get") this default property, what you get is a call to the very standard Range.Item indexer property:
So what Columns does, is simply take your input range, and yield a Range object laid out in such a way that it can be accessed using a RowIndex argument - we can prove this using named arguments, which show that this code is illegal:
?Sheet1.Range("A1:C1").Columns.Item(ColumnIndex:=2).Address
>> "wrong number of arguments"
As is this equivalent code:
?Sheet1.Range("A1:C1").Columns(ColumnIndex:=2).Address
>> "error 1004"
Note that the _Default property yields a Variant, so the above .Address member call can only be resolved at run-time (and you don't get any intellisense for it, and the compiler will not flinch at any typo, even with Option Explicit specified - you will get error 438 at run-time though).
Best stick to safe early-bound land, and pull the returned object reference into a local variable:
Dim foo As Range
Set foo = ws.Columns(1)
Debug.Print foo.Address '<~ early-bound w/intellisense & compile-time validation
TL;DR: You're being prompted for a RowIndex argument because you are making a call (albeit an implicit one) to a hidden _Default property that accepts a RowIndex argument.
Related
Background:
I have the following test code that is working where column H is equal to what's in column B versus column D
Range("H2:H17") = "=INDEX(D2:D17,MATCH(B2:B17,B2:B17,0))"
Question:
How do I use this in code to reference a separate sheet called "temp" to do the same thing. The idea is for each time the code looks for 'target' it does
an index and match checking column B to equal what's in column D so if the valuue A is passed then it would become Test1?
I tried the following code, but target is not getting updated with any value.
Dim Target As Variant
With Application
Target = .Index(Sheets("Temp").Range("D2:D17"), .Match(Sheets("Temp").Range("B2:B17"), Sheets("Temp").Range("B2:B17"), 0))
End With
Debugging shows the following for Target
Any help is appreciated!
Match is a member of the WorksheetFunction interface; you need a WorksheetFunction object instance to invoke it off of - a With block could hold that object reference for you so you only need to type it once:
With Application.WorksheetFunction
Target = .Index(sheet.Range("D2:D17"), .Match(sheet.Range("B2:B17"), sheet.Range("B2:B17"), 0))
End With
Where sheet would be a Worksheet variable to work with, or a Worksheet parameter to your procedure.
Something looks wrong with the first argument to Match though: lookup_value wants to be a single value: the early-bound Application.WorksheetFunction.Match method is rather picky about what Variant subtypes it's willing to play along with nicely, and will throw a type mismatch error as-is.
The late-bound version (watch out for typos! Option Explicit can't save you from late-bound code!) works as expected with the range/array lookup value argument, and yields a Variant() array:
With Application
Target = .Index(sheet.Range("D2:D17"), .Match(sheet.Range("B2:B17"), sheet.Range("B2:B17"), 0))
End With
Make sure Target is a Variant, because this late-bound Match will yield a Variant/Error value if the lookup fails (the early-bound version raises a run-time error instead) - and that'll be a type mismatch error if you try to assign to anything but a Variant.
I'm trying to match an obtained property from one class with another class's same property at run time. After some research, I found out that there is such thing as "Reflection" in .net but Im using just VBA. Just for background: I'm automating an application using this code, so some objects are exposed, while others are not.
The way I'm currently doing it is using a property of obtained class "Description", and use that to search for the same property at the targeted class.
Set TargetVar = hySetOperations.Item(j).TargetVariable 'This is a RealVariable a property that refers to a class
Set SourceObj =hySetOperations.Item(j).SourceObject 'This is also a RealVariable
'In order to import variable from source object, we r gonna use TargetVariable description and truncate space, and use it (This might not work if description
'is different than actual name of the variable)
Dim RealVarString As String
RealVarString = TargetVar.Description
'Trim spaces
RealVarString = Replace (RealVarString, " ", "")
Set SourceVar = CallByName ( SourceObj, RealVarString, vbGet)
This actually works for most of the cases since "description" is usually the same as property's name, but with spaces. However, in some cases, this is not the case, in which things go south.
SlicersCaches.add and Slicers.Add gives error when I name the parameters but works fine without.
Error:
Set SC1 = ActiveWorkbook.SlicerCaches.Add(Source:=PivTable Sourcefield:="Dept")
Set SL1 = SC1.Slicers.Add(Slicerdestination:=PivSheet)
No Error:
Set SC1 = ActiveWorkbook.SlicerCaches.Add(PivTable, "Dept")
Set SL1 = SC1.Slicers.Add(PivSheet)
Is this a bug in the program?
There are no bugs in the program. When you call a Sub or Function procedure, you can supply arguments positionally, in the order they appear in the procedure's definition, or you can supply the arguments by name without regard to position.
Named arguments are especially useful when you are calling a procedure that has optional arguments. If you use named arguments, you don't have to include commas to denote missing positional arguments. Using named arguments makes it easier to keep track of which arguments you passed and which you omitted.
When you call a procedure with an optional argument, you can choose whether or not to specify the optional argument. If you don't specify the optional argument, the default value, if any, is used. If no default value is specified, the argument is it would be for any variable of the specified type.
Below are the complete definitions of the slicer functions with arguments (optional in square brackets):
Slicer.Add
Add(SlicerDestination, [Level], [Name], [Caption], [Top], [Left], [Width], [Height]) As Slicer
SlicerCache.Add
Add(Source, SourceField, [Name]) As SlicerCache
Access 2013
I'm calling a formula to modify a string and it's changing the values w/in the parent sub.
Example:
Debug.Print Str 'Hello World my name is bob
BOBexists = InStringChceck(Str,"bob")
Debug.Print Str 'HELLO WORLD MY NAME IS BOB
Debug.Print BOBexists 'TRUE
I've used this function, InStringCheck, in Excel VBA before (and it's just an example, all of my string tools are doing this same thing now and I don't know why)
Function InStringCheck(Phrase as string, Term as string) as Boolean
Phrase = UCase(Phrase)
Term = UCase(Term)
if instr(1, Phrase, Term) then InStringCheck = True else InStringCheck = False
end function
In several of my functions I manipulate the input variables, to arrive at a solution, but I don't want those manipulations to persist outside of the function unless I pass them back up - some how they're being passed up, but they're not dimed as public variables
VBA parameters are implicitly passed by reference (ByRef). This means you're passing a reference to the value, not the value itself: mutating that value inside the procedure will result in that mutated value being visible to the calling code.
This is often used as a trick to return multiple values from a function/procedure:
Public Sub DoSomething(ByVal inValue1 As Integer, ByRef outResult1 As Integer, ...)
You have two options:
Pass the parameters by value (ByVal)
Introduce local variables and mutate them instead of mutating the paramters (and heck, pass the parameters ByRef explicitly)
If you have lots of occurrences of parameters being implicitly passed ByRef in your project, fixing them everywhere can easily get tedious. With Rubberduck you can easily locate all occurrences, navigate there, and apply appropriate fixes:
Disclaimer: I'm heavily involved in the Rubberduck project.
Building a little on #Sorcer's answer, VBA has default Sub/Functions parameters passing "by reference" (i. e.: "ByRef" keyword assumed if not specified) so that if you don't want their "inside" modifications survive outside them you have to explicitly type "ByVal" keyword before them in the arguments list.
But you have the option to avoid such modifications take place altoghether by using StrComp():
Function InStringCheck(Phrase as string, Term as string) as Boolean
InStringCheck = StrComp(Phrase, Term, vbTextCompare) = 0
End Function
Which could also lead you to avoid the use of InStringCheck() in favour of a direct use of StrComp() in your code
How can I reference a user defined type using a local variable without creating a copy of the type instance?
As an example, in the code below what I would ideally like to do is in MySub3 where I create a local variable, MT, and reference a data structure nested inside another struct ... but VBA doesn't allow this. It allows it for objects but not for user defined types (arrggg!) ... and for no apparent reason ... it just doesn't allow it.
MySub1 shows how to reference the nested struct in a long clunky way.
MySub2 shows how to do this by passing in the nested struct, but this clutters up the calling routine, and having multiple such nested structs gets ugly.
MySub2 demonstrates that VBA can do what I want, it just doesn't seem to provide a way to do it. I'm hoping there is a method I just haven't stumbled upon.
Note that my actual code is MUCH more complicated than this example, with multiple independent structs providing indices to many arrays as struct elements. Using these local reference variables would make the code much more readable and manageable.
Also Note that I am aware of the "with" statement, and it does help, but can only be used on one struct at a time.
Also Note that I am aware that I could use an actual object class. My code started out using an object but I quickly found out that VBA places limitations on arrays as property members ... a limitation that user defined types don't have.
Type tMyType
VariableA As Single
End Type
Type tMyOtherType
MyTypeArray() As tMyType
End Type
Type tOneMoreType
MyOtherType As tMyOtherType
End Type
Dim GlobalIndex As Integer
Sub TopLevel()
Dim TopLevelType As tOneMoreType
ReDim TopLevelType.MyOtherType.MyTypeArray(0 To 10)
Call MySub1(TopLevelType)
Call MySub2(TopLevelType.MyOtherType.MyTypeArray(GlobalIndex))
Call MySub3(TopLevelType)
End Sub
Sub MySub1(OMT As tOneMoreType)
Dim VarA As Single
VarA = OMT.MyOtherType.MyTypeArray(GlobalIndex).VariableA
End Sub
Sub MySub2(MT As tMyType)
Dim VarA As Single
VarA = MT.VariableA
End Sub
Sub MySub3(OMT As tOneMoreType)
Dim VarA As Single
Dim MT
Set MT = OMT.MyOtherType.MyTypeArray(GlobalIndex)
VarA = MT.VariableA
End Sub
From my point of view you have made it vary complicated. But I believe you have the reason for that.
The example you submitted generate the error you mentioned. But, when I changed some lines there is no error. I am not sure if my suggestion is the result you expected (while the question isn't fully clear to me) but try this instead of your MySub3:
Sub MySub3(OMT As tOneMoreType)
Dim VarA As Single
Dim MT
MT = OMT.MyOtherType.MyTypeArray(GlobalIndex).VariableA
VarA = MT
End Sub
Generally, this way I'm able to read any element im MySub3 passed from TopLevel.
If it is not the answer please clarify more.
I think here you have hit one of the limitations of VBA. I know of no way round the limitation on partial dereferencing of nested user types.
I think you would be best using classes containing private arrays with getter and setter functions (sadly, VBA doesn't have operator overloading either).