Someone asked me today for a Python-only example illustrating how to automation the following in Visio: Drop two masters and connect them. I wasn’t sure if “Python-only” included IronPython, so I decided to refresh myself on how Python (specifically Python 2.6 with the Python Win32 Extensions) code would look to the IronPython 2.0 code.

Sample 1: IronPython 2.0

Note: it doesn’t make use of helper libraries like VisioAutoExt)

 

#
# Visio: Connecting Shapes - IronPython 2.0 Example
#

import sys
import clr
import System
clr.AddReference("Microsoft.Office.Interop.Visio")
import Microsoft.Office.Interop.Visio
IVisio = Microsoft.Office.Interop.Visio
visapp = IVisio.ApplicationClass()

doc = visapp.Documents.Add("")
page = visapp.ActivePage

stencilname = "basic_u.vss"
stencildocflags = IVisio.VisOpenSaveArgs.visOpenRO | IVisio.VisOpenSaveArgs.visOpenDocked
stencildoc = visapp.Documents.OpenEx(stencilname , stencildocflags )

masterrect = stencildoc.Masters.ItemU["rectangle"]
masteroctagon = stencildoc.Masters.ItemU["octagon"]
masterconnector= stencildoc.Masters.ItemU["dynamic connector"]

shape1 = page.Drop( masterrect, 1,1 )
shape2 = page.Drop( masteroctagon, 4,3 )
connector1 = page.Drop( masterconnector, -1,-1)

connector1.CellsU( "BeginX" ).GlueTo(shape1.CellsSRC(1, 1, 0))
connector1.CellsU( "EndY" ).GlueTo(shape2.CellsSRC(1, 1, 0))

 

Sample 2: Python 2.6 Version Using Python Win32 Extensions

 

#
# Visio: Connecting Shapes - Python 2.6 Example via COM
#

import sys
import win32com.client
win32com.client.gencache.EnsureDispatch("Visio.Application")
visapp = win32com.client.Dispatch("Visio.Application")
doc = visapp.Documents.Add("")
page = visapp.ActivePage

stencilname = "basic_u.vss"
stencildocflags = win32com.client.constants.visOpenRO | win32com.client.constants.visOpenDocked
stencildoc = visapp.Documents.OpenEx(stencilname , stencildocflags )

masterrect = stencildoc.Masters.ItemU("rectangle")
masteroctagon = stencildoc.Masters.ItemU("octagon")
masterconnector= stencildoc.Masters.ItemU("dynamic connector")

shape1 = page.Drop( masterrect, 1,1 )
shape2 = page.Drop( masteroctagon, 4,3 )
connector1 = page.Drop( masterconnector, -1,-1)

connector1.CellsU( "BeginX" ).GlueTo(shape1.CellsSRC(1, 1, 0))
connector1.CellsU( "EndY" ).GlueTo(shape2.CellsSRC(1, 1, 0))

 

Sample 3: IronPython 2.0 with VisioInteractive

And for all 3 of my regular readers, here is the IronPython code when using the VisioInteractive components of my VisioAutoExt library. Strictly speaking, it’s unfair to compare this to Sample 1 and Sample 2 because VisioInteractive is built to optimize such tasks.

 

#
# Visio: Connecting Shapes - IronPython via VisioInteractive shell
#

from visiointeractive import *

vi.Start()
vi.Document.New()
shape1 = vi.Drop.Master( "basic_u.vss", "rectangle" , 1,1 )
shape2 = vi.Drop.Master( "basic_u.vss", "octagon", 4,3 )
vi.Connect.Shapes( shape1, shape2 )

 

Differences Between the Python and IronPython Automation

NOTE: this only covers Sample 1 vs Sample 2. Sample 3 is not part of the discussion.

  • The Interactive Experience – Tab Completion on instances of COM objects
    • example: on the interactive shell type “shape1.” and then hit TAB
    • IronPython –>
    • Python –> it works. TAB will cycle through the members
      • not all Pythin interactive shells support TAB completion in the first place. TAB Completion is supported in
        • The interactive PythonWin shell
        • IPython (when a readline module has been installed)
  • Adding methods to classes at runtime –
    • example:

    def foo(self) :
        print “the Shape’s ID is”, self.ID

    • IronPython –> it does not work

      >>> shape1.__class__.foo = foo

      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: '__ComObject' object has no attribute 'foo'

    • Python –> it works.


        >>> shape1.__class__.foo = foo
        >>> shape1.foo()
        >>> the Shape’s ID is 1

  • COM constants - naming
    • example: visOpenRO
    • IronPython – they are found in Visio’s namespace and each is a member of a specfic enum – for example VisOpenSaveArgs.visOpenRO
    • Python – these are loaded into win32com.client.constants. The constants are all lumped in together and not part of distinct enums – not sure what would happen under name collision
  • COM constants – loading
    • IronPython – adding the reference and importing it “just worked” – the constants were visible to the script after that
    • Python - You see that call to win32com.client.gencache.EnsureDispatch? This is required to load the constants. If that call is not there, then win32com.client.constants will not contain the Visio constants. (Sure, the alternative is to run makepy before the script runs for the first time – but I hate doing that)
  • Indexers
    • example: stencildoc.Masters.ItemU
    • IronPython – to get the specific master one uses the square brackets like this: stencildoc.Masters.ItemU["rectangle"]
    • Python - to get the specific master one uses parenthesis like this: stencildoc.Masters.ItemU("rectangle")
  • Overall
    • The scripts are very, very similar
    • Barring other issues unique to IronPython – you should be able to convert between them very easily

 

Links