Enhanced Visio Shape Placement

Visio has a few gaps in how they place shapes. You can align shapes on their edge or vertical/horizontal center line, but you can not stack shapes. You can use a guideline to “stack” two shapes, but anything more gets very complex. You can distribute shapes based on the first and last shape in a series, but you can not distribute with a fixed gap.

Luckily Visio has provided a solution, List Containers. Unfortunately it does not expose the magic to the UI, it is only available through the black magic of the Developer tool bar and VBA. To create a List Container, just draw a rectangle, open the shapesheet, add a User cell named msvStructureType with a value of “List”. Some other useful User cells for containers are…

The container only exposes these options in the ribbon.

It does not expose orientation, alignment or spacing.
It does have a good selection of options for margins.
Though it would be nice to allow the user to enter a different value for the margin.

So why not enhance the offering by creating a List Container with a right click menu exposing the extra user cell?

The first time I created a stack using a List container, I had fun selecting just the contained shapes with a rectangular selection. The ribbon has a Select Contents button in the Membership section. In the above example, it is grayed out because there was no content

So I have created a shape with Orientation, Alignment, Margin, Spacing, Flags and Resize menus. These are the options.

The ribbon does have a Margin menu, but the right click menu has three standard options and the ability to use a Shape Data value. A similar menu is added for a gap. So, the shape has these two Shape Data properties.

The Action section is…

Once you create the shape, you can play with it to see what the various user cells can do. Or you can just use it to stack shapes or distribute them with a fixed space.

Create a series of shapes and number them. Change the orientation and alignment as you add them to the container. Play with the margin and spacing. Open the Shape Data window, change the margin and gap values. Use the right click menu to use the new values.
You may have to use the arrow keys to reorder or add/delete a shape to see the effect.

So, finally the code.
I could have posted a Visio drawing including the shape, but I wanted to show how simple the process is and what is under the hood.

Public Sub CreateListContainer()
'Create a List Container shape
Dim i As Integer, shp As Visio.Shape

'User.msvSDContainerMargin - Distance between container boundary and member shapes
'User.msvSDContainerNoHighlight - Boolean value that suppresses the container highlighting when member shapes are selected or added to container
'User.msvSDContainerNoRibbon - Boolean value that hides the Container Tools contextual tab in the Ribbon for this container
'User.msvSDContainerResize - Automatic resize behavior for shape (0 = No automatic resize; 1 = Expand as needed; 2 = Always fit to contents)
'User.msvSDListAlignment - Determines how member shapes are aligned in list (0 = Left; 1 = Center; 2 = Right for vertical lists.  0 = Top; 1 = Middle; 2 = Bottom for horizontal lists.)
'User.msvSDListDirection - Direction that list arranges member shapes (0 = Left to Right; 1 = Right to Left; 2 = Top to Bottom; 3 = Bottom to Top)
'User.msvSDListSpacing - Distance between list members

Set shp = ActiveWindow.Page.DrawRectangle(0, 0, 1, 1)
shp.Cells("FillForegnd").FormulaU = "RGB(146,208,80)" ' green

'User cells - Visio needs special User cells to make the shape a List Container
shp.AddNamedRow visSectionUser, "msvSDContainerMargin", visTagDefault
shp.Cells("User.msvSDContainerMargin.Prompt").FormulaU = """Margin"""
shp.Cells("User.msvSDContainerMargin").FormulaU = 0
 
shp.AddNamedRow visSectionUser, "msvSDContainerNoHighlight", visTagDefault
shp.Cells("User.msvSDContainerNoHighlight.Prompt").FormulaU = """No Highlight"""
shp.Cells("User.msvSDContainerNoHighlight").FormulaU = 0

shp.AddNamedRow visSectionUser, "msvSDContainerNoRibbon", visTagDefault
shp.Cells("User.msvSDContainerNoRibbon.Prompt").FormulaU = """No Ribbon"""
shp.Cells("User.msvSDContainerNoRibbon").FormulaU = 0

shp.AddNamedRow visSectionUser, "msvSDContainerResize", visTagDefault
shp.Cells("User.msvSDContainerResize.Prompt").FormulaU = """Container Resize"""
shp.Cells("User.msvSDContainerResize").FormulaU = 2

shp.AddNamedRow visSectionUser, "msvSDListAlignment", visTagDefault
shp.Cells("User.msvSDListAlignment.Prompt").FormulaU = """List Alignment"""
shp.Cells("User.msvSDListAlignment").FormulaU = 0

shp.AddNamedRow visSectionUser, "msvSDListDirection", visTagDefault
shp.Cells("User.msvSDListDirection.Prompt").FormulaU = """List Direction"""
shp.Cells("User.msvSDListDirection").FormulaU = 2

shp.AddNamedRow visSectionUser, "msvSDListSpacing", visTagDefault
shp.Cells("User.msvSDListSpacing.Prompt").FormulaU = """Spacing"""
shp.Cells("User.msvSDListSpacing").FormulaU = 0

shp.AddNamedRow visSectionUser, "msvStructureType", visTagDefault
shp.Cells("User.msvStructureType.Prompt").FormulaU = """Structure Type"""
shp.Cells("User.msvStructureType").FormulaU = Chr(34) & "Container" & Chr(34)
shp.Cells("User.msvStructureType").FormulaU = Chr(34) & "Callout" & Chr(34)
shp.Cells("User.msvStructureType").FormulaU = Chr(34) & "List" & Chr(34)
 
'Shape Data
i = shp.AddRow(visSectionProp, visRowLast, visTagDefault)
shp.Section(visSectionProp).Row(i).NameU = "Margin"
shp.CellsSRC(visSectionProp, i, visCustPropsLabel).FormulaU = """Margin"""
shp.CellsSRC(visSectionProp, i, visCustPropsType).FormulaU = "2"
shp.CellsSRC(visSectionProp, i, visCustPropsPrompt).FormulaU = """Margin"""
shp.CellsSRC(visSectionProp, i, visCustPropsValue).FormulaU = "0"
i = shp.AddRow(visSectionProp, visRowLast, visTagDefault)
shp.Section(visSectionProp).Row(i).NameU = "Gap"
shp.CellsSRC(visSectionProp, i, visCustPropsLabel).FormulaU = """Gap"""
shp.CellsSRC(visSectionProp, i, visCustPropsType).FormulaU = "2"
shp.CellsSRC(visSectionProp, i, visCustPropsPrompt).FormulaU = """Gap betweem shapes"""
shp.CellsSRC(visSectionProp, i, visCustPropsValue).FormulaU = "0"

'create the action rows
'User.msvSDListDirection - Direction that list arranges member shapes
'(0 = Left to Right; 1 = Right to Left; 2 = Top to Bottom; 3 = Bottom to Top)
i = shp.AddNamedRow(visSectionAction, "Orientation", visTagDefault)
shp.Cells("Actions.Orientation.Menu").FormulaForceU = Chr(34) & "Orientation" & Chr(34)
shp.Cells("Actions.Orientation.Action").FormulaForceU = ""

i = shp.AddNamedRow(visSectionAction, "OrntL2R", visTagDefault)
shp.Cells("Actions.OrntL2R.Menu").FormulaForceU = Chr(34) & "Left to Right" & Chr(34)
shp.Cells("Actions.OrntL2R.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.OrntL2R.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListDirection),0)"
shp.Cells("Actions.OrntL2R.Checked").FormulaForceU = "User.msvSDListDirection=0"

i = shp.AddNamedRow(visSectionAction, "OrntR2L", visTagDefault)
shp.Cells("Actions.OrntR2L.Menu").FormulaForceU = Chr(34) & "Right to Left" & Chr(34)
shp.Cells("Actions.OrntR2L.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.OrntR2L.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListDirection),1)"
shp.Cells("Actions.OrntR2L.Checked").FormulaForceU = "User.msvSDListDirection=1"

i = shp.AddNamedRow(visSectionAction, "OrntT2B", visTagDefault)
shp.Cells("Actions.OrntT2B.Menu").FormulaForceU = Chr(34) & "Top to Bottom" & Chr(34)
shp.Cells("Actions.OrntT2B.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.OrntT2B.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListDirection),2)"
shp.Cells("Actions.OrntT2B.Checked").FormulaForceU = "User.msvSDListDirection=2"

i = shp.AddNamedRow(visSectionAction, "OrntB2T", visTagDefault)
shp.Cells("Actions.OrntB2T.Menu").FormulaForceU = Chr(34) & "Bottom to Top" & Chr(34)
shp.Cells("Actions.OrntB2T.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.OrntB2T.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListDirection),3)"
shp.Cells("Actions.OrntB2T.Checked").FormulaForceU = "User.msvSDListDirection=3"

'User.msvSDListAlignment - Determines how member shapes are aligned in list
'(0 = Left; 1 = Center; 2 = Right
'for vertical lists.  0 = Top; 1 = Middle; 2 = Bottom for horizontal lists.)
i = shp.AddNamedRow(visSectionAction, "Alignment", visTagDefault)
shp.Cells("Actions.Alignment.Menu").FormulaForceU = Chr(34) & "Alignment" & Chr(34)
shp.Cells("Actions.Alignment.Action").FormulaForceU = ""

i = shp.AddNamedRow(visSectionAction, "AlgnLeft", visTagDefault)
shp.Cells("Actions.AlgnLeft.Menu").FormulaForceU = Chr(34) & "Left" & Chr(34)
shp.Cells("Actions.AlgnLeft.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.AlgnLeft.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListAlignment),0)"
shp.Cells("Actions.AlgnLeft.Checked").FormulaForceU = "User.msvSDListAlignment=0"
shp.Cells("Actions.AlgnLeft.Invisible").FormulaForceU = "User.msvSDListDirection>1"

i = shp.AddNamedRow(visSectionAction, "AlgnCenter", visTagDefault)
shp.Cells("Actions.AlgnCenter.Menu").FormulaForceU = Chr(34) & "Center" & Chr(34)
shp.Cells("Actions.AlgnCenter.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.AlgnCenter.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListAlignment),1)"
shp.Cells("Actions.AlgnCenter.Checked").FormulaForceU = "User.msvSDListAlignment=1"
shp.Cells("Actions.AlgnCenter.Invisible").FormulaForceU = "User.msvSDListDirection>1"

i = shp.AddNamedRow(visSectionAction, "AlgnRight", visTagDefault)
shp.Cells("Actions.AlgnRight.Menu").FormulaForceU = Chr(34) & "Right" & Chr(34)
shp.Cells("Actions.AlgnRight.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.AlgnRight.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListAlignment),2)"
shp.Cells("Actions.AlgnRight.Checked").FormulaForceU = "User.msvSDListAlignment=2"
shp.Cells("Actions.AlgnRight.Invisible").FormulaForceU = "User.msvSDListDirection>1"

i = shp.AddNamedRow(visSectionAction, "AlgnTop", visTagDefault)
shp.Cells("Actions.AlgnTop.Menu").FormulaForceU = Chr(34) & "Top" & Chr(34)
shp.Cells("Actions.AlgnTop.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.AlgnTop.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListAlignment),0)"
shp.Cells("Actions.AlgnTop.Checked").FormulaForceU = "User.msvSDListAlignment=0"
shp.Cells("Actions.AlgnTop.Invisible").FormulaForceU = "User.msvSDListDirection<2"

i = shp.AddNamedRow(visSectionAction, "AlgnMiddle", visTagDefault)
shp.Cells("Actions.AlgnMiddle.Menu").FormulaForceU = Chr(34) & "Middle" & Chr(34)
shp.Cells("Actions.AlgnMiddle.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.AlgnMiddle.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListAlignment),1)"
shp.Cells("Actions.AlgnMiddle.Checked").FormulaForceU = "User.msvSDListAlignment=1"
shp.Cells("Actions.AlgnMiddle.Invisible").FormulaForceU = "User.msvSDListDirection<2"

i = shp.AddNamedRow(visSectionAction, "AlgnBottom", visTagDefault)
shp.Cells("Actions.AlgnBottom.Menu").FormulaForceU = Chr(34) & "Bottom" & Chr(34)
shp.Cells("Actions.AlgnBottom.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.AlgnBottom.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListAlignment),2)"
shp.Cells("Actions.AlgnBottom.Checked").FormulaForceU = "User.msvSDListAlignment=2"
shp.Cells("Actions.AlgnBottom.Invisible").FormulaForceU = "User.msvSDListDirection<2"

'User.msvSDContainerMargin - Distance between container boundary and member shapes
i = shp.AddNamedRow(visSectionAction, "Margin", visTagDefault)
shp.Cells("Actions.Margin.Menu").FormulaForceU = Chr(34) & "Margin" & Chr(34)
shp.Cells("Actions.Margin.Action").FormulaForceU = ""

i = shp.AddNamedRow(visSectionAction, "MrgNone", visTagDefault)
shp.Cells("Actions.MrgNone.Menu").FormulaForceU = Chr(34) & "None" & Chr(34)
shp.Cells("Actions.MrgNone.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.MrgNone.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerMargin),0)"

i = shp.AddNamedRow(visSectionAction, "MrgClose", visTagDefault)
shp.Cells("Actions.MrgClose.Menu").FormulaForceU = Chr(34) & "Close" & Chr(34)
shp.Cells("Actions.MrgClose.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.MrgClose.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerMargin),0.1)"

i = shp.AddNamedRow(visSectionAction, "MrgGap", visTagDefault)
shp.Cells("Actions.MrgGap.Menu").FormulaForceU = Chr(34) & "Gap" & Chr(34)
shp.Cells("Actions.MrgGap.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.MrgGap.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerMargin),0.5)"

i = shp.AddNamedRow(visSectionAction, "MgrShapeDate", visTagDefault)
shp.Cells("Actions.MgrShapeDate.Menu").FormulaForceU = Chr(34) & "Use Shape Data" & Chr(34)
shp.Cells("Actions.MgrShapeDate.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.MgrShapeDate.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerMargin),prop.Margin)"

'User.msvSDListSpacing - Distance between list members
i = shp.AddNamedRow(visSectionAction, "Spacing", visTagDefault)
shp.Cells("Actions.Spacing.Menu").FormulaForceU = Chr(34) & "Spacing" & Chr(34)
shp.Cells("Actions.Spacing.Action").FormulaForceU = ""

i = shp.AddNamedRow(visSectionAction, "SpNone", visTagDefault)
shp.Cells("Actions.SpNone.Menu").FormulaForceU = Chr(34) & "None" & Chr(34)
shp.Cells("Actions.SpNone.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.SpNone.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListSpacing),0)"

i = shp.AddNamedRow(visSectionAction, "SpGap", visTagDefault)
shp.Cells("Actions.SpGap.Menu").FormulaForceU = Chr(34) & "Gap" & Chr(34)
shp.Cells("Actions.SpGap.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.SpGap.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListSpacing),0.1)"

i = shp.AddNamedRow(visSectionAction, "SpShapeDate", visTagDefault)
shp.Cells("Actions.SpShapeDate.Menu").FormulaForceU = Chr(34) & "Use Shape Data" & Chr(34)
shp.Cells("Actions.SpShapeDate.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.SpShapeDate.Action").FormulaForceU = "=SETF(GetRef(User.msvSDListSpacing),prop.Gap)"

'User.msvSDContainerNoHighlight - Boolean value that suppresses the container highlighting when member shapes are selected or added to container
'User.msvSDContainerNoRibbon - Boolean value that hides the Container Tools contextual tab in the Ribbon for this container
'User.msvSDContainerResize - Automatic resize behavior for shape
'(0 = No automatic resize; 1 = Expand as needed; 2 = Always fit to contents)
i = shp.AddNamedRow(visSectionAction, "Flags", visTagDefault)
shp.Cells("Actions.Flags.Menu").FormulaForceU = Chr(34) & "Flags" & Chr(34)
shp.Cells("Actions.Flags.Action").FormulaForceU = ""

i = shp.AddNamedRow(visSectionAction, "FlgNoHighlight", visTagDefault)
shp.Cells("Actions.FlgNoHighlight.Menu").FormulaForceU = Chr(34) & "No Highlight" & Chr(34)
shp.Cells("Actions.FlgNoHighlight.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.FlgNoHighlight.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerNoHighlight),Not(User.msvSDContainerNoHighlight))"
shp.Cells("Actions.FlgNoHighlight.Checked").FormulaForceU = "User.msvSDContainerNoHighlight"

i = shp.AddNamedRow(visSectionAction, "FlgNoRibbon", visTagDefault)
shp.Cells("Actions.FlgNoRibbon.Menu").FormulaForceU = Chr(34) & "No Ribbon" & Chr(34)
shp.Cells("Actions.FlgNoRibbon.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.FlgNoRibbon.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerNoRibbon),Not(User.msvSDContainerNoRibbon))"
shp.Cells("Actions.FlgNoRibbon.Checked").FormulaForceU = "User.msvSDContainerNoRibbon"

i = shp.AddNamedRow(visSectionAction, "FlgResize", visTagDefault)
shp.Cells("Actions.FlgResize.Menu").FormulaForceU = Chr(34) & "Resize" & Chr(34)
shp.Cells("Actions.FlgResize.FlyoutChild").FormulaU = "False"
shp.Cells("Actions.FlgResize.Action").FormulaForceU = ""

i = shp.AddNamedRow(visSectionAction, "FlgResizeNo", visTagDefault)
shp.Cells("Actions.FlgResizeNo.Menu").FormulaForceU = Chr(34) & "No Automatic Resize" & Chr(34)
shp.Cells("Actions.FlgResizeNo.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.FlgResizeNo.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerResize),0)"
shp.Cells("Actions.FlgResizeNo.Checked").FormulaForceU = "User.msvSDContainerResize=0"

i = shp.AddNamedRow(visSectionAction, "FlgResizeExp", visTagDefault)
shp.Cells("Actions.FlgResizeExp.Menu").FormulaForceU = Chr(34) & "Expand as Needed" & Chr(34)
shp.Cells("Actions.FlgResizeExp.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.FlgResizeExp.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerResize),1)"
shp.Cells("Actions.FlgResizeExp.Checked").FormulaForceU = "User.msvSDContainerResize=1"

i = shp.AddNamedRow(visSectionAction, "FlgResizeNeed", visTagDefault)
shp.Cells("Actions.FlgResizeNeed.Menu").FormulaForceU = Chr(34) & "Fit to Content" & Chr(34)
shp.Cells("Actions.FlgResizeNeed.FlyoutChild").FormulaU = "True"
shp.Cells("Actions.FlgResizeNeed.Action").FormulaForceU = "=SETF(GetRef(User.msvSDContainerResize),2)"
shp.Cells("Actions.FlgResizeNeed.Checked").FormulaForceU = "User.msvSDContainerResize=2"
      
shp.Cells("DisplayLevel").FormulaU = -3000   ' and push it to the back
End Sub

Thank you John G for your help.

I hope this helps.

John… Visio MVP in x-aisle
JohnVisioMVP.ca

Published by johnvisiomvp

The original Visio MVP. I have worked with the Visio team since 1993

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s