Is there a way to programmatically change the Z-order position of CanvasShapes in Word 2010 using VBA?

Viewed 336

I am rendering 3D shapes in Word 2010, and since my document has several animated diagrams, I decided to move the shapes onto a drawing canvas, one for each animation. In doing so, I revised my code to support both Shapes and CanvasShapes. I've resolved most of the issues except for one: the Z-Order method does not change the Z-order positioning of the CanvasShapes.

I have been searching for a reason over the internet but could not find any. Using just the two search terms "ZOrder" and "CanvasShape" did not amount to much. I did find an MSDN class description of CanvasShape which did not list "Zorder" as a class member, however the usage information for Zorder listed "CanvasShape" as a "Supported class". What does that mean if the method is not altering the z-order positions?

The small number of hits led me to believe that I was doing something completely out of the norm or/and I was missing something quite fundamental.

Below is my test routine:

Sub Test()
    With ActiveDocument.Shapes.AddCanvas(72, 72, 144, 144)
        .Name = "Test Canvas"
        .CanvasItems.AddShape(msoShapeRectangle, 0, 0, 36, 36).Name = "Shape 1"
        With .CanvasItems.AddShape(msoShapeRectangle, 0, 0, 36, 36)
            .Name = "Shape 2"
            Debug.Print .ZOrderPosition
            .ZOrder msoSendToBack
            Debug.Print .ZOrderPosition
        End With
    End With
End Sub

When executed in the Word VBE, a drawing canvas would be added to the active document with two canvas items. This macro will try to switch the Z-order positioning of the two. The z-order position of the second canvas shape would be printed in the immediate/debug window before and and after the switch attempt. The before and after values should be different if the zorder method is functioning properly. On my system, is not because the zorderposition does not change. I also noticed that I did not get an error message or any kind of stoppage. Is this what MSDN meant by "Supported class"?

As a workaround, I thought about cutting the CanvasShapes then paste them back onto the canvas in their proper Z-order, unfortunately this approach would blowup my allotted time slice. The manual process of "Bring Forward" and "Send Backwards" through the Word GUI still works for both classes, how would I mimic that?

1 Answers

After a month break, I began exploring some ideas. I first thought that, since the "Bring Forward" and "Send Backward" buttons still worked for shapes inside a drawing canvas, I could either take a peek at the code in the buttons' OnAction property or just simply invoke the appropriate button somehow.

I did find the three CommandBars, named "Order", "Ribbon" and "Selection and Visibility", that, I expected, should have these buttons I needed. Unfortunately, the OnAction properties were empty, and I did not see a way to trigger any of those buttons. In frustration, I accidentally hit the ALT- key which activated the ribbon, showing the keyboard shortcuts that could be used to trigger the various ribbon items currently visible. It finally dawned on me that I could still use keystrokes to select ribbon items, a functionality I thought was removed with the introduction of the ribbon. I came up with this prototype below: a method specifically for shapes in a drawing canvas that would also work for all objects, inside or outside a drawing canvas.

Sub Zorder(ByRef ShapeObject As Object, ByVal ZorderCmd As MsoZOrderCmd)
    'Add code here, if needed, to preserve last item selected
    ShapeObject.Select
    SendKeys "%", True: SendKeys "P", True
    SendKeys Array("AF", "AE")(ZorderCmd Mod 2) & _
        Mid("RKFBTH", ZorderCmd + 1, 1), True
    'Add code here, if needed, to restore last item selected
End Sub

Using the ribbon to format objects would require that the object be selected, hence triggering a SelectionChange or LostFocus event which may be an issue in some implementations, but can easily be addressed. I, however, had issues using the SendKeys statements as they requires the active document be the active, top-most window which, in my scenarios, was not guaranteed. I initially though I could just replace SendKeys with SendMessage or PostMessage. Painfully, I kept pursuing this approach until I read a post advising against using SendMessage or PostMessage to send messages to the word application.

I was lost. What now? I was about to post another question on the board when I did a final search. Somehow I landed on the usage page for GetVisibleMso, a member function of CommmendBars. The page discussed the idMso, explaining that the standard buttons on the ribbon were associated with a unique idMso. It also hinted that these buttons could be triggered using their idMso. So, I took a closer look at the CommandBars class using the Object Browser in the VBE and there it was: the ExecuteMso method with the idMSO being its only argument; it would trigger the corresponding button.

When I used the search terms "execute Ribbon item", I got better, more accurate, hits than using trigger or activate, and may be the reason it took a while to come up with a proper solution. The list of standard idMsos for Word can be found here!

With this, the change to my prototype would be as follows:

Application.CommandBars.ExecuteMso Array("ObjectBringToFront", _
    "ObjectSendToBack","ObjectBringForward", "ObjectSendBackward", _
    "ObjectBringInFrontOfText", "ObjectSendBehindText")(ZorderCmd)

One of the recommendations I read was to use Application.CommandBars. I also found that the tab/panel associated with the ribbon button being triggered need not be activated. The button just needs to be available (in other words, Enabled).

Related