Visio 2003 has a defect in the **LengthIU **property of a Shape object. This article discusses the bug, describes a work-around, and offers Visual Basic code to help you get back on track!

*Note: LengthIU for open paths works properly in Visio 2007 and 2010. This article concerns a problem with Visio 2003.*

## LengthIU: Just What We Were Looking for!

If you’ve gotten far enough into the Visio Object Model to have discovered the **LengthIU **shape property, then you are likely quite proud of yourself, and surely ecstatic that you don’t have to calculate path-lengths using any…um…er…MATH

But if you’re using Visio 2003, you might have encountered some strange results which have caused you to distrust this property…and start searching for your trig books. The problem is that the LengthIU property returns 0 for paths that are not closed.

## LengthIU Background

LengthIU is supposed to conveniently returns the total length of the geometry paths in a single shape. If a shape has multiple geometry sections, then the lengths of each section are totaled together.

LengthIU doesn’t iterate through the shapes in a group, however. So don’t be shocked if you get 0 as the LengthIU of a grouped shape. This is not the bug that we’re talking about. For groups, we would just iterate through the sub-shapes and sum all the individual lengths together.

The result that LengthIU returns is always in inches, which is the **I**nternal **U**nit system that Visio uses for all calculations. Since this IU number is in the scale of the drawing, you could use the result to find out the amount of cabling required for a new networking project, or the distances to nearest fire escapes in a building plan.

A simple example would be to take a 1-inch square and query LengthIU. Of course, we would get 4 as the result. Slightly more complicated, we might draw two such squares, and combine them using: **Shape > Operations > Combine**. This gives a single shape with two paths. The length of these paths adds up to 8.

As you can see in the illustrations, you can easily test using a snippet of VBA code. First, select a shape in the drawing window. Then hit **Alt + F11** to bring up the VBA editing environment. In the VBA Editor’s **Immediate** window, you can then type this line and hit return:

? Visio.ActiveWindow.Selection(1).LengthIU

You should see your result on the next line.

## Bugsville: An Open and Closed Issue

However, if we’re measuring distances, we’re probably not measuring closed polygons like circles or rectangles. More likely, we’re measuring connectors or multi-segmented lines, free-form curves, or, god-forbid, NURBS. These shapes are open; they’re non-closed; they have no insides; there ends don’t meet. And that’s where the trouble starts.

*In Visio 2003, open paths return a LengthIU = 0*.

As you can see; 0, 0, and 0! This is simply a bug. Microsoft has confirmed the bug, but I don’t know if there is any explanation other than “oops!”

### Don’t Give Up, There’s Hope!

To get LengthIU to work on open paths, we need to first close them.

It turns out that each Geometry section conveniently has a NoFill section that allows us to do this! Simply setting GeometryN.NoFill = 0 causes a shape to be closed, and LengthIU to return a value other than 0.

But the LengthIU result that we’ll get will then be too long: it will contain the distance from the last vertex to the first vertex, which doesn’t really exist on the path.

So the work-around algorithm goes something like this:

- Determine if a shape has any GeometryN.NoFill = 1 sections
- If not, then just use LengthIU as normal
- For each non-closed geometry section in a shape…
- Close the geometry section using NoFill = 0
- Calculate the distance between the first and last vertex of the section with help from Mr. Pythagoras: SQRT( (x2-x1)^2 + (y2-y1)^2 )
- Increment the sum of all of these distances

- Loop to next geometry section
- Get LengthIU for the altered shape
- Subtract the first-vertex-last-vertex distance sum from that value
- Restore the NoFill cells to their original state

### Closing Points

- The LengthIU bug applies to Visio 2003
- Visio 2003 Service Pack 3 (LINK!!) does not fix this problem
- I haven’t tested on Visio 2002 (Edit: it’s a Visio 2003-only bug)
- Visio 2007 has fixed the LengthIU problem (so upgrade!) AMAZON LINK
- There is no ShapeSheet function for calculating LengthIU, it’s a code-only feature. Of course you can always try to derive a trigonometric formula and put it in a User-defined cell…

## The Cure

Below is a listing of the code for the CLengthIuShape class. You can paste the whole thing into a VBA Class Module with the name “CLengthIuShape”.

**Note**: *when cut-and-pasting this code, you might have to do a search and replace on the apostrophe character–to replace the fancy, tilted apostrophe with the simple vertical one, so that VB will understand it.*

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | '// Class: CLengthIuShape Option Explicit On '// Constants for ShapeSheet sections, rows, cells: Private Const Sec_FirstGeo = Visio.VisSectionIndices.visSectionFirstComponent Private Const Row_FirstVertex = Visio.VisRowIndices.visRowVertex Private Const Row_Component% = Visio.VisRowIndices.visRowComponent Private Const Cell_NoFill% = Visio.VisCellIndices.visCompNoFill Private Const Cell_X = Visio.VisCellIndices.visX Private Const Cell_Y = Visio.VisCellIndices.visY '// Internal class variables: Private m_visShp As Visio.Shape Private m_collNonClosedGeoSects As Collection Private m_extraClosedDistances As Double Private m_iGeoCt As Integer Public Function LengthIU(ByVal visShp As Visio.Shape) As Double m_visShp = visShp '// Test for zero geometry sections: m_iGeoCt = m_visShp.GeometryCount If m_iGeoCt = 0 Then LengthIU = 0 Exit Function End If '// Get the closed and non-closed geometry sections: Call m_getNonClosedSects() '// If there are no non-closed geometry sections, then '// LengthIU should function as normal: If m_collNonClosedGeoSects.Count = 0 Then LengthIU = m_visShp.LengthIU Exit Function End If '// If we get this far, then we have non-closed geometry... Dim lUndoScopeId As Long Dim varNonClosedSec As Object m_extraClosedDistances = 0 '// Since we have non-closed geometry, we will have to temporarily '// set the NoFill cells to 1 to get a LengthIU reading. We will '// want to undo these actions after we've got our calculation, so '// we begin an UndoScope here: lUndoScopeId = m_visShp.Document.BeginUndoScope("LengthIU Fix") For Each varNonClosedSec In m_collNonClosedGeoSects Call m_alterNonClosedGeoSect(CInt(varNonClosedSec)) Next LengthIU = m_visShp.LengthIU - m_extraClosedDistances '// Now, end the UndoScope, and throw away the changes that we made: Call m_visShp.Document.EndUndoScope(lUndoScopeId, False) End Function Private Sub m_alterNonClosedGeoSect(ByVal iSec As Integer) Dim xR1 As Double, yR1 As Double, xRN As Double, yRN As Double Dim iLastVertex As Integer Dim mag As Double iLastVertex = m_visShp.RowCount(iSec) - 1 '// Get the values for the first and last vertices: xR1 = m_visShp.CellsSRC(iSec, Row_FirstVertex, Cell_X).ResultIU yR1 = m_visShp.CellsSRC(iSec, Row_FirstVertex, Cell_Y).ResultIU xRN = m_visShp.CellsSRC(iSec, iLastVertex, Cell_X).ResultIU yRN = m_visShp.CellsSRC(iSec, iLastVertex, Cell_Y).ResultIU '// Close the row: m_visShp.CellsSRC(iSec, Row_Component, Cell_NoFill).ResultIU = 0 '// Calculate the distance between the first and last point: mag = Math.Sqr((xRN - xR1) ^ 2 + (yRN - yR1) ^ 2) '// Add this distance to the sum of all the manipulated distances: m_extraClosedDistances = m_extraClosedDistances + mag End Sub Private Sub m_getNonClosedSects() Dim i As Integer Dim iSec As Integer m_collNonClosedGeoSects = New Collection '// Find the non-closed geometry sections: For i = 0 To m_iGeoCt - 1 iSec = Sec_FirstGeo + i If m_visShp.CellsSRC(iSec, Row_Component, Cell_NoFill).ResultIU <> 0 Then Call m_collNonClosedGeoSects.Add(iSec) End If Next End Sub Private Sub Class_Terminate() m_visShp = Nothing m_collNonClosedGeoSects = Nothing End Sub |

'// Class: CLengthIuShape Option Explicit On '// Constants for ShapeSheet sections, rows, cells: Private Const Sec_FirstGeo = Visio.VisSectionIndices.visSectionFirstComponent Private Const Row_FirstVertex = Visio.VisRowIndices.visRowVertex Private Const Row_Component% = Visio.VisRowIndices.visRowComponent Private Const Cell_NoFill% = Visio.VisCellIndices.visCompNoFill Private Const Cell_X = Visio.VisCellIndices.visX Private Const Cell_Y = Visio.VisCellIndices.visY '// Internal class variables: Private m_visShp As Visio.Shape Private m_collNonClosedGeoSects As Collection Private m_extraClosedDistances As Double Private m_iGeoCt As Integer Public Function LengthIU(ByVal visShp As Visio.Shape) As Double m_visShp = visShp '// Test for zero geometry sections: m_iGeoCt = m_visShp.GeometryCount If m_iGeoCt = 0 Then LengthIU = 0 Exit Function End If '// Get the closed and non-closed geometry sections: Call m_getNonClosedSects() '// If there are no non-closed geometry sections, then '// LengthIU should function as normal: If m_collNonClosedGeoSects.Count = 0 Then LengthIU = m_visShp.LengthIU Exit Function End If '// If we get this far, then we have non-closed geometry... Dim lUndoScopeId As Long Dim varNonClosedSec As Object m_extraClosedDistances = 0 '// Since we have non-closed geometry, we will have to temporarily '// set the NoFill cells to 1 to get a LengthIU reading. We will '// want to undo these actions after we've got our calculation, so '// we begin an UndoScope here: lUndoScopeId = m_visShp.Document.BeginUndoScope("LengthIU Fix") For Each varNonClosedSec In m_collNonClosedGeoSects Call m_alterNonClosedGeoSect(CInt(varNonClosedSec)) Next LengthIU = m_visShp.LengthIU - m_extraClosedDistances '// Now, end the UndoScope, and throw away the changes that we made: Call m_visShp.Document.EndUndoScope(lUndoScopeId, False) End Function Private Sub m_alterNonClosedGeoSect(ByVal iSec As Integer) Dim xR1 As Double, yR1 As Double, xRN As Double, yRN As Double Dim iLastVertex As Integer Dim mag As Double iLastVertex = m_visShp.RowCount(iSec) - 1 '// Get the values for the first and last vertices: xR1 = m_visShp.CellsSRC(iSec, Row_FirstVertex, Cell_X).ResultIU yR1 = m_visShp.CellsSRC(iSec, Row_FirstVertex, Cell_Y).ResultIU xRN = m_visShp.CellsSRC(iSec, iLastVertex, Cell_X).ResultIU yRN = m_visShp.CellsSRC(iSec, iLastVertex, Cell_Y).ResultIU '// Close the row: m_visShp.CellsSRC(iSec, Row_Component, Cell_NoFill).ResultIU = 0 '// Calculate the distance between the first and last point: mag = Math.Sqr((xRN - xR1) ^ 2 + (yRN - yR1) ^ 2) '// Add this distance to the sum of all the manipulated distances: m_extraClosedDistances = m_extraClosedDistances + mag End Sub Private Sub m_getNonClosedSects() Dim i As Integer Dim iSec As Integer m_collNonClosedGeoSects = New Collection '// Find the non-closed geometry sections: For i = 0 To m_iGeoCt - 1 iSec = Sec_FirstGeo + i If m_visShp.CellsSRC(iSec, Row_Component, Cell_NoFill).ResultIU <> 0 Then Call m_collNonClosedGeoSects.Add(iSec) End If Next End Sub Private Sub Class_Terminate() m_visShp = Nothing m_collNonClosedGeoSects = Nothing End Sub

You can also paste the code into a VB.Net class module, but you’ll need to clean up a few items first:

- Add a reference to the Visio type library to your project
- Wrap the code in a Public Class CLengthIuShape – End Class block
- Add the line “Imports Visio = Microsoft.Office.Interop.Visio” to the beginning of the file
- Remove the “Option Explicit” line
- Define the const types with ” As Short” before the equals sign
- Replace “Math.Sqr” with “System.Math.Sqrt” in the m_alterNonClosedGeoSect subroutine

You can test the class against a selected shape using the following code:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Public Sub TestSelectedShape() '// See if a shape is selected: If Visio.ActiveWindow.Selection.Count = 0 Then Exit Sub '// Get the selected shape: Dim shp As Visio.Shape shp = Visio.ActiveWindow.Selection(1) '// Use the new class to calcualte the '// length of the shape's paths: Dim liuShp As New CLengthIuShape Dim ln As Double ln = liuShp.LengthIU(shp) Debug.Print("Length of shape = " & ln & " inches.") End Sub |

Public Sub TestSelectedShape() '// See if a shape is selected: If Visio.ActiveWindow.Selection.Count = 0 Then Exit Sub '// Get the selected shape: Dim shp As Visio.Shape shp = Visio.ActiveWindow.Selection(1) '// Use the new class to calcualte the '// length of the shape's paths: Dim liuShp As New CLengthIuShape Dim ln As Double ln = liuShp.LengthIU(shp) Debug.Print("Length of shape = " & ln & " inches.") End Sub

al edlund says

Chris,

this was one that showed up in 2003,

al

Visio Guy says

Thanks Al!

It’s a 2003-only bug folks!

Bryan says

I have been looking for something like this for Visio 2003, but when I created the class, as per your directions, I kept getting errors, both compile time and run-time, the most common of which is: ‘Object variable or With block variable not set.

Mark Nelson (MS) says

Hi Chris,

Note that starting in Visio 2003, the LengthIU and AreaIU properties take an argument indicating whether sub-shapes should be included. Also note that there are special exclusions for Data Graphic callouts as mentioned in the Visio 2007 help topics.

http://msdn2.microsoft.com/en-us/library/ms196113.aspx

Mark

Visio Guy says

Bryan,

Are your quote marks the simple ones or the 66 99 quote marks? I noticed a problem when cutting from the blog and pasting in the VBA editor…

– Chris

Visio Guy says

Mark

Thanks for the tip on the arguments, don’t know how I missed those!

And the info in the link is cool. Foks, it looks like that in general, Data Graphic sub-shapes won’t be included in the LengthIU and AreaIU calculations! See Mark’s link above for more info!

– Chris

John says

This looks great, but I can’t quite get it to work. I get the old error 91, ‘Object variable or with block variable not set’ error here:

shp = Visio.ActiveWindow.Selection(1)

A few debugs show that there is an instance, and it can be assigned to shp if that is a Variant. But no joy if “Dim shp As Visio.Shape”

Any tips appreciated.

neilkeron says

John

I cant quite get it to work either.

If I change the statement shp = visio.activewindow.selction(1) to Set shp =visio.activewindow.selection(1), I get past the first error but then I get error 91 at ln = liuShp.LengthIU(shp), presumably because something has gone wrong in the function. If anyone can throw any light on this I would be grateful.

Regards

Neil Keron

stodds says

Greetings,

Were the issues raised here ever sorted? I am having trouble getting this to work too and not having used VB before ain’t helping!

Cheers

Steve

Visio Guy says

LengthIU on open paths works properly in Visio 2010.