I'd like to pass an array of (sheet) controls to another sub to modify their properties:
E.g.
Dim collChk(0 To 2) As ? ' Tried Variant, OLEObject, Control...
collChk(0) = chk0
collChk(1) = chk1
collChk(2) = chk2
Sub:
Sub SetFalse(ByRef controls As ?)
Dim i As Long
For i = LBound(controls) To UBound(controls)
controls(i) = False
Next i
End Sub
But I keep getting: ByRef Argument Type mismatch or other errors
It could also be a Collection, that doesn't matter, but I have not been able to set that up too. I'm a bit lost what works how. Originally, the idea was to pass it as ParamArray, but errors popped out too.
(What would be great is simply to have a function that would work both with Sheet and UserForm controls but I want them specify manually and without duplication.)
For CheckBoxes:
Dim collChk(0 To 2) As MSForms.CheckBox
Set collChk(0) = Chk0
Set collChk(1) = Chk1
Set collChk(2) = Chk2
Note you need to use Set as they are objects. Then:
Sub SetFalse(ByRef controls() As MSForms.CheckBox)
Dim i As Long
For i = LBound(controls) To UBound(controls)
controls(i) = False
Next i
End Sub
Related
I am trying to copy range from 1 worksheet with conditional formatting rules and paste in another workbook using the exact formatting but deleting the conditional rules.
I am passing 2 range objects to my sub and when calling the macro I am getting the error. Please help me.
Sub Create()
Dim rgFrom, rgTo As Range
oldBook = ActiveWorkbook.Name
Workbooks.Add
Set rgFrom = Workbooks("Daily Flow Template.xlsm").Worksheets("DailyFlow").Range("A1:BZ110")
Set rgTo = ActiveWorkbook.Worksheets("Sheet1").Range("A1:BZ110")
PasteFormattedRange (rgFrom), rgTo ----- Error Object Required
End Sub
Sub PasteFormattedRange(ByRef rgFrom As Range, ByRef rgTo As Range)
Dim S As String
Dim rgStart As Range
Dim i As Long, CF_Format As Long
Dim SaveDisplayAlerts As Boolean, SaveScreenUpdating As Boolean
Dim HTMLInClipBoard As Boolean
Dim Handle As Long, Ptr As Long, FileName As String
Set rgStart = Selection
rgFrom.Copy
'Enumerate the clipboard formats
If OpenClipboard(0) Then
CF_Format = EnumClipboardFormats(0&)
Do While CF_Format <> 0
S = String(255, vbNullChar)
i = GetClipboardFormatName(CF_Format, S, 255)
S = Left(S, i)
HTMLInClipBoard = InStr(1, S, "HTML Format", vbTextCompare) > 0
If HTMLInClipBoard Then
Application.CutCopyMode = False
Application.Goto rgTo
ActiveSheet.PasteSpecial Format:="HTML"
Application.Goto rgStart
Exit Do
End If
CF_Format = EnumClipboardFormats(CF_Format)
Loop
CloseClipboard
End If
End Sub
ByRef argument type mismatch
This answer from "ByRef argument type mismatch in Excel VBA" might help explain this error a bit clearer than I can.
The basic gist of it is:
Unless you need to, pass arguments ByVal instead of ByRef.
If you use ByRef make sure to declare your statements explicitly rather than delcaring with the default data type (Variant) that way you will be passing a Range where a Range is expected.
This answer on my question on CodeReview also touches on passing arguments ByVal opposed to ByRef.
Object required (Error 424)
If you are not returning a value, you don't need to include parentheses ( ) - You can read about it on Using parentheses in code(VBA).
The below edit to your problem line will execute without the Object Required error:
PasteFormattedRange rgFrom, rgTo
When using parentheses for a Sub, Function or any other Method or Property, you would need to encapsulate all arguments within the parentheses rather than just one if the parentheses are actually required.
If you are assigning a value, you would need the parentheses, like so:
Sub Foo()
x = MyFunction(Argument1, Argument2)
End Sub
If you are not assigning a value, you don't include the parentheses, like so:
Sub Foo()
MySub Argument1, Argument2
End Sub
In VBA, you can use either Cells or Cells.Value, it has the same effect. Test1 and Test2 behaves the same way, allthough in test 2 the string is passed directly to the object.
Sub Test1()
Cells(1, 1) .Value = "Hello"
End Sub
Sub Test2()
Cells(1, 2) = "World"
End Sub
Is it possible to do something similar with any user class? Can-I assign a value directly to an object created from one of my classes withpout using the property value ?
Following Tim and K. recommendations, I've created a the following class:
Option Explicit
Dim LNG_Debut As Long
Public Property Let Debut(tLNG_Debut As Long)
LNG_Debut = tLNG_Debut
End Property
Property Get Debut() As Long
Debut = LNG_Debut
End Property
Then, I’ve exported that class to notepad and modified it the following way:
Property Get Debut() As Long
Attribute Debut.VB_UserMemId = 0
Debut = LNG_Debut
End Property
And finaly, I’ve imported it back in the VBA editor.
Then, both Test1 and Test2 have the same result
Sub Test1()
Dim MyVariable As obj_Test
Set MyVariable = New obj_Test
MyVariable.Debut = 10
End Sub
And
Sub Test2()
Dim MyVariable As obj_Test
Set MyVariable = New obj_Test
MyVariable = 10
End Sub
Many thanks
I'm dynamically creating option buttons on a form in Microsoft Word. I programatically assign the new options buttons a sub by way of the code below, but I can't work out how to assign one class to many buttons. Is this possible?
The code below works, but it only throws the message box up on the last button. I think what my code is doing is reassigning this class to the newest button each time, rather than adding a button to that class if that makes sense? How would I go about doing it the other way around so many buttons call the Focus_btn_Click event?
My Class (called "clsSectorbtn"):
Public WithEvents Focus_btn As MSForms.OptionButton
Sub Focus_btn_Click()
Msgbox "Test"
End Sub
Code within my form:
Dim SectorBtn As New clsSectorBtn
Sub Create_Radios(Radio_Array)
Dim RadioArray (0 to 2) As String
RadioArray(0) = "optionbutton1"
RadioArray(1) = "optionbutton2"
RadioArray(2) = "optionbutton3"
Set Fm = UserForm1("Frame1")
For x = 0 To UBound(Radio_Array)
Set opt = Fm.Object.Controls.Add("Forms.OptionButton.1")
opt.Caption = Radio_Array(x)
Set SectorBtn.Focus_btn = opt
Next x
End Sub
You need a global collection to hold the instances of clsSectorbtn, and it's easier to use a "factory" function to build each instance.
I'd do something like this:
Dim colBtn As Collection
Sub Create_Radios(Radio_Array)
Dim RadioArray (0 to 2) As String
RadioArray(0) = "optionbutton1"
RadioArray(1) = "optionbutton2"
RadioArray(2) = "optionbutton3"
Set Fm = UserForm1("Frame1")
Set colBtn = New collection
For x = 0 To UBound(Radio_Array)
Set opt = Fm.Object.Controls.Add("Forms.OptionButton.1")
opt.Caption = Radio_Array(x)
colBtn.Add OptObject(opt)
Next x
End Sub
'create an instance of clsSectorbtn and assign an option button to it
Function OptObject(opt) As clsSectorbtn
Dim rv as New clsSectorbtn
Set rv.Focus_btn = opt
Set OptObject = rv
End Function
I have a macro (code attached) which writes the data from two sheets into two variant arrays. It then uses a nested loop to look for all possible matches in the 2nd sheet on a piece of data in the 1st sheet.
When the first match is found one of the variant arrays appears to get wiped and I get a 'Subscript out of range'. this can happen when the data is being compared or when I subsequently try to pass data from that array to another procedure as a result of a match being found.
When I look in the Locals window, this array can change from showing the stored values to having the error message "Application-defined or object-defined error" in each index, or no indexes at all, or indexes with high negative numbers.
Regardless, if I try to investigate further while the code is in debug mode, Excel crashes ("Excel has encountered a problem and needs to close").
I have followed the advice at this link:
http://exceleratorbi.com.au/excel-keeps-crashing-check-your-vba-code/
...but to no avail.
I've stepped through the code and can trace it to the first time the data values being tested match. It happens for the same indexes (same i and j values) every time I run.
I'm using Excel 2013 on our office network.
Can anyone tell me what might be causing this or any tests I could perform to help narrow down the cause?
Could it be due to memory use? The arrays come out at about 15000 x 11 and 4000 x 6 and it's the smaller one that is being corrupted/failing.
Sub classTest()
Dim i As Long, j As Long
Dim CK_Array() As Variant, RL_Array() As Variant
Dim wb As Workbook
Dim CK_Data As Worksheet, RL_Data As Worksheet
Set wb = ThisWorkbook
Set CK_Data = wb.Sheets(1)
Set RL_Data = wb.Sheets(2)
Call getRange_BuildArray(CK_Array, CK_Data)
Call getRange_BuildArray(RL_Array, RL_Data) ' this sets the array that gets corrupted.
For i = 2 To UBound(CK_Array)
If Not IsEmpty(CK_Array(i, 6)) Then
For j = 2 To UBound(RL_Array)
If CK_Array(i, 6) = RL_Array(j, 4) Then ' array gets corrupted here or line below
Call matchFound(dResults, CStr(CK_Array(i, 1) & " | " & CK_Array(i, 5)), CStr(RL_Array(j, 2) & " " & RL_Array(j, 3)), CStr(RL_Array(j, 1)), CStr(RL_Array(1, 3))) ' or array gets corrupted here
End If
Next j
End If
Next i
End Sub
Private Sub getRange_BuildArray(arr As Variant, ws As Worksheet)
Dim endR As Long, endC As Long
Dim rng As Range
endR = ws.UsedRange.Rows.Count
endC = ws.UsedRange.Columns.Count
Set rng = Range(ws.Cells(1, 1), ws.Cells(endR, endC))
arr = rng
End Sub
EDIT:
As requested here is the code to the matchfound Sub. It's a dictionary, which holds class objects in a collection. Therefore I have also posted the class code below. I'm not yet making use of all of the class properties and methods as this issue has halted my testing.
Sub matchFound(dictionary As Object, nameCK As String, nameRL As String, RLID As String, dataitem As String)
Dim cPeople As Collection
Dim matchResult As CmatchPerson
If dictionary.exists(nameCK) Then
Set matchResult = New CmatchPerson
matchResult.Name = nameRL
matchResult.RLID = RLID
matchResult.matchedOn = dataitem
dictionary.Item(nameCK).Add matchResult
Else
Set cPeople = New Collection
Set matchResult = New CmatchPerson
matchResult.Name = nameRL
matchResult.RLID = RLID
matchResult.matchedOn = dataitem
cPeople.Add matchResult
dictionary.Add nameCK, cPeople
End If
End Sub
Class
Option Explicit
Private pName As String
Private pRLID As String
Private pMatchedOn As String
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Name As String)
pName = Name
End Property
Public Property Get RLID() As String
RLID = pRLID
End Property
Public Property Let RLID(ID As String)
pRLID = ID
End Property
Public Property Get matchedOn() As String
matchedOn = pMatchedOn
End Property
Public Property Let matchedOn(textString As String)
pMatchedOn = textString
End Property
Public Sub MatchedOnString(datafield As String)
Dim text As String
text = Me.matchedOn & "|" & datafield
Me.Name = text
End Sub
I've reduced your problem to a Minimum, Verifiable and Complete Example.
The problem occurs when you assign the implicit default value of a range to a Variant variable that was passed as a Variant array.
Sub VariantArrayWTF()
Dim aBar() As Variant
Dim aFoo() As Variant
GetArray aBar
GetArray aFoo
Debug.Print aBar(1, 1)
'aFoo() has now lost it's `+` sign in Locals window, but the bounds are still visible
Debug.Print aBar(1, 1)
'aFoo() has now lost its bounds in Locals Window
'aFoo(1,1) will produce subscript out of range
'Exploring the Locals Window, incpsecting variables, will crash Excel
Debug.Print aFoo(1, 1)
End Sub
Sub GetArray(ByRef theArray As Variant)
'Note the use of theArray instead of theArray()
'Implicitly calling the default member is problematic
theArray = Sheet1.UsedRange
End Sub
There are a number of workarounds - I'd recommend using both:
Use Explicit calls to `Range.Value`
You can even make explicit call to the default member Range.[_Default]. The exact method isn't important, but it must be explicit.
Sub GetArray(ByRef theArray As Variant)
theArray = Sheet1.UsedRange.Value
End Sub
Avoid the use of `Call`, and pass common Variant definitions
Call is a deprecated statement, and can be omitted.
Declare the arrays and the helper functions' array argument consistently. That is, use () in all instances, or none.
Note the difference between declaring Dim aFoo() As Variant which is an array of Variants, and declaring Dim aFoo As Variant which is a Variant that can contain an array.
With Parentheses
Sub VariantArrayWTF()
Dim aBar() As Variant
Dim aFoo() As Variant
GetArray aBar
GetArray aFoo
Debug.Print aBar(1, 1)
Debug.Print aBar(1, 1)
Debug.Print aFoo(1, 1)
End Sub
Sub GetArray(ByRef theArray() As Variant)
theArray = Sheet1.UsedRange
End Sub
Without Parentheses
Sub VariantArrayWTF()
Dim aBar As Variant
Dim aFoo As Variant
GetArray aBar
GetArray aFoo
Debug.Print aBar(1, 1)
Debug.Print aBar(1, 1)
Debug.Print aFoo(1, 1)
End Sub
Sub GetArray(ByRef theArray As Variant)
theArray = Sheet1.UsedRange
End Sub
I have found the lines of code which were causing the problem. However, I cannot explain why it would necessarily cause a crash so I would appreciate other input on why this is happening.
When passing the RL and CK arrays to the getRange_Build Array sub I left out the brackets that would have denoted these variables as arrays.
The code was this...
Call getRange_BuildArray(CK_Array, CK_Data)
Call getRange_BuildArray(RL_Array, RL_Data)
...but should have been this
Call getRange_BuildArray(CK_Array(), CK_Data)
Call getRange_BuildArray(RL_Array(), RL_Data)
I'm thinking the reason that this didn't get flagged as a compile error is because the parameter in question in the getRange_BuildArray procedure itself also lacked the necessary brackets to denote an array.
It was this...
Private Sub getRange_BuildArray(arr As Variant, ws As Worksheet)
...it should have been this
Private Sub getRange_BuildArray(arr() As Variant, ws As Worksheet)
With those changes in place the macro is completing successfully for the full dataset and is not causing excel to crash.
As mentioned it would be great if someone could offer a more detailed breakdown of how this caused excel to crash.
I'm having some trouble with syntax options while writing a VBA Macro for Excel. In VBA you can call a method on an object in two different ways:
foo.bar(arg1, arg2)
or
foo.bar arg1, arg2
I absolutely detest the second sort of syntax because I find it lacks any sort of clarity, so I normally adhere to the first option. However, I've come across a situation where using the first option creates an error, while the second executes fine. (This may perhaps be an indicator of other problems in my code.) Here is the culprit code:
Function GetFundList() As Collection
Dim newFund As FundValues
Range("A5").Select
Set GetFundList = New Collection
While Len(Selection.Value)
Set newFund = New FundValues
' I set the fields of newFund and move Selection
The problem is in this next line:
GetFundList.Add newFund
Wend
End Function
FundValues is a class I created that is essentially just a struct; it has three properties which get set during the loop.
Basically, when I call GetFundList.Add(newFund) I get the following error:
Run-time error '438':
Object doesn't support this property or method
But calling GetFundList.Add newFund is perfectly fine.
Does anyone understand the intricacies of VBA well enough to explain why this is happening?
EDIT: Thanks much for the explanations!
Adding items to a collection is not defined as a function returning a value, but as a sub routine:
Public Sub Add( _
ByVal Item As Object, _
Optional ByVal Key As String, _
Optional ByVal { Before | After } As Object = Nothing _
)
When calling another sub routine by name and sending arguments (without adding the "Call" statement), you are not required to add parentheses.
You need to add parentheses when you call a function that returns a value to a variable.
Example:
Sub Test_1()
Dim iCnt As Integer
Dim iCnt_B As Integer
Dim iResult As Integer
iCnt = 2
iCnt_B = 3
fTest_1 iCnt, iResult, iCnt_B
End Sub
Public Function fTest_1(iCnt, iResult, iCnt_B)
iResult = iCnt * 2 + iCnt_B * 2
End Function
Sub Test_2()
Dim iCnt As Integer
Dim iCnt_B As Integer
Dim iResult As Integer
iCnt = 2
iCnt_B = 3
iResult = fTest_2(iCnt, iCnt_B)
End Sub
Public Function fTest_2(iCnt, iCnt_B)
fTest_2 = iCnt * 2 + iCnt_B * 2
End Function
Let me know if not clear.
This Daily Dose of Excel conversation will be helpful.
When you use the parentheses you are forcing VBA to evaluate what's inside them and adding the result to the collection. Since NewFund has no default property - I assume - the evaluation yields nothing, so can't be added. Without the parentheses it evaluates to the instance of the class, which is what you want.
Another example. This:
Dim coll As Collection
Set coll = New Collection
coll.Add Range("A1")
Debug.Print coll(1); TypeName(coll(1))
and this ...
coll.Add (Range("A1"))
Debug.Print coll(1); TypeName(coll(1))
... both yield whatever is in A1 in the debug.window, because Value is Range's default property. However, the first will yield a type of "Range", whereas the type in the 2nd example is the data type in A1. In other words, the first adds a range to the collection, the 2nd the contents of the range.
On the other hand, this works:
Dim coll As Collection
Set coll = New Collection
coll.Add ActiveSheet
Debug.Print coll(1).Name
... and this doesn't:
coll.Add (ActiveSheet)
Debug.Print coll(1).Name
because ActiveSheet has no default property. You'll get an runtime error 438, just like in your question.
Here's another way of looking at the same thing.
Let assume that cell A1 contains the string Hi!
Function SomeFunc(item1, item2)
SomeFunc = 4
End Function
Sub Mac()
' here in both of the following two lines of code,
' item1 will be Variant/Object/Range, while item2 will be Variant/String:
SomeFunc Range("A1"), (Range("A1"))
Let i = SomeFunc(Range("A1"), (Range("A1")))
'this following is a compile syntax error
SomeFunc(Range("A1"), (Range("A1")))
' while here in both the following two lines of code,
' item1 will be Variant/String while item2 will be Variant/Object/Range:
SomeFunc ((Range("A1")), Range("A1")
Let j = SomeFunc((Range("A1")), Range("A1"))
'this following is a compile syntax error
SomeFunc((Range("A1")), Range("A1"))
Set r = Range("A1") ' sets r to Variant/Object/Range
Set r = (Range("A1")) ' runtime error 13, type mismatch; cannot SET r (as reference) to string "Hi!" -- Strings are not objects in VBA
Set r = Range("A1").Value ' runtime error (same)
Let r = Range("A1") ' set r to "Hi!" e.g. contents of A1 aka Range("A1").Value; conversion to value during let = assignment
Let r = (Range("A1")) ' set r to "Hi!" e.g. contents of A1 aka Range("A1").Value; conversion to value by extra ()'s
Let r = Range("A1").Value ' set r to "Hi!" by explicit use of .Value
End Sub
I only add this to help illustrate that there are two things going on here, which could be conflated.
The first is that the () in an expression that converts the item to its Value property as stated above in other answers.
The second is that functions invoked with intent to capture or use the return value require extra () surrounding the whole argument list, whereas functions (or sub's) invoked without intent to capture or use the return value (e.g. as statements) must be called without those same () surrounding the argument list. These surrounding () do not convert the argument list using .Value. When the argument list has only one parameter, this distinction can be particularly confusing.