Can you group shapes as they are created in Excel? - 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.

Related

Find Workflow objects 3D in visio

I´m trying to create a code in vba excel to detect what´s inside the work flow objects - 3D as the ones shown in the following picture:
The pictures are always the same. I have been able to find and select the sentence inside the cell. But I need it to search for all the work flow objects in different visio.
This is where I got to:
Dim DiagramServices As Integer
DiagramServices = ActiveDocument.DiagramServicesEnabled
ActiveDocument.DiagramServicesEnabled = visServiceVersion140 + visServiceVersion150
Dim vsoCharacters1 As Visio.Characters
Set vsoCharacters1 = Application.ActiveWindow.Page.Shapes.ItemFromID(228).Characters
Debug.Print vsoCharacters1
I need the code to first find all the work flow objects in different pages in visio and then obtain the sentence within (vsoCharacters1)
Please try this simple code
Sub ttt()
Dim doc As Document ' Variable for Document
Dim pg As Page ' Variable for Page
Dim shp As Shape ' Variable for Shape
Dim txt As String ' Variable for Shape's text
For Each doc In Documents ' Iterate all documents in Visio application session
For Each pg In doc.Pages ' Iterate all pages in 'doc'
For Each shp In pg.Shapes ' Iterate all docunents in 'pg'
txt = shp.Text ' Define 'txt' variable
Select Case txt ' Criterion
Case "ololo", "trololo" ' Found text
ActiveWindow.Page = pg ' Activate page with criterion
ActiveWindow.Select shp, visSelect ' Select shape with criterion
MsgBox "Page: " & pg.Name & ", ShapeID: " & shp.ID, , "A shape was found, the text of which matches the criterion: " & txt
End Select
ActiveWindow.DeselectAll ' Unselect a shape
Next shp
Next pg
Next doc
MsgBox "TheEnd!!!"
End Sub
Note:
This code started in MS Visio, code without recursion, dont find shapes into groups !
May I propose a more systematic approach?
Drawing explorer
Make sure you're in developer mode.
Switch the drawing explorer on.
Identify the shape to explore
Expand its tree to see its sub-shapes
If you're lucky a pro has made this shape and named the subshapes eg Label, Frame, what ever. This will simplify the access to this shape.
in VBA:
shp being your group shape object
access the sub-shape via: set subshp = shp.Shapes(name_of_subshape)
This works also for the sub-shapes of the sub-shape.
Otherwise - the sub-shapes are named sheet.234 - you need to find another identification method.
Open the shapesheet of the sub-shape (right-mouse-click)
Inspect it and try to figure out in how far it differs from the other sub-shapes. That can be a text, user or prop field, a geometry section ... etc.
in VBA you would then loop over all the sub-shapes and check for this property.
eg:
for each subshape in shp.Shapes:
if subshape.CellExists("soAndSo",0) then
if subshape.Cells("soAndso").ResultStr("") = "thisAndThat" then
'you found it, do your stuff.
By the way, you don't need to access the characters object of a shape to get its text. It is simply "shp.Text". The characters object is more complexe and lets you do funny stuff with the text.

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

Excel VBA Chart Positions not constant

I have a worksheet where I create graphs in VBA and then place them according to named ranges in the worksheet.
graph3.Height = gRange.Height
graph3.Width = gRange.Width
graph3.Top = gRange.Cells(1, 1).Top
graph3.Left = gRange.Cells(1, 1).Left
It used to work fine but for some reason the graph no longer is positioned in the correct location. When I set a a stop point on any of the four lines and then continue to run the procedure, the graph returns to the correct position. Has anyone ever experienced this and how can I correct it?

Some doubts about Dim and Set Worksheet

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

Add multiple signature blocks into excel document using vba

I'm using vba to create an excel document and fill it in dynamically (already completed and working perfectly). What I need is: to figure out how to add, size, position, and prefill (suggested signer, email, but not the signature itself) the signature block at multiple locations in this document.
I don't even know if this can be done with vba (my searches on the subject have been unhelpful), but I'm hopeful as it will save me a lot of time and tedious work in the future. Any help on this would be welcome.
You may want to place simple text boxes across defined cells (as anchor points) and fill it with some text. To get you started here's the bare minimum that you need:
the actual text box creating Sub which takes all info as parameters:
Sub CreateShapeText(NailToCell As Range, w_pt As Single, h_pt As Single, DTxt As String)
Dim TB As Shape
' create a text box shape
' note: shapes belong to worksheets, therefore we derive a WS from cell.parent
Set TB = NailToCell.Parent.Shapes.AddLabel(msoTextOrientationHorizontal, NailToCell.Left, NailToCell.Top, w_pt, h_pt)
' make its border visible
TB.Line.Visible = msoTrue
' switch off that annoying auto-resize when text is entered
TB.TextFrame2.AutoSize = msoAutoSizeNone
' enter text ... and yes - this object tree is crazy
TB.TextFrame2.TextRange.Characters.Text = DTxt
' as it should be - text is vertical bottom
' but to have more control over the TB, this could be a parameter, too
TB.TextFrame2.VerticalAnchor = msoAnchorBottom
End Sub
and you would call that from wherever in your code as in below example
Sub CallCreate()
CreateShapeText [A1], 132, 32, "sign: me"
CreateShapeText [C12], 132, 32, "sign: you"
End Sub
You take it from here and research what these objects can do for you (e.g. make dotted lines instead of solid for the frame, experiment with font sizes, alignments etc.) and come back with more questions in case ...

Resources