Visio Add-in QueueMarker action creates 1 shape, Redo action creates 2 shapes!

Started by Visisthebest, August 20, 2021, 12:02:53 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Visisthebest

On Visio shapes on a Visio page, the only way to add Office Add-in functionality to the right-click mouse menu on Visio shapes is to use the QUEUEMARKEREVENT shapesheet function.

Unfortunately, this way of letting the user use functionality on a Visio shape is causing issues with the Undo/Redo system.

I have made some sample code below that can be dropped in to a new Visio VSTO VB.NET Project in Visual Studio (I use 2019, Visio also version 2019)

Compile the VSTO add-in and when Visio starts up, choose a Basic Diagram as that opens up the stencil I use for the test.

Drop one shape on the page and edit the ShapeSheet to add an Action Section, as per the screenshot below this post.

On this shape in the right-click menu you will find the "Test" menu item. Run this item and a new Square shape will be added to the page.

Now use Undo and then perform a Redo. If you move the new shape you will see there is an identical square shape below it. A Redo doesn't add one but actually two new shapes.

I think I am misunderstanding something about how the QueueMarker events work I am very much just an amateur developer, hopefully someone knows how to fix this robustly as reliable Undo/Redo behavior is very important for a good add-in application. Thank you for sharing your insights!


    Imports System.Windows.Forms
    Imports System.Diagnostics
    Imports System.Text
    Imports Visio = Microsoft.Office.Interop.Visio

    Partial Public Class ThisAddIn
   
        Private Sub Application_MarkerEvent(app As Visio.Application, SequenceNum As Integer, ContextString As String) Handles Application.MarkerEvent
   
            Dim Stencil As Visio.Document
   
            Stencil = GetStencil()
   
            If Stencil Is Nothing Then
                MessageBox.Show("The Basic Shapes stencil is closed, please open it before running this action.")
                Exit Sub
            End If
   
            Dim Square As Visio.Shape
   
            Square = Application.ActivePage.Drop(Stencil.Masters.ItemU("Square"), 0, 0)
   
        End Sub
   
        Private Function GetStencil() As Visio.Document
   
            Dim DocCounter As Long
   
            For DocCounter = 1 To Application.Documents.Count
   
                If Application.Documents(DocCounter).Type = Visio.VisDocumentTypes.visTypeStencil Then
   
                    If Application.Documents(DocCounter).Name = "BASIC_M.vssx" Or Application.Documents(DocCounter).Name = "BASIC_U.vssx" Then
   
                        'MessageBox.Show(Application.Documents(DocCounter).Name)
   
                        Return Application.Documents(DocCounter)
   
                        Exit Function
   
                    End If
   
                End If
   
            Next DocCounter
   
            Return Nothing
   
        End Function
   
    End Class



https://docs.microsoft.com/en-us/office/client-developer/visio/queuemarkerevent-function
Visio 2021 Professional

Visisthebest

(Part of) the issue may be that Visio, after an Undo of such a use action initiated via QUEUEMARKEREVENT, upon Redo actually creates two actions please see screenshot attached.

The question then is, how to prevent this from happening? It is impossible to (reliably) expand an UndoScope to include the QUEUEMARKEREVENT that initiated the action (dropping a shape in this case) as far as I can imagine.
Visio 2021 Professional

Nikolay

If you want the UNDO to work properly, why not simply avoid automatic stuff like auto-assigning shape GUID. I'd just make that GUID myself. Creating a GUID in .NET is absolutely trivial: Guid.NewGuid()

Paul Herber

Electronic and Electrical engineering, business and software stencils for Visio -

https://www.paulherber.co.uk/

Visisthebest

Nikolay I removed the .UniqueID from the test code same problem as above still.
Visio 2021 Professional

Visisthebest

Visio 2021 Professional

Visisthebest

Visio 2021 Professional

Nikolay

Hmmm sorry, probably I don't quite get what you are trying to accomplish. Do you want to provide a context menu item for a shape (that runs your code when clicked?)
It looks like you don't need any of QUEUEMARKEREVENT or guids to do that. You can simply declare the context menu, maybe?
You can declare custom context menu items using <ContextMenu> element in your extension's ribbon (custom user interface) definition file.
QuoteNikolay I removed the .UniqueID from the test code same problem as above still.
if you could provide the code that is self-sufficient and not a fragment, that could be helpful.

Just to clarify undo/redo a bit, maybe. Visio is responsible for undoing/redoing all the shape actions, it will undo/redo all shape operations itself just fine (even those which were actually initiated by your code)
You should not try to undo or redo shape-related actions yourself. Typically, your application should leave all undo/redo activity to Visio and not mess with it.

If you want to see a single entry in the "undo list", i.e. group all your action as one "logical action" for a user, you can use undo scopes ("begin undo scope" / "end undo scope")
The macro recorder gives a good example how to use them, i.e. you could just record any macro with it and then look at the code generated.

If you want to know if some of your code is currently running as a result of undo/redo, there is also a property named "IsUndoingOrRedoing",
but this is probably for some advanced scenarios, and you must be doing something really complex to have a need for that one.
The "undo scopes" are probably good enough in 99% of the cases.

In a custom undo/redo handler (in case you created one) you should only undo/redo actions that are NOT related to Visio,
like adding/deleting records in some database or something like this.



A side note: there is actually no need to check the stencil is open, you can just always "open" it, if it's arleady open it will just stay like this.
So, your code in the first post could be probably simplified to the "iconic" code (keeping the same efficiency):


Set Stencil = Application.Documents.OpenEx("basic.vss", visOpenDocked + visOpenRO)

Set Square = Application.ActivePage.Drop(Stencil.Masters.ItemU("Square"), 0, 0)

Note: even though the file "basic.vss" does not physically exist, it's fine, it's a "logical" historical name for this stencil, so Visio should be able to find and open it just fine.

Visisthebest

Hi Nikolay,

Thank you for the extensive answer, great tip about opening a standard stencil like the Basic Shapes much simpler this way!

Actually you have all the code you need above to reproduce the issue, I have reduced it to the minimal example in which the issue occurs exactly the same way. This way I discovered I wrongly concluded it was related to the UniqueID.

The code above also works fine in your excellent Visio VSTO Add-in template for VB.NET (the VS2017/2019 version of your template).

I already use Undoscopes for anything that performs more than one change to a Visio page, but that does not solve the issue.

Is there a way to run code in the add-in from a shape's context menu that doesn't require QUEUEMARKEREVENT (unfortunately for the non-profit I make the solution for they cannot use any VBA on their locked down desktops).

In any case I'd be really interested to know if you can reproduce this issue with the code I provided. Thank you very much for looking in to this Nikolay!
Visio 2021 Professional

Nikolay

Yes, sorry it seems I must have misread it, your instructions are perfect, one just need to start with the "New Visio VSTO AddIn (VisualBasic)" as a starter point (it is a built-in Visual Studio template, not mine, as far as I can tell?).

Yes, I can reproduce the behavior you describe. You become two actions in the undo list,
- the action itself.
- the drop operation (that adds new shape)

I would guess this is due to the nature of the "QUEUEMARKEREVENT" - it queues the event, i.e. executes your code AFTER the main action ("execute action") completes.
So, you get two actions - first is "execute menu item" and then the second one (deferred) "drop shape".

You can run add-in code from a context menu, by defining a custom context menu item.
QUEUEMARKEREVENT (or any modification to shapes) is not required for this approach to work.
Maybe there is a misunderstanding, QUEUEMARKEREVENT (or RUNADDON / RUNADDONWARGS) is required to run add-in code from a shapesheet FORMULA, as far as I understand. But if all you need is a custom context menu item, you can just define it in your add-in.

I would say providing menu item directly is a bit more flexible than providing it using the "Actions" shapesheet section because:
- you can set an image for your menu item (you could even have a dynamic image or text)
- you can have a long tooltip/description/help for your menu item
- you can enable/disable your menu item depending on what's selected (or whatever logic your app uses)
- show/hide your menu item depending on your app logic
- have a checkbox or gallery for your menu item.
- you don't need to modify user stencils or shapes, if you want your item to be available on those.
- you may have it not not only in the shape context menu, but basically in any context menu (page, blank area, tab, shapesheet, whatever - basically you specify in which context menus you want your item to be)
- the uno thing should work as you expect :)

Please note that having a context menu item defined with custom logic behind may have some impact to the performance (depending on what kind of logic you put there). Since Actions approach has no logic, it will be much more performant for sure. May be important for example in case your diagram has thousands of shapes, and you want the custom context menu only for a single shape out of those.

They have added the ability to define custom menu items around 10 years ago, you can see there the context menus in which you may have your item: http://visguy.com/vgforum/index.php?topic=1593.0

Visisthebest

Hi Nikolay,

Thank you for checking yes good to know this is expected behavior! In any case this is not good, and I'm very happy I now realize this is a really big issue with adding context menus this way.

I didn't know about the other context menu option, I thought CALLTHIS/QUEUEMARKEREVENT etc was the only way to make a context menu so very good to know!

Even if this requires some performance the alternative of problematic Undo/Redo behavior is definitely worse.

Thank you for the link I will read up on building a context menu that way, and yes I will probably keep it simple to keep it performing well.

Visio 2021 Professional

Nikolay


Visisthebest

Hi Nikolay,

Thank you so much yes very interested will check it out right away will let you know! :D
Visio 2021 Professional

Visisthebest

Hi Nikolay,

Thank you this is excellent example code for Visio context menus, I downloaded your VS solution files from Github.

You demonstrate the context menu being specific for specific kinds of shapes which is exactly what I need, I think this is relevant for most Visio add-in solutions where there are different kinds of shapes used in the diagram and require context-specific functionality for this kind of shape.

I have the question also on Stackoverflow, if you add this answer I will mark it as THE answer of course! :)

https://stackoverflow.com/questions/68861850/visio-vsto-add-in-queuemarker-event-from-shapesheet-user-action-adding-one-shap

Visio 2021 Professional

Visisthebest

Again really happy with this solution thank you Nikolay, unreliable Undo/Redo behavior is just terrible especially because Visio has such a good Undo/Redo system!

Those poor Excel developers have this issue: running any VBA that changes a worksheet (even one cell) destroys the user's Undo history, and this just leads to a terrible User Experience.

Us Visio devs got it real good in that regard! :D
Visio 2021 Professional