Some doubts about Dim and Set Worksheet - excel

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

Related

Can you group shapes as they are created in Excel?

I have a userform in Excel 2016 that will generate a certain group of shapes (a welding symbol, if the context is helpful), mainly consisting of lines, arcs, and textboxes. Some of these will be the same every time the code is run, while others are options to be determined by the user via the userform. At the end those elements are grouped into a single symbol. My current code works as described thus far.
The problem comes when I try to run the form a second time (generating a second group of shapes independent of the first group). I have it set up such that as the code is executed, it creates a shape, names that shape appropriately, then groups all shapes at the end, referring to them by name. The second time the code is run, it uses the same names as in the first run. As soon as it tries to form the second group, I get an error due to names referring to two different shapes.
My question is this: Is there a way to add shapes to a group (or to a collection to be grouped later) as they are created? It seems naming shapes isn't the way to go, as the names are retained after the code ends. I tried referencing by shape index, but since I have images on the page as well, it's hard to determine exactly what a particular shape's index is. I apologize for the lack of code, as I don't have access to it right now. If needed I can write up something simple to get the point across. Any help is greatly appreciated!
You can group shapes with a command like this:
Dim ws as Worksheet
Set ws = ActiveSheet ' <-- Set to the worksheet you are working on
ws.Shapes.Range(Array("Heart 1", "Sun 2", "Star 3")).Group
(you can access the shapes via name or via index). The result of the group command is another shape that is added to the sheet. But be aware that the grouped shapes still exists in the sheet, you can access them with the GroupItems-property.
With ws.Shapes
Dim shGroup As Shape, sh As Shape
Set shGroup = .Range(Array("Heart 1", "Sun 2", "Star 3")).Group
shGroup.Name = "MyNewGroup" & .Count
For Each sh In shGroup.GroupItems
Debug.Print sh.Name, sh.Type
Next sh
End With
As you can see, the single shape elements don't change their names, so grouping would not solve your naming issue. The only way is to add a suffix to the name, e.g. a number (as Excel does it when it creates a shape).
Update: Of course the Array- parameter does not need to be static. You can declare an array that is large enough (it doesn't matter if it contains some empty elements).
Const maxShapes = 12
Dim myShapes(1 to maxShapes) as String
myShapes(1) = *Name of first shape you created*
myShapes(2) = *Name of second shape you created*
...
ws.Shapes.Range(myShapes).Group
or use the Redim command:
Dim myShapes() as String
Redim myShapes(1 to NumberOfShapesInYourNewGroup)
myShapes(1) = *Name of first shape you created*
myShapes(2) = *Name of second shape you created*
...
ws.Shapes.Range(myShapes).Group
To get a unique shape and group name, you can implement various methods. I don't like the attempt with a global variable as they might get reset - for example when you cancel execution during debugging. You could use for example the suffix that Excel generates when you create a new shape. Or put the rename-statement into a loop, put a On error Resume Next before the rename (and don't forget to put an On error Goto 0 after it) and loop until renaming was successfull. Or loop over all shapes in your sheet to find the next free name.
After some trial and error, the solution I came up with is something like the following.
'Count shapes already on sheet
Shapesbefore=ActiveSheet.Shapes.Count
'Create new shapes
'Create array containing indexes of recently created shapes
Dim shparr() As Variant
Dim shprng As ShapeRange
ReDim shparr(Shapestart + 1 To ActiveSheet.Shapes.Count)
For i = LBound(shparr) To UBound(shparr)
shparr(i) = i
Next i
'Group shapes and format weight/color
Set shprng = ActiveSheet.Shapes.Range(shparr)
With shprng
.Group
.Line.Weight = 2
.Line.ForeColor.RGB = 0
End With
This way I don't have to worry about creating and managing various group and shape names, as I don't need to go back and reference them later.

VBA Populate Different Objects Using the Value from a Sub Argument

Good Afternoon All,
Let me preface this post by saying I have very little experience using VBA, but it is the tool I have to work with in this instance, so here I am. I am using the SAS Add-In for Microsoft Office, which isn't entirely relevant in this situation as far as I can tell, but it is best to give you some context. I have 2 subs that function very differently but make use of the same named ranges.
The first sub uses values from 4 cells within Excel and submits them to a stored process which returns values to particular cells which are defined as named ranges - approximately 64. Once that takes place the end-user will validate the results, make some changes to the values in the 64 cells and then submit the second sub. The second sub then passes the values contained within the 64 cells for processing by a second stored process.
It makes more sense to me to have the 64 variables defined once and not multiple times to save on maintenance, but they are applied to different objects for example:
Sub1
Dim outputParams As SASRanges
Set outputParams = New SASRanges
Dim DD_BD_Age As Range
Set DD_BD_Age = Sheet1.Range("DD_BD_Age")
outputParams.Add "DD_BD_AGE", DD_BD_Age
Sub2
Dim prompts As SASPrompts
Set prompts = sas.CreateSASPromptsObject
Dim DD_BD_Age As Range
Set DD_BD_Age = Sheet1.Range("DD_BD_Age")
prompts.Add "DD_BD_AGE", DD_BD_Age
Is there any way that I can define these variables for use across either sub. If I was using SAS I would create a macro with a parameter which would allow me to specify the value of outputParams or prompts depending on the context in which I was using them.
Admittedly, with my limited experience with VBA, I may just be making things more difficult than they need to be, so please let me know if this is the case.
Any help would be greatly appreciated.
If your SAS objects have a common "Add" method then you can do something like this:
Dim outputParams As SASRanges
Set outputParams = New SASRanges
AddParams outputParams
Dim prompts As SASPrompts
Set prompts = sas.CreateSASPromptsObject
AddParams prompts
'add common parameters
Sub AddParams(obj As Object)
With obj
.Add "DD_BD_AGE", Sheet1.Range("DD_BD_Age")
'etc for the rest
End With
End Sub

Executing VBA function from current workbook in cell formula get error #name (#nom for French version)

I meet a problem which is probably very simple when I use a VBA function into a formula of a cell, I get the cell content "#NAME" (not found function ? while a macro using the function (for test) is executed normally (displays the wished content for the cell, the returned value by the function, which extracts the filename from a fullpath).
context :
I had by the past (more than 12 years ago) developed, may be 50,000, VBA instructions, using office2003.
Today I have to develop again some macros with Office365. So I have forgotten a lot since this time and some features can have changed which can become tricking (I need to read again my old soft to recall all my knowledge, but I have no access to for now)
The problem
I get the error "#name" when I use a function created into VBA
associated to the current workbook. No explanation, no help, I tried to
find something during several hours and I found nothing.
I have developed too for testing (see code) a "sub" which calls the function, and his execution is successful, but...
I cannot run the function from the formula of any cell.
I have tested the security parameters of macro and fully unlocked the execution temporarily, and too declare the local directory as confident area.
Note : this code is detailed as an example
The code
Public Function FNameOf(CellPointed As Range)
Dim CurCell As Range
Dim Text1 As String
Dim Text2 As String
Set CurCell = CellPointed
Text1 = CurCell.Value
Text2 = Mid$(Text1, InStrRev(Text1, "\") + 1, Len(Text1))
FNameOf = Text2
End Function
Sub DispFileName2()
Dim style, disp, titre
Dim Cursel As Range
'Cursel = ActiveCell
disp = FNameOf(ActiveCell)
style = vbOKOnly
titre = "Nom du fichier extrait du texte (fullpath) de la cellule courante"
MsgBox disp, style, titre
End Sub
If I submit the macro DispFileName2 if get the message with the file name extracted from the path which is the content of the current cell
If I set the formule of a cell :
=FNameOf(AnotherCell) 'which contains a fullpath to a file
I get always the error "#NOM" (in French version) or, I think so, "#NAME" (in english Version) as if the function name (ref) was unknown from the workbook (the code is not reached, a stop is set on the first instruction)
What can be the reason ?
Please place your user defined function somewhere in a module (neither in "ThisWorkbook" nor in the individual worksheet's code, e. g. "Feuil1").
You should add the result declaration As String also.
I understood the example is for reference only, but you may shorten it:
Public Function FNameOf(CellPointed As Range) As String
FNameOf = Mid(CellPointed.Value, InStrRev(CellPointed.Value, "\") + 1)
End Function
I just got the solution while reading in more details previous edited threads in several tabs.
It is explained into a remark of : thread 12351339
The text is :
Microsoft Excel Objects such as 'Sheet1' or 'ThisWorkbook' are
classes. I don't believe you can access Functions which you put in
these classes through a cell. You could access them in VBA e.g.
ThisWorkbook.Square2() but it's recommended to put all UDF's into as
standard module and not a worksheet module. – Eddie Sep 28 '17 at
13:49
By default the creation panel defines code associated to current worksheet, then the function is not visible for the worksheet while the sub is a macro of the worksheet.
I have created a module and the function has run immediately.
Best regards
Trebly
Note : I never met this problem of visibility before because the developments where since the beginning concerning VBA user classes and modules combining multiple Excel workbooks and Word and a Mail manager activeX and so on...
I keep the subject because of the explanations, code and keywords may be to find more easily the solution for anybody else.

LibreOffice Basic Ignoring “some” of my Type...End Type Definition

I'm using LibreOffice Version: 4.4.3.2 Build ID: 40m0(Build:2) Locale: en_AU
I have a Basic Module
At the top of this module before any sub or functions I have
Type InitHeadings
MySort_By As Integer
MyCharacter As Integer
MyInitiative As Integer
MyRolled As Integer
MyTotal As Integer
End Type
...
Global InitiativeColumn As New InitHeadings
But when I run a sub, set a breakpoint and 'watch' the InitiativeColumn Object only the first two fields are shown.
The rest of my code relevant to this struct as the documentation calls them is below. I don't reference it anywhere else. Can anyone tell me why the first two would work but not the rest? I have two other structs in this code and both also ignore the last three fields. Is this a Bug?
Sub Main
'Initialise Doc and Sheet Objects
Dim Doc As Object
Doc = ThisComponent
StatsSheet = Doc.Sheets.getByName("Stats")
InitiativeSheet = Doc.Sheets.getByName("Initiative")
CombatSheet = Doc.Sheets.getByName("Combat")
'LOAD HEADING NAMES
'Initiative Sheet
For Column = 0 to 25 'Columns A to Z
MyHeadingName = InitiativeSheet.getCellByPosition(Column,0).String
Select Case MyHeadingName
Case "Sort By"
InitiativeColumn.MySort_By = Column
Case "Character"
InitiativeColumn.MyCharacter = Column
Case "Initiative"
InitiativeColumn.MyInitiative = Column
Case "Rolled"
InitiativeColumn.MyRolled = Column
Case "Total"
InitiativeColumn.MyTotal = Column
End Select
Next Column
End Sub
Sub MyInitiativeButton
'Iterate over a range of cells:
For Row = 1 To 25 'Rows 2 to 26
'Column 3 is column D the "Rolled" column
InitiativeSheet.getCellByPosition(InitiativeColumn.MyRolled,Row).VALUE = Roledice(1,20,0)
Next Row
End Sub
It looks like a bug, and seems to have been reported here. The problem did not occur when I tested it in a newer version (LO 5.1.0.3).
This is only an issue for the debugger window. The values are still there:
Sub TestStructs
InitiativeColumn.MySort_By = 5
InitiativeColumn.MyCharacter = 5
InitiativeColumn.MyTotal = 5
InitiativeColumn.DoesntExist = 5
End Sub
This code works fine until the line InitiativeColumn.DoesntExist = 5, whereupon it crashes.
Now the Global problem that you mentioned in the comments is really a problem. Considering the standard programming advice that global variables are bad, I think it's wise to consider alternatives.
Instead of a subroutine, could you perhaps use a Function that returns InitiativeColumn? If not, then assigning the variable as you suggested seems a viable workaround. Personally for LO macros I prefer Python or Java since they have classes.

Option Explicit - Not sure how to dim this

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

Resources