I recently presented the modeless door lister created by Agnius Vasiliauskas as his very first attempt to make use of the Revit API. While representing an impressive achievement, it also contains a number of flaws that I did not notice. Luckily, Arnošt Löbel of the Revit development team took the time to examine the application implementation in greater detail, and he has a number of suggestions that are of great interest to anyone wanting to make further use of the add-in presented, or the Idling event and modeless dialogues interacting with Revit in general.
You should probably read Arnošt's suggestions as a follow-up on his explanation of asynchronous API calls and Idling, which provides a valuable background to all the practical feedback on this specific new sample application given below.
Here are Arnošt's initial comments:
I am looking at the code of the application and you can probably guess I do not like it that much (although our PM is rather excited about it). The sample demonstrates too many rather dangerous techniques for my taste.
I am not trashing, just commenting.
Here are some more detailed explanations of these points:
This one I am not saying it is critical; it is simply as dangerous as any sneaking under the hood of another application can be. The code of the sample does not try to communicate with Revit's main window, it only uses it as the parent of its modeless dialog. This way the dialog becomes Revit's main frame's child. If there is any code in Revit (now or in the future) that needs to iterate through all children it would most likely crash at this one. Or there could be a code that if it finds even one single modeless dialog it may believe it is Revit's and may do something to it that it would not work or crash
Revit API safeguards API users' transactions. It does not allow more transactions than just one, and also makes sure there is no one left open behind by any external application (which would prevent the document from being modified again). The safeguard is in effect only when Revit knows it communicates with an external application (through regular Commands, Events, Callbacks, etc.). Revit does not prevent users calling into its API from outside threads, but the transaction firewall is off at those times, therefore damage to a document is possible and likely.
From what I saw in the code, the Idling was used only once – to start the dialog. If that is indeed how the sample works, I do not see it as very effective. The sample obviously works with documents and views, therefore it should bring up a dialog only when a document is opened and/or a view activated.
External Command objects are not guaranteed to live throughout an entire Revit session. Revit can decide, now or in the future, to unload commands at any time, which would cause objects used by the command be deleted and collected. If there is an event handler that depends, directly or indirectly, on such objects, it would crash. On the other hand, external applications are never unloaded, therefore whatever is allocated in them is not going to get deleted prematurely. For these reasons we recommend having event handlers, updaters, and other callbacks registered and defined in an external application rather than in a command. The exceptions are handlers and callbacks that are only used while their external commands are running.
Besides the above points, the main issue is that Revit simply does not support external applications calling the API from other than the main thread (a work thread or a modeless dialog). The API is not built that way and even when it actually works (for some application) sometimes, it is not guaranteed it will always work (besides the point mentioned above about the transaction firewall). The only supported way for modeless dialogs to work within Revit is through standard callbacks, from which the Idling event is probably the most convenient way to deliver data back to Revit. The following steps outline how it is expected to work:
Again, I want to point out that it is not my intention at all to trash the code of the sample. It is a very interesting example of using the API and we are happy to see more like that. On the other hand, I prefer to be careful when recommending code that does not exactly follow the guidelines for using the API. It's fine if it works for some, but I cannot generally recommend it as an example other application should closely mimic. I hope it is understandable.
Very many thanks to Arnošt for taking the time and effort to spell this out for us!
By the way, a slightly cleaner non-generic example of using the Idling event to handle messages received from a modeless dialogue attached to the main Revit window is given by my modeless loose connector navigator, although it also does violate some of Arnošt's suggestions, and Daren Thomas suggested an elegant generic pattern for this kind of semi-asynchronous Idling API access.