Attribute VB_Name = "z_VsoUtilities" ' ====== Extension functions for Visio ===== ' Shared module version: 20220624 ' Tested in: TestVisio ' Depends on: API_UndoWrapper ' Required reference: Option Private Module Option Explicit Private Const CELLSU_STRUCTURE_TYPE = "User.msvStructureType" Private Const CELLSU_CONTAINER_RESIZE = "User.msvSDContainerResize" Private Const RESIZE_FIT_TO_CONTENTS = 2 Private Const SORT_PRECISION = 1# / 25.4 ' mm to inches Public Function GlobalUndo() As API_UndoWrapper Static s_Wrapper As API_UndoWrapper If s_Wrapper Is Nothing Then Set s_Wrapper = New API_UndoWrapper Call s_Wrapper.Init(Visio.Application) End If Set GlobalUndo = s_Wrapper End Function Public Function FindMaster(iSource As Visio.Document, sMaster$) As Visio.Master On Error Resume Next Set FindMaster = iSource.Masters.Item(sMaster) End Function Public Function SetupFixedGridPage(target As Visio.Page) With target.PageSheet .CellsU("XRulerOrigin").Formula = "=GUARD(0)" .CellsU("YRulerOrigin").Formula = "=GUARD(0)" .CellsU("XGridOrigin").Formula = "=GUARD(XRulerOrigin)" .CellsU("YGridOrigin").Formula = "=GUARD(YRulerOrigin)" End With End Function Public Function CellsExists(target As Visio.Shape, sCellName$) As Boolean CellsExists = target.CellExistsU(sCellName, visExistsAnywhere) End Function Public Function CellsGetValue(target As Visio.Shape, sCellName$) As Variant If Not CellsExists(target, sCellName) Then _ Exit Function CellsGetValue = target.CellsU(sCellName) End Function Public Function CellsSetValue(target As Visio.Shape, sCellName$, vNewValue As Variant) If Not CellsExists(target, sCellName) Then _ Exit Function target.CellsU(sCellName) = vNewValue End Function Public Function CellsGetFormula(target As Visio.Shape, sCellName$) As String If Not CellsExists(target, sCellName) Then _ Exit Function CellsGetFormula = target.CellsU(sCellName).Formula End Function Public Function CellsSetFormula(target As Visio.Shape, sCellName$, sFormula$) If Not CellsExists(target, sCellName) Then _ Exit Function target.CellsU(sCellName).Formula = sFormula End Function Public Function GetContainingShape(target As Visio.Shape) As Visio.Shape Dim vContainer As Variant For Each vContainer In target.MemberOfContainers Set GetContainingShape = target.ContainingPage.Shapes.ItemFromID(vContainer) Exit Function Next vContainer End Function Public Function VsoShapeExists(sShapeName$, iWhere As Visio.Page) As Boolean On Error GoTo RETURN_FALSE Dim iShape As Visio.Shape: Set iShape = iWhere.Shapes(sShapeName) VsoShapeExists = Not iShape Is Nothing Exit Function RETURN_FALSE: On Error GoTo 0 VsoShapeExists = False End Function Public Function VsoIsListContainer(target As Visio.Shape) As Boolean VsoIsListContainer = CellsGetFormula(target, CELLSU_STRUCTURE_TYPE) = """List""" End Function Public Function VsoIsMovable(target As Visio.Shape) As Boolean If target.OneD Then VsoIsMovable = False ElseIf CellsExists(target, CELLSU_CONTAINER_RESIZE) Then VsoIsMovable = CellsGetValue(target, CELLSU_CONTAINER_RESIZE) = RESIZE_FIT_TO_CONTENTS Else Dim shpContainer As Visio.Shape: Set shpContainer = GetContainingShape(target) If shpContainer Is Nothing Then VsoIsMovable = True Else VsoIsMovable = Not VsoIsMovable(shpContainer) End If End If End Function Public Function VsoIsConnected(iSource As Visio.Shape, iDestination As Visio.Shape) As Boolean VsoIsConnected = False If iSource.OneD Then _ Exit Function Dim iOuts() As Long: iOuts = iSource.ConnectedShapes(visConnectedShapesOutgoingNodes, "") Dim nItem& For nItem = LBound(iOuts) To UBound(iOuts) Step 1 If iOuts(nItem) = iDestination.ID Then VsoIsConnected = True Exit Function End If Next nItem End Function Public Function VsoApplyColorTo(target As Visio.Shape, nStart&, nEnd&, sColor$) Dim iChars As Visio.Characters: Set iChars = target.Characters iChars.Begin = nStart iChars.End = nEnd iChars.CharProps(visCharacterColor) = 1 iChars.End = nStart + 1 Dim nRow& Dim nPrevious&: nPrevious = -1 Do While iChars.End <= nEnd And iChars.Begin <> iChars.End nRow = iChars.CharPropsRow(visBiasLetVisioChoose) If nRow <> nPrevious Then nPrevious = nRow target.CellsSRC(visSectionCharacter, nRow, visCharacterColor).FormulaU = sColor End If iChars.Begin = iChars.End iChars.End = iChars.End + 1 Loop End Function ' Extracts selected shapes or all shapes on ActivePage if selection is empty Public Function VsoActiveShapes(iSource As Visio.Application) As Collection Dim iSelected As Visio.Selection: Set iSelected = iSource.ActiveWindow.Selection Dim iShapes As New Collection Dim aShape As Visio.Shape If iSelected.Count <> 0 Then For Each aShape In iSelected Call iShapes.Add(aShape) Next aShape Else For Each aShape In iSource.ActivePage.Shapes Call iShapes.Add(aShape) Next aShape End If Set VsoActiveShapes = iShapes End Function Public Function VsoGetSelectedShapes(vsoAppl As Visio.Application) Dim iShapes As New Collection Dim iSelection As Visio.Selection: Set iSelection = vsoAppl.ActiveWindow.Selection Dim iShape As Visio.Shape For Each iShape In iSelection Call iShapes.Add(iShape) Next iShape Set VsoGetSelectedShapes = iShapes End Function Public Function VsoSelectShapes(iShapes As Collection, iPage As Visio.Page) ActiveWindow.Page = iPage Dim iSelection As Visio.Selection: Set iSelection = ActiveWindow.Selection Call iSelection.DeselectAll Dim iShape As Visio.Shape For Each iShape In iShapes Call iSelection.Select(iShape, visSelect) Next iShape ActiveWindow.Selection = iSelection End Function Public Function VsoSelectShapesIDs(iShapeIDs As Collection, iPage As Visio.Page) Dim iSelection As Visio.Selection: Set iSelection = iPage.Application.ActiveWindow.Selection Call iSelection.DeselectAll Dim nShapeID As Variant For Each nShapeID In iShapeIDs Call iSelection.Select(iPage.Shapes.ItemFromID(CLng(nShapeID)), visSelect) Next nShapeID iPage.Application.ActiveWindow.Selection = iSelection End Function Public Function VsoClearPage(target As Visio.Page) Do While target.Shapes.Count > 0 Call target.Shapes(1).Delete Loop End Function Public Function VsoShowWholePage(target As Visio.Page) target.Application.ActiveWindow.Page = target target.Application.ActiveWindow.ViewFit = visFitPage End Function Public Function VsoCenterViewOn(target As Visio.Shape) Call target.Application.ActiveWindow.ScrollViewTo(target.CellsU("PinX"), target.CellsU("PinY")) End Function Public Function VsoIncrementWidth(target As Visio.Shape, valueInMM&) If target.OneD Then _ Exit Function If TryIncrementContained(target, valueInMM) Then _ Exit Function Dim dWidth As Double: dWidth = target.CellsU("Width") If VsoIsListContainer(target) Then Dim iMember As Visio.Shape Dim memberID As Variant For Each memberID In target.ContainerProperties.GetListMembers Set iMember = target.ContainingPage.Shapes.ItemFromID(memberID) Call iMember.Resize(visResizeDirE, valueInMM, visMillimeters) Next memberID End If If dWidth = target.CellsU("Width") Then _ Call target.Resize(visResizeDirE, valueInMM, visMillimeters) End Function Public Function VsoCalculateFillingFor(target As Visio.Page) As Double ' returns percentage of used space on the page ' does not account for shapes overlapping Dim sumAreas As Double: sumAreas = 0 Dim aShape As Visio.Shape For Each aShape In target.Shapes If VsoIsMovable(aShape) Then _ sumAreas = sumAreas + aShape.CellsU("Width") * aShape.CellsU("Height") Next aShape Dim dSheetArea As Double dSheetArea = target.PageSheet.CellsU("PageWidth") * target.PageSheet.CellsU("PageHeight") If dSheetArea <> 0 Then _ VsoCalculateFillingFor = sumAreas / dSheetArea End Function ' Sort by topmost and leftmost position of a shape Public Function VsoGeometricSort(ByRef target() As Long, iPage As Visio.Page) If Not IsArrayAllocated(target) Then _ Exit Function Call GeometricQuickSort(iPage, target, LBound(target, 1), UBound(target, 1)) End Function ' ========== Private Function TryIncrementContained(target As Visio.Shape, valueInMM&) As Boolean TryIncrementContained = False Dim wrapper As Visio.Shape: Set wrapper = GetContainingShape(target) If wrapper Is Nothing Then _ Exit Function If Not VsoIsListContainer(wrapper) Then _ Exit Function Call VsoIncrementWidth(wrapper, valueInMM) TryIncrementContained = True End Function Private Function GeometricQuickSort(iPage As Visio.Page, ByRef target() As Long, ByVal nLow&, ByVal nHigh&) Do While nLow < nHigh Dim nPivot&: nPivot = GSPartition(iPage, target, nLow, nHigh) Call GeometricQuickSort(iPage, target, nLow, nPivot - 1) nLow = nPivot + 1 Loop End Function Private Function GSPartition(iPage As Visio.Page, ByRef target() As Long, nLow&, nHigh&) As Long Dim iPivot As Visio.Shape: Set iPivot = iPage.Shapes.ItemFromID(target(nHigh)) Dim nSmallest&: nSmallest = nLow - 1 Dim iTempValue As Variant Dim n& For n = nLow To nHigh - 1 Dim iShape As Visio.Shape: Set iShape = iPage.Shapes.ItemFromID(target(n)) If GeometricCompare(iShape, iPivot) < 0 Then nSmallest = nSmallest + 1 iTempValue = target(nSmallest) target(nSmallest) = target(n) target(n) = iTempValue End If Next n nSmallest = nSmallest + 1 iTempValue = target(nSmallest) target(nSmallest) = target(nHigh) target(nHigh) = iTempValue GSPartition = nSmallest End Function Private Function GeometricCompare(iShape1 As Visio.Shape, iShape2 As Visio.Shape) As Double GeometricCompare = iShape2.CellsU("PinY") + iShape2.CellsU("Height") / 2# - iShape1.CellsU("PinY") - iShape1.CellsU("Height") / 2# If Abs(GeometricCompare) > SORT_PRECISION Then _ Exit Function GeometricCompare = iShape1.CellsU("PinX") - iShape1.CellsU("Width") / 2# - iShape2.CellsU("PinX") + iShape2.CellsU("Width") / 2# If Abs(GeometricCompare) > SORT_PRECISION Then _ Exit Function GeometricCompare = 0 End Function