Hi guys,
I have a modeless dialog and I use Idling event to handle its evets. So the code goes like this
First the External Command:
Result MenuHandler::Execute(ExternalCommandData^ commandData, String^% message, ElementSet^ elements)
{
Transaction^ transaction = gcnew Transaction(commandData->Application->ActiveUIDocument->Document, "Menu Handler");
transaction->Start();
// Here I create a new Form and Show it
transaction->Commit();
return Result:: Succeeded;
} //MenuHandler::Execute
Then I queue events from the modeless dialog to be all handled when the Idling event is fired.
And the code for the IdleHandler is
System::Void FormsHandler:: onIdling(Object^ sender, IdlingEventArgs^ args)
{
UIApplication^ uiApplication = dynamic_cast<Autodesk::Revit::UI::UIApplication^>(sender);
if (uiApplication == nullptr)
return;
//This Next line throws 'Autodesk.Revit.Exceptions.InvalidObjectException' occurred in RevitAPI.dll
//Additional information: The managed object is not valid.
Transaction^ transaction = gcnew Transaction(uiApplication->ActiveUIDocument->Document, "Idle Handling");
transaction->Start();
//Here I handle each event in the queue and then empty the queue
transaction->Commit();
} //FormsHandler:: onIdling
Can anyone help with this, whenever I try to use the Document object it throws InvalidObjectException, I assume it has something to do with the transaction but I am not sure what. Am I missing something ?
Thank you
Hi
I made a quick test on my side, it works fine, but we need to check if the Document is not null before try to use it to start a transaction.
As you are trying to get data on the background, I would suggest you check the main properties, such as ActiveUIDocument and Document, before accessing it.
Augusto has a very valid point. It is not necessary that there always is an active document open in Revit. The Idling event would be raised even when no document is open yet, or after the last document was closed by the end user. Therefore, it is important to check if the ActiveUIDocument is actually valid.
I have a few more comments/questions:
Thank you
Arnošt Löbel
Autodesk Revit R&D
Hi guys, thanks for the replies.
@Augusto: Yes I check if it is null I just didn't add that part of the code, so these checks are there
if (uiApplication == nullptr || uiApplication->ActiveUIDocument == nullptr)
return;
host.setUIApplication(uiApplication); //this is my host don't worry about it
if (uiApplication->ActiveUIDocument->Document != nullptr) {
Transaction^ transaction = gcnew Transaction(uiApplication->ActiveUIDocument->Document, "Idle Handling");
transaction->Start();
host.getProject()->setDocument(uiApplication->ActiveUIDocument->Document);
handleAsynchronous();
transaction->Commit();
}
@Arnošt
1 - I subscribe to the Idling event in "OnStartup" of the ExternalApplication
if (host.getUIControlledApplication() != nullptr)
host.getUIControlledApplication()->Idling += gcnew EventHandler<IdlingEventArgs^>(&FormsHandler::onIdling);
2 - A transaction is created there because whenever I show a dialog I update (if found) or create a new DataStorage element.
I can't make any changes ? cause I assumed that if I don't handle the events from the modeless dialog and wait to handle all the events when Idling is called that should work fine.
So my modeless dialog has a button to insert new elements, so when the user clicks the button, I save a queue containing all the information about the user click and what to add, and when the Idling event is fired I create all the elements based on the data I saved and empty that queue so I not to add more elements than the number of clicks by the user.
3 - Yes the modeless dialog is designed so that the elements will be added to whatever document the user has active at that moment.
4 - It means that I don't handle user events at the same instance they click the insert element button on the dialog cause then the Document and Application pointers will be invalid, so I queue user actions till the Idling event is fired and I assumed that I can make any changes at this point, the Idling event that is.
Cheers
Hi,
I would suggest you also check the ActiveUIDocument property....on my testing, this was NULL in many scenarios.
Hi,
Yes I do check for it in the first line above
if (uiApplication == nullptr || uiApplication->ActiveUIDocument == nullptr)
return;
The problem is this works fine the first time it goes through, but when I try to create any Transaction after that throws the same exception.
So I have two places where I create transactions or use the Document object,
1 - When a user click the ribbon button (in the Execute of the ExternalCommand) to open the dialog.
2 - When Idling event is fired.
Whichever one of them is called first works the first time, but any subsequent calls throw the same exception.
Am I missing something in the way I should handle transactions ? or should I use TransactionGroup instead ?
Hello Waleed511,
Let me answer your last question as first, before I forget: No, you cannot use transaction groups in this scenario. A transaction group does not let you modify documents, and groups too, like transactions, must be closed before returning from you code back to Revit.
Now, back to our bullet list:
None of my comments above would explain why you get the exceptions. My remarks and notes just clarify the workflow. I do not see anything suspicious in your event handler, assuming that you indeed check validity of both UIApplication and UIDocument before you try to create the transaction. On the other hand, the InvalidObjectException is only thrown when a managed instance of a Revit object (like a Document, Element, etc.) does not point to a valid native object in Revit. For example, if you acquire an element from Revit and then you delete the element from the document, you still have your Element object variable (and not null) in your code, but the actual element does not exist anymore in Revit. If you then attempt to invoke any method on your Element variable, you would get this kind of Exception.
I suggest you try to find out what object particular is invalid. Please add the following lines to your code before the line on which you instantiate the transaction:
UIDocument uidoc = UIApplication->ActiveUIDocument; Document dbdoc = UIDocument->Document; String title = dbdoc->Title; Transaction trans = gcnew Transaction(dbdoc,"idle event");
On which line do you get the exception?
Thank you
Arnošt Löbel
Autodesk Revit R&D
Hi Arnošt,
I added the 4 lines of code.
- The first time the onIdling function is called it passes as expected, a transaction is started and commited with no problems at all.
- But the second time the same function is called, "InvalidObjectException" is thrown on the Third line (accessing the document Title), so the DB Document is the invalid object, and I checked it is not null it is just invalid.
Any idea on why it works only the first time ?
Hello waleed511,
I do not have good news for you. What you experience is not supposed to happen. In fact, I have not heard about a case like this yet. We need to continue with thorough investigation. Possibly, you can help us with that.
Before we go any further, we need more information about your setting. Please tell me what version of Revit you are using (what build, exactly), and what is your developer’s environment (VS version, NET version, etc.).
Here’s is what I am thinking. First, let’s sum up what is happening:
I will follow up with our team and also with ADN team. (You are not an ADN member, are you?) We will try to reproduce it once we have the information I asked you about (Revit version, NET version, etc.).
The way you describe the problem and the situation makes me assume that it is not related with your modeless dialog. Since you subscribe to the event from your OnStartup (and by that I mean the actual code of the OnStartup method), and then you simply handle the event every time it is raised, the fact that you have some other modeless dialog(s) around should not affect the workflow of handling the event. For better illustration I am going to post a simplified application that shows just the code we are interested in:
namespace IdlingTests { public class TestApplication : Autodesk.Revit.UI.IExternalApplication { private string docTitle = null; public Result OnStartup(UIControlledApplication application) { application.Idling += OnIdling; return Result.Succeeded; } public Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; } internal void OnIdling(Object sender, IdlingEventArgs args) { UIApplication app = sender as UIApplication; if (app.ActiveUIDocument != null) { try { docTitle = app.ActiveUIDocument.Document.Title; } catch (Autodesk.Revit.Exceptions.InvalidObjectException ) { TaskDialog.Show("Exception", "Invalid Active Document Object!" ); app.Idling -= OnIdling; } } } } }
The code shown is in C#, but it is going to be practically the came in C++/CLI. It would be greatly appreciated if you could confirm that this simple application still demonstrates the same problem.
What leaves me puzzled about this thing is that if the Document object gets somehow invalidated between Idling events, how come we do not hear about it more often (or ever, in fact.) The Idling event is used by many developers, both internal and external, and it seems to be working well. I do not want to jump to any conclusion yet; I just wanted to state this as a fact. Something is happening in your application that is not happening in other applications – we need to find out what that is.
Thank you
Arnošt Löbel
Autodesk Revit R&D
Hi Arnošt,
Thank you very much for your reply,
I tried the C# code and it works fine, so I must be doing something wrong in C++ as I am mixing managed and unmanaged C++ code.
I will look more into my C++ implementation.
Cheers
Thank you; please let me know once you find anything; if you find anything.
The important thing for you to keep in mind is that the exception is thrown (only!) when the actual native object does not exists (more common), or if the managed instance is not linked to anything native in Revit (less common – should not happen that easily). The first case ought to be obvious and I already mentioned some scenarios, like deleting a document or element and then attempting to work with the managed Document object, or Element object respectively. The second scenario is trickier and harder to get, since something would need to forcefully decouple the managed instance from its native counterpart. I assume it could be done with some shallow copying of the managed instance, but it is not something programmers do every day, and it should not happen in the basic workflow I presented in the code sample.
Arnošt Löbel
Autodesk Revit R&D
Hi Arnošt,
I am using unmanaged C++ and so to cache the Document object between calls I used msclr::auto_gcroot, but forgot to release the pointer before I return, so the Document was being deleted automatically after the first call.
Thank you very much for your help.
Hello waleed511,
I am glad that you were able to find and resolve the problem. As you have found it requires extra precautions when mixing native and managed code, and particularly when caching object between calls to the API. As a general rule, it is not recommended to cache the document or the application object between calls, since you should be able to always get them for the next call’s arguments (with only a few exceptions).
Cheers
Arnošt Löbel
Autodesk Revit R&D