Triggering Immediate External Event Execution

We held our first West European Devday in Paris on Monday and I am now sitting in the airport headed for Milano.

En route to Milano, I worked at testing Joe Ye’s

trick to trigger the Idling event

(potential SetRaiseWithoutDelay workaround)
to force an

immediate execution of my external event
.

I also added a comment from Guy Robinson on
Revit requiring focus that
had me worried for a moment.

However, the solution is easy!

Before we get to that, here is our one and only photo from the DevDay event in Paris, a shot of beautiful evening sky at the Charles de Gaulle airport:

Evening sky at Charles de Gaulle airport

Triggering External Event Execute by Setting Focus

Let me recapitulate the situation: I can call the external event Raise method from an external thread, giving me complete control over timing and performance overhead.

I can see from my debug output log messages that Revit has received and taken account of the Raise and the external event is now marked as pending.

However, Revit does not call the external event Execute method until some time later, often when the mouse cursor is moved over its window area.

Guy mentions
Revit requiring focus.

That can easily be achieved using the Windows API SetForegroundWindow provided by user32.dll.

I use it in conjunction with its sibling GetForegroundWindow method, so I can restore focus back to the original window after activating Revit for an instant.

They can both be referenced from user32.dll using p/invoke like this:


  // DLL imports from user32.dll to set focus to
  // Revit to force it to forward the external event
  // Raise to actually call the external event 
  // Execute.
 
  /// <summary>
  /// The GetForegroundWindow function returns a 
  /// handle to the foreground window.
  /// </summary>
  [DllImport( "user32.dll" )]
  static extern IntPtr GetForegroundWindow();
 
  /// <summary>
  /// Move the window associated with the passed 
  /// handle to the front.
  /// </summary>
  [DllImport( "user32.dll" )]
  static extern bool SetForegroundWindow(
    IntPtr hWnd );

With those references in place, I can momentarily shift focus to Revit in the CheckForPendingDatabaseChanges method, immediately after raising the external event, and Revit forwards that call to the external event Execute method right away, just as I had hoped:


    if( rdb.LastSequenceNumberChanged(
      DbUpdater.LastSequence ) )
    {
      _event.Raise();
 
      ++_nUpdatesRequested;
 
      Util.Log( string.Format(
        "database update pending event raised {0} times",
        _nUpdatesRequested ) );
 
      // Set focus to Revit for a moment.
      // Otherwise, it may take a while before 
      // Revit forwards the event Raise to the
      // event handler Execute method.
 
      IntPtr hBefore = GetForegroundWindow();
 
      SetForegroundWindow(
        ComponentManager.ApplicationWindow );
 
      SetForegroundWindow( hBefore );
    }

With these three extra lines in place, Revit reacts immediately to my external event Raise.
This is also reflected in the log file (compare with the previous version

waiting for Revit to react
):


01:16:56.820 CheckForPendingDatabaseChanges loop 1 - check for changes 1
01:16:57.012 CheckForPendingDatabaseChanges loop 2 - check for changes 2
01:16:57.217 CheckForPendingDatabaseChanges loop 3 - check for changes 3
. . .
01:17:09.058 CheckForPendingDatabaseChanges loop 63 - check for changes 63
01:17:09.076 database update pending event raised 1 times
01:17:09.115 UpdateBim begin
01:17:09.216 UpdateBim end
01:17:09.283 CheckForPendingDatabaseChanges loop 64 - check for changes 64
01:17:09.457 CheckForPendingDatabaseChanges loop 65 - check for changes 65
01:17:09.562 CheckForPendingDatabaseChanges loop 66 - check for changes 66
. . .
01:17:19.144 CheckForPendingDatabaseChanges loop 153 - check for changes 153
01:17:19.249 CheckForPendingDatabaseChanges loop 154 - check for changes 154
01:17:19.305 database update pending event raised 2 times
01:17:19.333 UpdateBim begin
01:17:19.400 UpdateBim end
01:17:19.515 CheckForPendingDatabaseChanges loop 155 - check for changes 155
01:17:19.652 CheckForPendingDatabaseChanges loop 156 - check for changes 156
. . .

Much shorter, instantaneous reaction.

Yay!

Success!

Perfection!

As already explained, I much prefer this use of an external event over the Idling event, due to the detailed timing control over the polling frequency and possibility to run my polls in a separate thread, as opposed to the Idling all-or-nothing approach.

Download

I am quite happy about this and committed the changes implementing this version in the
RoomEditorApp GitHub repository
as

release 2014.0.0.19
.

I hope that solves this issue for good.

Hardly likely, though.

Anyway, I urgently need to get some sleep now before the Milano DevDay conference, due to begin in just a very few hours…

Again, wish me luck, guys.


Comments

5 responses to “Triggering Immediate External Event Execution”

  1. The danger of the proposed solution is that switching foreground windows might be done against the end user’s will and desire. Maybe the user did not want to go back to Revit for a while. Maybe she does not care about backend threads finishing just yet. Maybe she is chatting with her manager in a messenger window and the moment. If I were she I would be rather surprise finding that I’ve stop typing, apparently. As elegant as this solution may seem I would recommend using it very cautiously. In fact, I believe it should be used only (if at all) when using external events from a modeless dialog upon some explicit request/action of the end user, because only then it is perfectly clear that the user wants to work with Revit at that time. For external events doing some work for background threads (like analysis, etc., i.e. job not really visible to the end user) I’d recommend against playing with foreground windows. Just my opinion.
    Arnošt Löbel
    Autodesk Revit R&D

  2. Dear Arnošt,
    Thank you very much for these words of wisdom and experience!
    I fully agree.
    To be clear: the solution designed above is very specifically targeted at my own personal needs in order to demo this specific application.
    It is cool to hit the Save button in my JavaScript-generated browser interface and immediately see the BIM updated with no noticeable change whatsoever in the window arrangement.
    For many and possibly all real-world workflows, however, this behaviour is not optimal or even useful, just as you point out.
    Thank you very much for highlighting and clarifying this!
    Cheers, Jeremy.

  3. It looks like triggering the WPF message loop also triggers immediate ExternalEvent calls. The following is working for me in Revit 2015:
    System.Windows.Application.Current.Dispatcher.InvokeAsync(() => { }, DispatcherPriority.Background);

  4. Dear Jon,
    Congratulations on that very useful discovery!
    Thank you for letting us know!
    Cheers, Jeremy.

  5. The suggested method only works ones. Afterwards, only real user interaction will trigger the idle event.
    I’ve also tried different triggering the WPF messagepump as Jon suggested but that doesn’t work in R14.

Leave a Reply to KenCancel reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading