Peltier Tech Blog

Excel Charting Utilities | Custom Solutions | Training | Charts and Tutorials | PTS Blog

 

Main menu:

 
Peltier Tech
Chart Utilities

PTS Waterfall Chart Utility
PTS Cluster-Stack Column Chart Utility
PTS Box and Whisker Chart Utility
PTS Marimekko Chart Utility
PTS Dot Plot Utility
PTS Cascade Chart Utility

Subscribe

Site search

Subscribe

Site search


Recent Posts

Recently Commented

Popular Posts

Archive


 

Categories


 

Buy me a coffee

If this topic or the whole blog has been helpful, support further development by treating me to a cup of coffee.


Privacy Policy

Creative Commons License
Licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.

Programming Excel 2007 and Excel 2010 AutoShapes with VBA (Guest Post)

by Jon Peltier
Tuesday, January 12th, 2010
Peltier Technical Services, Inc., Copyright © 2010.
Licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.

Today I am pleased to present a guest post written by my colleague Nicholas Hebb. Nick appears online frequently, commenting on Excel-related blogs and corresponding in the various forums.

Programming Excel 2007 and Excel 2010 AutoShapes with VBA

By Nicholas Hebb

With the release of Office 2007, Microsoft rewrote the drawing tools from the ground up. On the plus side, this meant new shapes, new styles, and the addition of SmartArt. On the downside, they didn’t have time to incorporate AutoShape operations in their macro recorder prior to release. Thankfully, Excel 2010 has added macro support back, but like all macros the code generated is often bloated and relies heavily on the Selection object, which tends to hide the core objects in use. For the most part, the online help is good, but there is some information that doesn’t get explained in detail. This article attempts to provide a basic overview of working with AutoShapes using VBA and touch on some areas that are not covered extensively in the help documentation.

Definitions

Two properties of the Shape object will be used in the code samples below – Shape.Type and Shape.AutoShapeType. Excel has a broad range of shape Types consisting not only of AutoShapes, but also connectors, lines, pictures, charts, comments, and many other graphical items. For AutoShapes, the AutoShapeType property lets you get/set the type of shape as shown in the gallery image below.

Excel 2010 Autoshapes Gallery

Knowing when to check the Shape.Type property versus the Shape.AutoShapeType is very useful. For example, if the AutoShapeType value is -2, then for all practical purposes the shape is not an AutoShape. If the value is greater than 1, then the shape is one of the types display in the Shapes gallery. The tricky part comes when the AutoShapeType is 1, which equals the AutoShape constant msoShapeRectangle. It could be a Rectangle AutoShape, but it could also be anything shaped like a rectangle, such as a text box, a comment, or even a picture. So if the AutoShapeType evaluates to 1, then you also need to check the Type property.

Callouts are another special type of shape that can cause confusion. They are discussed more in the Miscellaneous Issues section below.

Accessing a Shape Object

Each worksheet contains a Shapes collection consisting of Shape objects. Like other collections in VBA, the Shape object is accessed either via its name or index number, as in:

ActiveSheet.Shapes("SHAPE_NAME")

or

ActiveSheet.Shapes(1)

Or, using the For…Each syntax:

Dim shp as Shape
For Each shp in ActiveSheet.Shapes
  MsgBox shp.Name
Next

Adding an AutoShape

The syntax for adding a shape is:

Worksheet.Shapes.AddShape(AutoShapeType, Left, Top, Width, Height)

The AutoShapeType is a constant that ranges from 1 to 137 for Excel 2003 and earlier versions. Excel 2007 added shapes 139 through 183. AutoShapeTypes 125-136 are special AutoShapes. The online help file states that they support mouse over and click events, but that only applies when they are used in PowerPoint presentations. You can use them in Excel but they don’t have any special properties.

To see what the AutoShapeType constant is for each AutoShape, you can copy and paste the following code into the Excel Visual Basic Editor and run it (or download the sample file and run the macro). Not all the AutoShapes are available in the Shapes gallery, so this will also give you a look at some of the hidden ones.

Sub CreateAutoshapes()
  Dim i As Integer
  Dim t As Integer
  Dim shp As Shape

  t = 10
  For i = 1 To 137
    Set shp = ActiveSheet.Shapes.AddShape(i, 100, t, 60, 60)
    shp.TextFrame.Characters.Text = i
    t = t + 70
  Next
  ' skip 138 - not supported
  If CInt(Application.Version) >= 12 Then
    For i = 139 To 183
      Set shp = ActiveSheet.Shapes.AddShape(i, 100, t, 60, 60)
      shp.TextFrame.Characters.Text = i
      t = t + 70
    Next
  End If
End Sub

The Left, Top, Width, and Height parameters of AddShape() are specified in points. The origin is the top left corner of the worksheet, with the Left and Top values increasing to the right and down, respectively. Dealing with points on a worksheet isn’t intuitive, so if you prefer you can add a shape to a given range address by using code like this:

Function AddShapeToRange(ShapeType As MsoAutoShapeType, sAddress As String) As Shape
  With ActiveSheet.Range(sAddress)
    Set AddShapeToRange = ActiveSheet.Shapes.AddShape(ShapeType, _
        .Left, .Top, .Width, .Height)
  End With
End Function

Adding Text to an AutoShape

The Shape object has both a TextFrame and TextFrame2 members. The TextFrame2 member was added in Excel 2007 and gives better control over the formatting of the text. Because it is not backward compatible, I would recommend using the TextFrame object, as shown in the following code.

Sub AddFormattedTextToShape(oShape As Shape, sText As String)
  If Len(sText) > 0 Then
    With oShape.TextFrame
      .Characters.Text = sText
      .Characters.Font.Name = "Garamond"
      .Characters.Font.Size = 12
      .HorizontalAlignment = xlHAlignCenter
      .VerticalAlignment = xlVAlignCenter
    End With
  End If
End Sub

Setting Border and Fill Styles

If you take advantage of the built-in styles for Excel 2007 and Excel 2010, setting the AutoShape formatting is ridiculously easy compared to Excel 2003 and previous versions. Excel 2007 introduced the ShapeStyle property with the 42 preset styles shown below.

The style numbers can be set using a simple line of code:

Shape.ShapeStyle = msoShapeStylePresetXX

Where Shape is the shape object and XX is the style number. The style numbers are shown in the image gallery in order from left to right, top to bottom. For example, the red button in the second row msoShapeStylePreset10).

Adding Connectors and Lines

Connectors and lines are different objects in Excel. Connectors are special lines  that “connect” to shapes, and if the shape is moved the connector stays connected and reroutes accordingly. Connectors cannot connect to other connectors, but they can connect to the end point of a line.

The syntax for adding a line is straightforward:

Worksheet.Shapes.AddLine(BeginX, BeginY, EndX, EndY)

…with all coordinates as Singles. Adding a connector is a bit more complex, since you typically want it to connect two shapes. The code below calculates the begin and end points, creates the connector, attaches the connector to the two shapes, then finally does a reroute to ensure the shortest path.

Function AddConnectorBetweenShapes(ConnectorType As MsoConnectorType, _
    oBeginShape As Shape, oEndShape As Shape) As Shape

  Const TOP_SIDE As Integer = 1
  Const BOTTOM_SIDE As Integer = 3
  Dim oConnector As Shape
  Dim x1 As Single
  Dim x2 As Single
  Dim y1 As Single
  Dim y2 As Single

  With  oBeginShape
    x1 = .Left + .Width / 2
    y1 = .Top + .Height
  End With
  With oEndShape
    x2 = .Left + .Width / 2
    y2 = .Top
  End With
  If CInt(Application.Version) < 12 Then
    x2 = x2 - x1
    y2 = y2 - y1
  End If

  Set oConnector = ActiveSheet.Shapes.AddConnector(ConnectorType, x1, y1, x2, y2)
  oConnector.ConnectorFormat.BeginConnect oBeginShape, BOTTOM_SIDE
  oConnector.ConnectorFormat.EndConnect oEndShape, TOP_SIDE
  oConnector.RerouteConnections

  Set AddConnectorBetweenShapes = oConnector

  Set oConnector = Nothing 
End Function

Several points worth mentioning are:

  • The ConnectorType can be one of three constants – msoConnectorCurve, msoConnectorElbow, or msoConnectorStraight.
  • The calculations for the beginning and ending points are not normally needed. You could put any values in for the AddConnector() function because once you call BeginConnect and EndConnect, the connector is attached to the shapes and the begin and end points get set automatically.
  • How the end coordinates are specified is not consistent between Excel versions. Prior to Excel 2007, the end coordinates were relative to the begin coordinates. Starting in Excel 2007, the function now uses absolute coordinates.
  • When you route a Connector to an AutoShape, you need to specify the side using a connection site constant. The constants are different for each AutoShape type, but generally they start with the top side = 1 and go counter-clockwise. For example, most rectangular shapes have connection site constants where top = 1, left = 2, bottom = 3, and right = 4.
  • When you call the RerouteConnections() function, it sets the connection sides automatically in order to create the shortest path between the two shapes. So, unless you want a specific routing, you can get usually just guess at the connection site values then call RerouteConnections().

Formatting Connectors and Lines

Like AutoShapes, formatting Connectors and Lines is fairly straightforward in Excel 2007 and 2010. Here is a comparison of two formatting routines for older versions of Excel versus the newer versions:

Sub FormatConnector2003(oConnector As Shape)
  With oConnector
    If .Connector Or .Type = msoLine Then
      ' rough approximation of the Excel 2007 preset line style #17
      .Line.EndArrowheadStyle = msoArrowheadTriangle
      .Line.Weight = 2
      .Line.ForeColor.RGB = RGB(192, 80, 77)
      .Shadow.Type = msoShadow6
      .Shadow.IncrementOffsetX -4.5
      .Shadow.IncrementOffsetY -4.5
      .Shadow.ForeColor.RGB = RGB(192, 192, 192)
      .Shadow.Transparency = 0.5
      .Visible = msoTrue
    End If
  End With
End Sub

Sub FormatConnector2007(oConnector As Shape)
  With oConnector
    If .Connector Or .Type = msoLine Then
      .Line.EndArrowheadStyle = msoArrowheadTriangle
      .ShapeStyle = msoLineStylePreset17
    End If
  End With
End Sub

The Connector property, used above, returns a Boolean indicating whether the shape is a connector. The Type=msoLine statement checks if the shape is a line. In this case the code will format both connectors and lines the same way, but at times you may want handle them separately. (NB: The Insert Shapes gallery of Excel 2007 only lets you add Connectors, not Lines. So unless you are dealing with legacy files or add Lines via code, testing Type=msoLine may never be an issue for you.)

Like the shape styles, you can format the line style by setting the ShapeStyle to one of the msoLineStylePresetXX values, where XX matches the order they appear in the style gallery (below) from left to right, top to bottom.

The Line object has several other members worth mentioning. In addition to the EndArrowheadStyle shown above, there is a corresponding BeginArrowheadStyle property, a DashStyle property, and also a Style property that lets you create double lines.

Miscellaneous Issues

Here are a few Excel 2007 issues with AutoShapes that are good to be aware of :

  • If you copy a set of shapes, older versions of Office gave the option to Paste Special as editable AutoShapes in other Office applications. This option no longer exists in Office 2007. I haven’t tested this in Office 2010 beta yet.
  • In Excel 2007 and the 2010 beta, changing the AutoShape type will disconnect any incoming or outgoing Connectors to a shape.
  • Some print drivers (including PDF export handlers) do not handle printing thick Connectors well, e.g., elbow connectors may straighten. If this happens, either change the line thickness or try grouping all the shapes together prior to printing.
  • The Arc is an AutoShape but needs to be treated as a line when setting the ShapeStyle property.
  • Most of the styles are backward compatible except for styles 37-42 (the glossy button look in the bottom row of the style gallery). Styles 31-36 (the second row from bottom) do not render very well in Excel 2000.
  • You can add Callouts using the AddShape() or AddCallout() methods, but the AddCallout() method will only let you add four types, two of which are actually the same. Callouts have AutoShapeType values in the range of 105-124. Even though they have AutoShapeType values, the Shape.Type property will return msoCallout – not msoAutoShape. Confused? Wait, there’s more. The callouts with AutoShapeTypes from 105-108 actually return a Shape.Type = msoAutoShape – not msoCallout.

Sample File

The sample file includes three demo sheets. The ShapeDemo sheet contains a macro to add two shapes, format them, then add a connector and format it. The Animation sheet has a simple macro showing how to move a shape around the sheet. The CreateAutoShapes sheet has a macro to create all AutoShapes available in your version of Excel.

The ShapeDemo routine has two function calls that are commented out – IlluminateShapeText2003() and IlluminateShapeText2007(). These subs add some gaudy formatting to the first letter of each text block, but they serve to highlight some of the differences between Excel 2007 and previous versions. Two parts of the code worth looking at are the HasText property and the formatting properties of TextFrame2. With the old TextFrame object, you would have to try accessing the TextFrame.Characters.Count property, which throws an error if no text exists. As for the formatting, the font colors in Excel 2003 and previous were limited to the colors in the pallet. In Excel 2007 and 2010, you can add reflections, drop shadows, and glow as well as set the color to whatever RGB value your heart desires.

Lastly, there is a module MCommon containing a sub that deletes all the AutoShapes on a sheet. In order not to delete the command buttons on the sheet (which are shapes too), it creates a ShapeRange of the AutoShapes and deletes that. The online help file shows the syntax for creating a ShapeRange when you know the names of the shapes at design time, but the syntax is a bit tricky when creating one dynamically. The DeleteAllAutoShapes() sub in the sample file shows you how to do this.

Click Here to Download the Sample File

About the Author

FlowBreeze Flowchart SoftwareNicholas Hebb is the founder of BreezeTree Software and author of FlowBreeze Flowchart Software, an add-in that automates flowcharting in Excel.

Related Posts:

Bookmark and share this entry:
  • Twitter
  • Digg
  • Facebook
  • LinkedIn
  • del.icio.us
  • Technorati
  • StumbleUpon
  • Google Bookmarks
  • Reddit
  • MySpace
  • Slashdot
  • Yahoo! Buzz
  • Tumblr

Learn how to create Excel dashboards.

Comments


Comment from Prerna
Time: Friday, January 22, 2010, 7:00 am

Hi Jon,

I was reading the above article and downloaded the sample file and have a question here.

Function AddShapeToRange(ShapeType As MsoAutoShapeType, sAddress As String) As Shape
With ActiveSheet.Range(sAddress)
Set AddShapeToRange = ActiveSheet.Shapes.AddShape(ShapeType, _
.Left, .Top, .Width, .Height)
End With
End Function

-> Here sAddress is usually static like B2:D6. Can this field be dynamic? e.g. I have to create a diagram where Job A triggers Job B, Job C and Job D and then Job B triggers Job E. If I give B2:D6 as a static field and put the code in a loop it gives me an error at sAddress point. Is there anyway to do this?


Comment from Jon Peltier
Time: Friday, January 22, 2010, 7:57 am

Which line produces an error? What is the error? Is the active sheet a worksheet?

You can send any address to this function.


Comment from Prerna
Time: Friday, January 22, 2010, 8:18 am

Trying to use the below code:

Set oShape1 = AddShapeToRange(msoShapeFlowchartProcess, “B3:D5″)
AddFormattedTextToShape oShape1, “Job 1″

Set oShape2 = AddShapeToRange(msoShapeFlowchartAlternateProcess, “B9:D11″)
AddFormattedTextToShape oShape2, “Job 2″

Set oShape3 = AddShapeToRange(msoShapeFlowchartAlternateProcess, “B16:D18″)
AddFormattedTextToShape oShape3, “Job 3″

The problem here is we are hardcoding the number of shapes we want which is 3 here and hence the range like B3:D5, B9:D11,B16:D18.

What I am trying to do is put this in a For loop and repeat it n number of times. So obviously I cannot hard code the ranges and want to give something dynamic.

For i = 2 To 5
job_name = Range(“A” & i).Value
triby_job = Range(“D” & i).Value
trigjobname_array(i) = Cells(i, “F”).Value
Set oShape1 = AddShapeToRange(msoShapeFlowchartAlternateProcess, “B3:D5″)
AddFormattedTextToShape oShape1, Range(“A” & i).Value
Next
End Sub
Private Function AddShapeToRange(ShapeType As MsoAutoShapeType, _
sAddress As String) As Shape
With Flow.Range(sAddress)
Set AddShapeToRange = Flow.Shapes.AddShape(ShapeType, .Left, .Top, .Width, .Height)
End With
End Function

I am getting error at line “With Flow.Range(sAddress)”.
Run time error 91 object variable or with block not set.

Can you please suggest something so that the set shape statement can be executed in a loop?


Comment from Jon Peltier
Time: Friday, January 22, 2010, 8:46 am

What’s “Flow”? Is it the sheet where you want the shapes drawn? How does the program understand this?

In the modified calling sub (below), I construct a range address that includes the worksheet name, and in the function I determine the range, and use the parent of this range (the worksheet that the range is on) as the locaton of the added shape.

Sub Something()  Dim i As Long  Dim job_name As Variant  Dim triby_job As Variant   Dim trigjobname_array(2 To 5) As Variant  Dim job_address As String  Dim oShape1 As Shape  Dim wsData As Worksheet  Dim wsShapes As Worksheet   Set wsData = Worksheets("Data Sheet")  Set wsShapes = Worksheets("Shapes")   For i = 2 To 5    job_address = "'" & wsShapes.Name & "'!B" & 3 + (i - 2) * 3 & ":D" & 5 + (i - 2) * 3    job_name = wsData.Range("A" & i).Value    triby_job = wsData.Range("D" & i).Value    trigjobname_array(i) = wsData.Range("F" & i).Value    Set oShape1 = AddShapeToRange(msoShapeFlowchartAlternateProcess, job_address)  NextEnd Sub Private Function AddShapeToRange(ShapeType As MsoAutoShapeType, _    sAddress As String) As Shape  With Range(sAddress)    Set AddShapeToRange = .Parent.Shapes.AddShape(ShapeType, .Left, .Top, .Width, .Height)  End WithEnd Function


Comment from Prerna
Time: Friday, January 22, 2010, 12:15 pm

Yes, Flow is the worksheet where I am trying to create a flow diagram in the excel using this macro.

Table -> the worksheet which has the input in the below format:

Job name triggered by Job triggered job
in colmn A in Colmn D in colmn F

A B
B A C
D
D B E

above is an example input. Here Job A triggers Job B. Job B triggers C and D. D triggers job E. I am trying to create a Job flow diagram using shapes and I am stuck at the second job where B is triggering 2 jobs C and D where it involves to create 2 branches.

Appreciate your help on this Jon!!

Thanks again.


Comment from Prerna
Time: Friday, January 22, 2010, 12:28 pm

Hi Jon,

The above tabular column which is the input didnt come properly in the post. I have emailed you the same @ jonxlmvp@peltiertech.com


Comment from Jon Peltier
Time: Friday, January 22, 2010, 2:53 pm

Trying to program a flow chart generator goes beyond the scope of this post and its comments. Before spending too much time, search Google for ‘excel vba flow chart’, and you’ll see a lot of information from people who have already invented that wheel.


Comment from Brett
Time: Tuesday, January 26, 2010, 1:44 pm

Thank you soo much for this article. I have been searching and searching for some tips on working with drawing objects (because my usual record macro method wasn’t working) and this article explained how to do everything that, so far, I think I need to do to complete my project :)


Comment from jumpjack
Time: Friday, January 29, 2010, 5:59 pm

great article, thanks for helping working around MS dumbness…

I know about an excel add-on to get back the old-fashioned menu ( http://www.ubit.ch/software/ubitmenu-languages/ ), I wonder if also getting back “drawing recorder” would be possibile (maybe it’s just there but disabled, like menu?)


Comment from Jon Peltier
Time: Saturday, January 30, 2010, 9:55 am

Jumpjack -

The “old fashioned menu” add-in is like many others, which mimic the old menu arrangement as best they can, assuming the old features are still available. The “drawing recorder” is just not available in 2007. It has not just been disabled, it was not (yet) written for the new generation of shapes.

Write a comment

I welcome comments from my readers. If you have an opinion on this post, if you have a question or if there is anything to add, I want to hear from you. Whether you agree or disagree, please join the discussion.

If you want to include an image in your comment, post it on your own site or on one of the many free image sharing sites, and include a link in your comment. I'll download your image and insert the necessary html to display the image inline.

Read the PTS Blog Comment Policy.





Subscribe without commenting

PTS Waterfall Chart Utility PTS Cluster-Stack Column Chart Utility PTS Box and Whisker Chart Utility PTS Marimekko Chart Utility PTS Dot Plot Utility PTS Cascade Chart Utility

Create Excel dashboards quickly with Plug-N-Play reports.