I have an existing macro that I use to format columns. I've been using this without problems. Now, I'm looking to learn how to use Option Explicit and I am running into a problem with defining my variable.
What should I be dim'ing Level as? I tried Dim Level As String but that didn't work. I'm trying to get a better understanding so any feedback would be appreciated.
Option Explicit
Sub adviseformat()
Dim Form As Worksheet
Set Form = Sheets("Formatting")
With Form
Level = WorksheetFunction.Match("Level", .Rows("1:1"), 0)
.Columns(Level).Delete
.Columns("D:E").Delete
.Range("U:U").Value = Range("E:E").Value
.Columns("E").EntireColumn.Delete
.Columns("F:I").Delete
.Columns("I").Delete
.Columns("L").Delete
.Columns("M").Delete
Form.Range("A:B").EntireColumn.Insert
Form.Range("A1").Value = "Owner"
Form.Range("B1").Value = "Comment"
Form.Range("A1").Interior.Color = 65535
Form.Range("B1").Interior.Color = 65535
Form.Range("O1").Interior.Color = 65535
End With
End Sub
As you type the WorksheetFunction.Match part, the VBA editor should pop up and give you a clue to the return type. It should say something like:
Match(Arg1, Arg2, [Arg3]) as Double
The "As Double" part tells you the return type of the Match function. This is the type you should use to declare your Level variable.
Looking on MSDN, I found this :
MATCH returns the position of the matched value within lookup_array, not the value itself.
For example, MATCH("b",{"a","b","c"},0) returns 2, the relative position of "b" within the array {"a","b","c"}.
So my guess is that you should use Dim Level As Variant
Related
I'm having a class module with some data:
Private sharedFolders() As String
Public Property Let SetSharedFolders(val As String)
Dim i As Integer
sharedFolders = Array("folder one", "folder two")
i = UBound(sharedFolders)
i = UBound(sharedFolders)
ReDim Preserve sharedFolders(i)
sharedFolders(i) = CStr(val)
End Property
Property Get GetSharedFolders()
GetSharedFolders = sharedFolders()
End Property
And I want to add something to this property from other module like this:
Sub PrepareData()
Dim e
Dim s
Dim a(2) As String
Set e = New Entry
a(0) = "add one"
a(1) = "add two"
For Each s In a
e.SetSharedFolders (s) 'Here comes exception
Next
For Each s In e.GetSharedFolders
Debug.Print s
Next
End Sub
But I receive an "wrong number of arguments or invalid property assignment vba" exception... Can anyone assist?
Addendum
Thanks to #AJD and #Freeflow to pointing out a mistake and idea to make it easier. Decided to make as like below.
Class Module:
Private sharedFolders As New Collection
Public Property Let SetSharedFolders(val As String)
If sharedFolders.Count = 0 Then ' if empty fill with some preset data and add new item
sharedFolders.Add "folder 1"
sharedFolders.Add "folder 2"
sharedFolders.Add CStr(val)
Else
sharedFolders.Add CStr(val)
End If
End Property
Property Get GetSharedFolders() As Collection
Set GetSharedFolders = sharedFolders
End Property
and regular module:
Sub AddData()
Dim e As New Entry ' creating an instance of a class
Dim s As Variant ' variable to loop through collection
Dim a(1) As String 'some array with data to insert
a(0) = "add one"
a(1) = "add two"
For Each s In a
e.SetSharedFolders = s
Next
For Each s In e.GetSharedFolders
Debug.Print s
Next
End Sub
Initially I thought the problem lies in this code:
i = UBound(sharedFolders)
i = UBound(sharedFolders)
ReDim Preserve sharedFolders(i)
sharedFolders(i) = CStr(val)
i is set twice to the same value, and then the sharedFolders is reDimmed to the same value it was before! Also, there is some trickery happening with the use of ix within a 0-based array.
But the problem is most likely how you have declared your variables.
For Each s In a
e.SetSharedFolders (s) 'Here comes exception
Next
s is a Variant, and a is a Variant. At this point VBA is trying to guess how to handle a For Each loop with two Variants. And then the improper call is made. The correct syntax is:
e.SetSharedFolders s '<-- no parenthesis
There are plenty of posts on StackOverflow explaining how to call routines and what the impact of the evaluating parenthesis are!
However, at this point we are only assuming it is passing in a single element of the array - it could be passing the full array itself (albeit unlikely).
And the third factor -
Public Property Let SetSharedFolders(val As String)
The parameter val is being passed ByRef and should be passed ByVal. This also has unintended side effects as I found out (Type mismatch trying to set data in an object in a collection).
Public Property Let SetSharedFolders(ByVal val As String)
All in all you have the perfect storm of ambiguity driving to an unknown result.
The answer here is to strongly type your variables. This removes about two layers of ambiguity and areas where errors can happen. In addition, this will slightly improve code execution.
Another aspect is to understand when you should pass something ByVal and when to use the default (preferably explicitly) ByRef.
And a final gratuitous hint: Use a Collection instead of an Array. Your code you have implies a Collection will be more efficient and easier to manage.
Addendum
(thanks to #FreeFlow):
If the OP changes the definition of sharedfolders to Variant rather than String() then the array statement will work as expected.
The line e.SetSharedFolders (s) will work fine if it is changed to e.SetSharedFolders = s because the method SetSharedFolders is a Let Property not a Sub. There are other errors but these two changes will make the code run.
The problem that I'm facing is that I have an entire column that has text separated by _ that contains pixel size that I want to be able to extract but currently can't. For example:
A
Example_Number_320x50_fifty_five
Example_Number_One_300x250_hundred
Example_Number_two_fifty_728x49
I have tried using Substitute function to grab the numbers which works but only grabs the numbers when I need something like: 320x50 instead I'm getting 0, as I'm not sure how to exactly extract something like this. If it was consistent I could easily do LEFT or RIGHT formula's to grab it but as you can see the data varies.
The result that I'm looking for is something along the lines of:
A | B
Example_Number_320x50_fifty_five | 320x50
Example_Number_One_300x250_hundred | 300x200
Example_Number_two_fifty_728x49 | 728x49
Any help would be much appreciated! If any further clarification is needed please let me know and I'll try to explain as best as I can!
-Maykid
I would probably use a Regular Expressions UDF to accomplish this.
First, open up the VBE by pressing Alt + F11.
Right-Click on VBAProject > Insert > Module
Then you can paste the following code in your module:
Option Explicit
Public Function getPixelDim(RawTextValue As String) As String
With CreateObject("VBScript.RegExp")
.Pattern = "\d+x\d+"
If .Test(RawTextValue) Then
getPixelDim = .Execute(RawTextValue)(0)
End If
End With
End Function
Back to your worksheet, you would use the following formula:
=getPixelDim(A1)
Looking at the pattern \d+x\d+, an escaped d (\d) refers to any digit, a + means one or more of \d, and the x is just a literal letter x. This is the pattern you want to capture as your function's return value.
Gosh, K Davis was just so fast! Here's an alternate method with similar concept.
Create a module and create a user defined function like so.
Public Function GetPixels(mycell As Range) As String
Dim Splitter As Variant
Dim ReturnValue As String
Splitter = Split(mycell.Text, "_")
For i = 0 To UBound(Splitter)
If IsNumeric(Mid(Splitter(i), 1, 1)) Then
ReturnValue = Splitter(i)
Exit For
End If
Next
GetPixels = ReturnValue
End Function
In your excel sheet, type in B1 the formula =GetPixels(A1) and you will get 320x50.
How do you create a user defined function?
Developer tab
Use this URL to add Developer tab if you don't have it: https://www.addintools.com/documents/excel/how-to-add-developer-tab.html
Click on the highlighted areas to get to Visual Basic for Applications (VBA) window.
Create module
Click Insert > Module and then type in the code.
Use the user defined function
Note how the user defined function is called.
Normally, Dim should be done first and then Set should be done in vba code, like the below code.
Dim xWs1 As Worksheet
Dim xWs2 As Worksheet
.
.
.
Dim xWsN As Worksheet
Set xWs1 = Worksheets("A")
Set xWs2 = Worksheets("B")
.
.
.
Set xWsN = Worksheets("NNN")
If I need to Dim and Set 5 worksheets, 10 lines of code will be needed. It seems that the code will be too long when I need to Dim and Set more than 5 worksheets.
I found that if I just use Set, the vba code can also run properly. I would like to ask this will cause any problems if I didn't use Dim?
Set xWs1 = Worksheets("A")
Set xWs2 = Worksheets("B")
.
.
.
Set xWsN = Worksheets("NNN")
Thanks!
If you don't use dim statement , variable is automatically created as a Variant type.
The Variant type can be an integer, a string, a workbook, or any of the other type of variable and it can change as the variable changes, one moment it can be a string, then it can be changed to a workbook.
Using Dim
Without using Dim
There are mainly two problems with not using Dim,
Variant types uses more computer memory as a result it will make your code slow especially when you use loops.
Difficult to find errors ( in your case you can assign anything to variable XWs1 such as numbers , names etc. which can be avoided if you use dim)
How to Declare and Set 92 Objects using 92 Characters:
If "taking up space" is your concern, and you hypothetically want to explicitly set 5 procedure-level variables (w1..w5) to Set to 5 worksheets (Sht1..Sht5), you could use:
Example #1:
DefObj W
Sub wSet1()
Dim w1, w2, w3, w4, w5
Set w1=[Sht1]:Set w2=[Sht2]:Set w3=[Sht3]:Set w4=[Sht4]:Set w5=[Sht5]
End Sub
...or, even more space-efficient, if for example, you had 92 worksheets to Set in 92 declared variables? Then:
Example #2:
DefObj W
Sub wSet2():Dim wks(1To 92),x:For x=1To 92:Set wks(x)=Sheets("Sht"&x):Next x:End Sub
(That's 92 characters... not counting the line feed!)
Explanation:
Between these two ways of shortening declaration, we're using six shortcuts. Below is a summary of each, and see the links under "More Information" for full documentation on each.
Disclaimer: There are a number of reasons we shouldn't use shortcuts in programming. The obvious one is that, the more you compress code, the harder it is to read and understand (especially by others), and therefore can be harder to troubleshoot or expand upon.
If you don't know what the "standard methods" are, do not learn the shortcuts first! Learn how to do things "THE RIGHT WAY" before learning the shortcuts, no matter how appealing it may seem. There was a time that I argued that neatness like indentation and commenting, and full, proper techniques, didn't matter. I was wrong; had to learn that the hard way. If you're reading this, you'll probably have to learn the hard way too, but at least:
Don't use shortcuts when posting example code in your Stack Overflow questions. (This is not a method of [MCVE]!) You will probably get yelled at! ...and possibly have you questions down-voted or ignored... You were warned!
✓ DefObj (Default Data Types)
[Deftype statements][1] are a forgotten method of declaring default data types. Normally, the default data type is [`Variant`][2], so this:
Dim myVariable as Variant
...is identical to:
Dim myVariable
...however the DefObj W statement (used at module-level) says:
All variables declared in this module, that start with the letter 'W' default to type Object (unless otherwise specified). Note that Deftypes statements must be used at module-level (before your first Sub).
The entire list: (More Info)
DefBool DefByte DefCur DefDate DefDbl DefDec DefInt DefLng DefSng DefStr DefObj DefVar
✓ , (Commas in 'Dim' Statements)
When declaring variables with Dim, multiple variables can be listed on the same line, separated with a comma. Therefore this:
Sub mySub()
Dim myVariable1 as Currency
Dim myVariable2 as Currency
…
...is identical to this: (combining examples with Deftypes)
DefCur m
Sub mySub()
Dim myVariable1, myVariable1
…
✓ Sheets ('Sheets' collection)
The WorkSheets Object refers to the collection of all the Worksheet objects in the specified or active workbook.
The Charts Object` refers to the collection of **all the Chart objects in the specified or active workbook.
But the **Sheets Objectrefers to ***both*** theWorksheets*and*Charts` collections.
So, if a workbook has 3 worksheets and 2 chart sheet, in VBA:
Sheets.Count will return 5
Worksheets.Count will return 3
Warning: Using Sheets could cause a conflict if you have a Chart and a Worksheet with the same name (and should also be avoided when referring to worksheets in other files). But for a simple single-file, worksheet-only workbook, save yourself some Work and stick with just Sheets.
✓ [ ] (Square-Bracket Reference Shortcuts)
[Square brackets] can be used as a shortcut when referring to Worksheets, Cell Ranges and individual Cells. You can use either the A1 Reference Style or a named range within brackets as a shortcut for the Range property. You do not have to type the word "Range" or use quotation marks.
Worksheets("Sheet1").[A1:B5].ClearContents
[MyRange].Value = 30
This is barely documented, and even less documented is the fact that, if used in the logical order, square brackets can be used to refer to worksheets.
Combining examples, all of these statements will have identical result:
Worksheets("Sheet1").Range("A1") = Now()
Sheets("Sheet1").Range("A1") = Now()
Worksheets("Sheet1").[A1] = Now()
Sheets("Sheet1").[A1] = Now()
[Sheet1].[A1] = Now()
✓ wks() (Variable Arrays)
If you have a large number of similar objects to declare, it's often easier (and more organized) to group them together in an array. An array can be declared as any type including, for example, Object, Worksheet. (...or even the rarely-used and bizarre types like LongLong and IConverterApplicationPreferences. (Apparently whoever thought up that one doesn't care for shortcuts.)
✓ For..Set..Next (Loop to Set Variable Arrays)
When using an array of objects (any any variable sets), the next logical step is to reduce code with any tasks that need to be performed on the entire group of objects.
Other Notes:
Example #1 could have been compressed to one line but I wanted it to be easy to read in the answer. If our sheet names were S1..S5 instead of the oh-so-lengthy Sht1..Sht5, and we use the :, we could accomplish the same thing in 105 characters:
Example #1b:
DefObj W
Sub wSet():Dim w1,w2,w3,w4,w5:Set w1=[S1]:Set w2=[S2]:Set w3=[S3]:Set w4=[S4]:Set w5=[S5]:End Sub
Data Type Shortcut Symbols
Another rarely used set of dates back to 1974: data type shortcuts chosen by Gary Kildall for the CP/M Operating System
Symbol Data Type Constant
% Integer vbInteger = 2
$ String vbString = 8
& Long vbLong = 3
# Decimal vbDecimal = 6
! Single vbSingle = 4
# Double vbDouble = 5
Still supported today in many coding languages, you could for example use these interchangeably:
Dim myVariable as String
Dim myVariable$
More Information:
Microsoft.com : How to Break and Combine Statements in Code (VB/VBA)
MSDN : Refer to Cells by Using Shortcut Notation
Excel Hero : Excel VBA Shortcut Range References
MSDN : Using Data Types Efficiently
MSDN : Dim Statement (VBA)
ExcelHowTo : Worksheets vs. Sheets
Stack Overflow : Difference between Worksheets & Worksheet objects
MSDN : Set Statement
MSDN : Declaring Arrays
Take the following example of why using implicit variable declaration is usually a bad idea:
Sub Test()
myVariable = 10
myOutcome = myVaraible + 5
End Test
myOutcome = 5. Can you see why?
I misspelled myVariable in the second line, so I just essentially created a brand new variable myVaraible (which had a default value of 0).
This is why you should always use Option Explicit at the beginning of every module; and why you should always explicitly declare all variables.
While it still works, you are just setting yourself up for needless debugging headaches.
If your issue is that you want to condense your code to use less lines, you can do something like this:
Option Explicit
Sub Test()
Dim myVariable As Long: myVariable = 10
Dim myOutput As Long
myOutput = myVariable + 5
End Sub
You can also declare multiple variables on the same line:
Option Explicit
Sub Test()
Dim myVariable As Long, myOutput As Long
myVariable = 10
myOutput = myVariable + 5
End Sub
Not necessarily recommending this (as it can degrade readability), but it's yet another method of declaring variables. This does require the same data type, but you can add your worksheets in an array (from your example):
Option Explicit
Sub Test()
Dim xWs(1 To 5) As Worksheet
Set xWs(1) = Worksheets("A")
Set xWs(2) = Worksheets("B")
Set xWs(3) = Worksheets("C")
Set xWs(4) = Worksheets("D")
Set xWs(5) = Worksheets("E")
End Sub
I'm writing a few VBA functions for work and ran into a problem that should be easy to solve, but somehow I can't manage to, despite my best attempts at finding an answer here and on Google. I wrote a function that should give me the range between two strings in a column:
Function FindRng(StartRng As String, EndRng As String) As Variant
Dim TopOfRange As Single
Dim BottomOfRange As Single
TopOfRange = WorksheetFunction.Match(StartRng, Sheets("InfCom").Range("B:B"), 0)
BottomOfRange = WorksheetFunction.Match(EndRng, Sheets("InfCom").Range("B:B"), 0)
FindRng = Range(Sheets("InfCom").Cells(TopOfRange, 2), Sheets("InfCom").Cells(BottomOfRange, 2))
End Function
So if the inputs A and B are on rows 100 and 105, it should return B100:B105. When I test this by adapting the code to read FindRng = Range(...).Address, I indeed get $B$100:$B$105.
However, when I then input the result of FindRng into a customized Index Match function, I get an error. The function is as follows:
Function subsetPBPC(rngReturn As Range, LookupValueH As Variant, TopOfRange As String, BottomOfRange As String, LookupValueV As Variant) As Variant
subsetPBPC = sPBPC(rngReturn, LookupValueH, FindRng(TopOfRange, BottomOfRange), LookupValueV)
End Function
The problem is that it seems to read the output of FindRng not as a range, but as the content of that range: when I use the Evaluate Formula tool on FindRng embedded in another formula, it shows the output of FindRng as {A,B,C,D,E} instead of $B$100:$B$105, where A to E are the contents of the cells in the range. I have the feeling the solution is really simple, but I don't see it. The functions underlying the customized Index Match function have been tested and all work like a charm.
Set instead of let. Let assigns the value of an expression to a variable. Set assigns an object reference to a variable. You want to return a reference to the range object, not return the value produced by the range object's default property.
In VBA writing
FindRng = Range(...)
is implicitly writing
Let FindRng = Range(...)
However you want
Set FindRng = Range(...)
Edit 1:
It is quite important to understand the difference between an object reference and a value in VBA. This is a similar concept to passing arguments by value or by reference. Hopefully these two links help some:
The Let statement on MSDN
The Set statement on MSDN
Edit 2:
Oh, and I guess I should touch on default properties! Some objects like range have default properties. If you treat the range as a value instead of an object, it uses the default property instead of throwing an error because it's an object not a value. In the case of range the default property is Value. So if you say A = Range("A1") what you're actually saying is Let A = Range("A1").Value when you might mean Set A = Range("A1"). So you're getting the value contained in the cell A1, instead of a range object representing that cell.
Picking up that your current code should both
use Set as per AndADM's commnet
dimension SetRng as a Range rather than Variant
you can simplify your function as below (which may save time if you are calling it repetitively)
Also, you could test for this range being Nothing (if your two strings werent found), whereas you current code will error out if either string is missing.
Function SetRng(str1 As String, str2 As String) As Range
With Sheets("infCom").Columns(2)
Set SetRng = Range(.Find(str1, , xlValues, xlWhole), .Find(str2, , xlValues, xlWhole))
End With
End Function
I have a number of ActiveX controls/buttons on a page, and I would like to modify several of the parameters of the buttons (in a loop function).
I am fine with writing the loop function to achieve this, but cannot find a way to refer to the object using a string variable. I have set up an object variable (as per below), and a string variable to be used to change the reference for the object variable - but can't find a way to get it to work.
This is the code that does NOT work:
Private Sub TrialCode_Click()
Dim ButtonObj As Object
Dim ButtonCaption As String
Dim ButtonString As String
ButtonString = "CommandButton1"
Set ButtonObj = ButtonString
ButtonCaption = "Something"
ButtonObj.Caption = ButtonCaption 'example of the kind of parameters I want to change
End Sub
The Set ButtonObj = ButtonString is the command that fails, reporting a Type Mismatch error.
I'm working in Excel 2013.
I really hope there is some way to do this. Any help will be really appreciated!!!
The CommandButton belongs to an OLEObject
try
ButtonString = "CommandButton1"
Set ButtonObj = ActiveSheet.OLEObjects(ButtonString)
ButtonCaption = "Something"
ButtonObj.Object.Caption = ButtonCaption 'example of the kind of parameters I want to change
Note that some properties occur directly under ButtonObj, others such as Caption sit below Object