SETATREF(Height) / EventXFMod Behavior

Started by MacGyver, April 23, 2024, 04:19:25 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

MacGyver

I'm attempting to run a macro when height of a shape is changed to dynamically adjust connection points.  I'm running into an issue that appears others have run into (similar topic)

I've attempted using CALLTHIS in a user cell like such:
=SETATREF(Height,CALLTHIS("ConnPtAdjust"))And the ConnPtAdjust macro gets executed anywhere from 4-20 times.  I've attempted moving the code to the EventXFMod Behavior as well and same result - code is executed multiple times. 

The real oddity is if i toss a break in there for debug purposes, code functionality works exactly as expected.  If no break, it executes between 4 and 20 times.  Even adjusted my code so that called routine only debug prints the time to verify it wasn't my code causing the error and same results.

I changed from CALLTHIS to use RUNMACRO (and forgot to remove the visShape as input) and it errored out multiple times.  I have added various wait times to my code which also doesn't fix the issue.  Attached the drawing with shape in question, and included code that gets executed.

Any thoughts/tips would be greatly appreciated!

MacGyver

#1
Another weird characteristic I've noticed is that when my macro is run multiple times. The various routine's appear to be running in parallel. As in one the first run of the routine may not have finished running before the second instance of the routine is executed.

I added code to debug.print start/stop times of an otherwise empty macro.  I then added some code that throws an intentional error.  The debug window shows that the macro started 6 times, and only ended once.  If the intentional error is removed, the macro was started some random number of times and ended some random number of times.

Yacine

#2
Your observation is right. When you drag something (shape or size handle) the event gets fired multiple times. You can observe this if you run the events manager.

Why not put the macro on an action command (right button menu, then deliberately run the command)

Other option, but slightly more complex. Let the macro know that you've finished the resizing.
Define a general variable to hold the time. You're macro checks first the time between the current call and the value stored. If difference is small (threshold), it stores the current time in the global variable and leaves, otherwise it runs the actually intended code.

'*******************************
Public lastcall As Double
'*******************************


Sub ConnPtAdjust(shp As Visio.Shape)
    ' this will add remove conection points from some shapes - Box, Device, EGSE after shape is resized
    ' this macro is called from the shapesheet of the resized shape
    ' shp is passed into this routine from when it is called within the shapesheet
   
    ' connections points for these shapes are defined from the top down (connection point 1 and 2 are top/bot mid; cp3,4 is upper most left/right; cp5,6 are cps below 3,4
    ' additional connection points req to be added if last connection point Y value is >= 0.875 inches
    ' connection points req to be deleted if last connection point Y values is < 0.875 inches
   
    Dim connPtRow As Integer    ' row identifier of last connection point
    Dim UndoScopeID1 As Long
    Dim lastCPYVal As Single    ' y value of last connection point (zero based)
    Dim rowsToAdd As Integer    ' number of connection point rows we need to add to vba formula is 0 based. for shapesheet formulas it is one-based
    Dim rowNew As Integer      ' the row we just added
    Dim i As Integer
       
    Dim currentTime As Date
    Dim milliseconds As Double
    currentTime = Now()
'*******************************
    If Timer - lastcall < 100 Then
      lastcall = Timer
      Exit Sub
    End If
'*******************************

....

Yacine

wapperdude

Couple additional notes: 
>  Your usage of SETATREF is not correct.  The cell reference is intended to be the target for action, not the trigger.  Instead, use CALLTHIS("ConnPtAdjust")+DEPENDSON("Height").  For reference, see https://learn.microsoft.com/en-us/office/client-developer/visio/setatref-function and https://visguy.com/vgforum/index.php?topic=6383.msg26308#msg26308  This applies to all 3 of your SETATREF formulae.

>  I'm having trouble getting the code to run manually, but don't have time to investigate.  However, I recommend deconstructiong it, without the error stuff and without the auto-triggering.  Just add rows and delete.  You can step thru using <F8>, and you can set execution breakpoints.  There should not be any re-triggering.  Keep the control point count low.  I'm not convinced the code fully restricts adding / deleting.  Then, add the +Dependson statement in shapesheet.  Keep breakpoints in code.  See if behavior is as desired.  Finally, remove breakpoints and let code fully run.

>  As resizing the height is the trigger, moving should not be an issue.  Also, duplicating should not be an issue if the code is properly checking row count and connection position. There should not be any retriggering, or if there is, it's of no consequence.
Visio 2019 Pro

MacGyver

Thanks guys!
@wrapperdude, I changed it up to CALLTHIS("ConnPtAdjust")+DEPENDSON("Height"), but code continues to get executed multiple times.
 
QuoteAlso, duplicating should not be an issue if the code is properly checking row count and connection position. There should not be any retriggering, or if there is, it's of no consequence.
I'm finding this not to be the case.  With additional debug messaging, I'm finding out what is happening is 1st execution of macro Adds a new point and before it is able to set its properties, a 2nd macro might add another point, while the 3rd might delete one of those points.  so then when 1/2 macro goes to set the value it is not setting the intended row. 

Think I'll be resorting to @Yaccine's solution of making use of the shape actions.  Thanks all for the assistance!

wapperdude

#5
I ran the macro from the shape with the Dependson syntax...confirmed the multiple executions.  Then, using Size and Position Window, typed in a value for height.  Execution was as expected for both increased and decreased heights.  The problem is dragging the height causes multiple triggers.

To eliminate the multiple triggers, the obvious is to disallow resizing by mouse drag.  Another approach might be to required mouse button up as necessary event.  Other approaches????

For the record, although I dont' recomme nd it, your SETATRREF syntax also works using keyboard value entry.
Visio 2019 Pro

Yacine

The code with the global variable works. Why not try it?
Yacine

wapperdude

Quote from: Yacine on April 24, 2024, 05:52:28 AMThe code with the global variable works. Why not try it?
How did you do this?  I tried and it made no difference,
Visio 2019 Pro

Yacine

#8
Did you add the two pieces of code marked red?
Yacine

wapperdude

Oh.  I thought you were referencing the original code.  So, no, I did not add the red stuff.
Visio 2019 Pro

wapperdude

#10
Alrighty.  Copied / pasted the red code.  Variable declaration as global, the little IF timer is 1st executable in the macro.  Still doesn't execute properly.  I did tbhis with original code, located in Module1.,,

However, I did put a code break to pause the code before IF statement executed, then let code run, and all is well.  So, the If statement is being recognized.  Perhaps the test value needs adjusting. 

Seems to me, catching moluse button up event ought to be, perhaps less PC dependent, more reliable/predictable.

Edit:  Also need to test for initial case when lastcall = 0.  This would pass the IF test, but give incorrect multi-looping.
Visio 2019 Pro

wapperdude

Well, the global delay thing doesn't work for me.  I did add a test for lastcall=0, which will occur 1st time code runs with new launch of Visio. The timing errors tend to be grouped together, but range all over the place.  Doesn't seem to be a pattern nor predictable.

Visio 2019 Pro

Thomas Winkel

#12
I store the status in a user field to prevent such feedback loops:
QuoteEventXFMod=IF(User.Processing,SETF(GetRef(User.Processing),FALSE),SETF(GetRef(User.Processing),TRUE)+CALLTHIS("DoSomething"))

And some demo VBA:
Sub DoSomething(shp As Visio.Shape)
    Debug.Print "Do something that triggers EventXFMod..."
    shp.Cells("Width").ResultIU = 2 * shp.Cells("Width").ResultIU
End Sub

Yacine

Isn't your solution handling only the first event?
We need to handle the last one.

Regarding the solution I posted, Wayne is right. The function gets called several times.

I consulted both ChatGPT and Gemini for a solution involving a timer object, but they did not manage to find a workable code.
In my idea the very first call would start the timer object and set a flag. As long as this flag is not reset do nothing.

A handler that the timer object triggers when the time is elapsed would then call the actual routine, then reset the flag.

Lost patience and dropped the idea. I don't know if someone else wants to pick it up.
Yacine

wapperdude

[quoteLost patience and dropped the idea][/quote]
I actually did spend a bit of time on this.  The concept seemed sound.  But ultimately, I think what happens is that the last event gets filtered by the timer too. After all, the system ought to be consistent in that regard.  Consequently, either all events are allowed to go thru or no events.  Using the timer did not allow the code to uniquely identify the last event.

What is it that makes the last event unique?  It is the mouse release (mouse up event) that immediately stops the dragging.  But there are a lot of mouse up events too.  So, the starting process is mouse down & shape selection & height change (& not 1D shape).  Followed by termination of mouse up.  What we take for granted is not a simple process.
Visio 2019 Pro