I am still going through my emails after returning from the desert. Here is a summary of an interesting discussion between Katsuaki Takamizawa, Arnošt Löbel, Partha Sarkar, Joe Ye and Miroslav Schonauer on how to determine the quiescent state of Revit for accessing the API from a modeless context, and some recommendations to simply avoid that entirely, since other more efficient and stream-lined interaction models end up being easier to implement and more reliable to use.
Question: I am using an external event to interact with the Revit API from a modeless dialogue.
Is there any way to find if Revit is in a busy state so that the request won't be executed right away? I would like to disable the modeless dialogue during such a busy state, so the user can clearly see when the dialogue box is usable.
Are there any events like 'begin/end the idling state' that I can use to achieve this?
Answer: There is no begin-end Idling state. There is just the Idling event itself. External events are executed at the same (literary) time – that means that during each internal idling event Revit executes all delegates of API Idling event and also all external events that are signalled at the same time.
The recommendation for using external events in modeless dialogues is this:
- The dialogue acquires an instance of ExternalEvent
- It remains enabled until the end user interacts with it, assuming that while the end user is doing something in the modeless dialogue he or she is unlikely working in Revit at the same time.
- After the user starts an action in the dialogue, it raises the external event and waits for its handler to be called back. At that time the dialogue's control should be disabled and remain so until the handler has been executed.
- When Revit calls the handler back, the handler does what it needs to, depending on the action requested by the dialogue user.
- After the handler completes its API calls, its controls are enabled again
- Back to step #2
This is demonstrated by the ModelessDialog ModelessForm_ExternalEvent SDK sample.
Although the Application class provides an IsQuiescent property that one would assume could be used to check if the current application is quiescent, it is unfortunately useless in Revit. It does not do anything. If an application can legally call it, it will return false. If it returns true, it means the caller calls it illegally and may not make any other API calls anyway. IsQuiescent is not thread-safe either.
We already presented a more detailed discussion on the uselessness of the Application IsQuiescent property in January 2013.
Response: Thank you for the explanations.
My modeless dialogue displays an 'Apply' button which modifies the Revit model when pressed. I would like to enable the button only if the modification may be executed right away.
If the following scenario makes sense (and unless it imposes any issue or side effect), I would like to file a wish list item for a 'begin/end the idling state' event.
- Disable the 'Apply' button.
- The application receives the 'begin idling state' event.
- Enable the 'Apply' button.
- The user then presses the 'Apply' button. The external event Raise method is called, the event handler is called and Revit model modification performed.
- The application receives the 'end idling state' event. Go back to the step 1.
Answer: As said, using Idling is probably not so great an idea for this.
There is no Begin or End event for Idling. There is only the Idling event itself. Besides, using Idling would negate the benefits of using External Events.
There is unfortunately no way in Revit API to achieve exactly what you want to do – that is, having the dialogue controls enabled only and only if Revit is ready to execute external events immediately.
Choosing the best approach depends on what the dialogue actually wants to do with Revit.
In typical scenarios, end users interact with an external modeless dialogue only when they do not do anything else In Revit at that time. That is why using External events works very well for that kind of applications. The dialogue's controls are enabled all the time except for the time between raising an external event and handling it.
You can subscribe to the Idling event and unsubscribe immediately in the event handler the first time it is called. This almost provides the functionality of a 'begin Idling' event. It also avoids the idling handler being called repeatedly, to avoid degrading system performance.
However, this still does not enable any reliable way to get 'Idling Started' and 'Idling Ended' events.
The scenario below will work to get only one Idling event that is 'quasi-Idling-started', but then you can't do anything further after that single catch – the problem is that you cannot do the reverse, i.e. cause Idling to restart again from the Idling event handler, simply because it's not running.
It therefore does not help at all in this situation, which is better addressed by using an external event. Both the Idling and external events are raised at the same time (assuming the external event has been signalled).
Summary: there is currently no way for external applications to know when their UI should be disabled and when it can be enabled based on the current state of Revit, and simple and effective alternative approaches are available to eliminate the need for this functionality.
Many thanks to everybody involved for this illuminating discussion and clarification!