How to determine when a document is completely loaded

How to determine when a document is completely loaded

Anonymous
Not applicable
4,894 Views
13 Replies
Message 1 of 14

How to determine when a document is completely loaded

Anonymous
Not applicable
After several days of navigating the Object ARX .NET API documentation, I have yet to find a "good" way to determine when a document that is opened via Application.DocumentManager.Open() has been completely loaded. I was a bit surprised to find that there is no event for a Document object that will be fired once the document is completely loaded

In my case, I need to update custom properties and automatically save the document (which I am currently doing by running the "QSAVE" command). My custom command is running in "Session" mode (CommandFlags.Session), because it must open a new document and then make changes to this new document. This is my first ObjectArx app, so I have no idea if DocumentCollection.Open() is usually executed asynchronously or if it is only because my command is running in session mode.

I originally attempted to implement this functionality by handling the Application.DocumentManager.DocumentCreated event. However, when attempting to execute a command for an opening/loading document at this phase, the ObjectARX API throws an exception. I later decided to go outside the .NET wrapper API and take a look directly at the COM-interop assembly, where I came across the AcadApplication.EndOpen event. It turns out that I can both populate custom properties and save a document when this event is triggered. However, this event does not provide a document object in its Event arguments, only the path to the drawing. Since the same drawing can be opened multiple times, how do you narrow this down to an individual document?

I am hoping that someone can provide a better solution, or suggestion, for determining when a drawing has been completely loaded, and how to also retrieve the Document object (either from the managed wrapper API or Interop API) that corresponds to the drawing that was opened.
0 Likes
4,895 Views
13 Replies
Replies (13)
Message 2 of 14

Mike.Wohletz
Collaborator
Collaborator
if I am reading what you are doing correctly this should get you what you are looking for I think..

{code}

Public Sub BatchFiles()

Dim acDialog As New System.Windows.Forms.OpenFileDialog
If acDialog.ShowDialog = System.Windows.Forms.DialogResult.OK Then
For Each i As String In acDialog.FileNames
Dim acDocMgr As DocumentCollection = Application.DocumentManager
Dim acDoc As Document = acDocMgr.Open(i, True)
'you now have the current document to work with
acDoc.CloseAndDiscard()
Next
End If
End Sub

{code}

Edited by: mike.wohletz on May 18, 2010 6:41 PM Edited by: mike.wohletz on May 18, 2010 6:44 PM
0 Likes
Message 3 of 14

Anonymous
Not applicable
Move the code for everything that you must do in the
open document to a seperate method and apply the
CommandMethod attribute to that method, *without*
the CommandFlags.Session flag.

Immediately after you call the Open() method, use
the DocumentCollection's SendStringToExecute()
method to execute the command method.


--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD
Supporting AutoCAD 2000 through 2011

http://www.acadxtabs.com

Email: string.Format("{0}@{1}.com", "tonyt", "caddzone");

wrote in message news:6393082@discussion.autodesk.com...
I am currently working on an ObjectARX .NET application, and this application
has a command that downloads a file from a remote server, opens the local file
as a new document, and then it should write to the document's custom properties
to store information about the remote server where the file originated from.

The problem I am having is that I am getting an eLockViolation error when trying
to update the custom properties, because the call to
Application.DocumentManager.Open() is asynchronous and is not complete when I am
attempting to update the custom properties. I should mention that the command
which does all this work is being executed in Session mode
(CommandFlags.Session), since the command will be executed on one drawing and
then must be able to open a new drawing and manipulate it.

I have tried handling various events on the DocumentCollection (DocumentCreated,
DocumentLockModeChanged), but none of them seem to really be what I'm looking
for. I would like to be able to detect when the call to
Application.DocumentManager.Open() has completed so that I can then make changes
to it (if it's writable).
0 Likes
Message 4 of 14

Anonymous
Not applicable
Mike,

Thanks for the reply. Your code unfortunately will not solve the issue that I have. My issue is that the call to Application.DocumentManager.Open() is asynchronous, at least when you run a command in the application scope (CommandFlags.Session). That is, when you call Open(), it will return before the document is completely loaded. Therefore, if you immediately attempt to modify and save the document, which in my case I am doing (setting custom properties and saving), AutoCAD will throw an error that is basically saying something to the effect of "the document is not currently in a state where it can be modified".

I have solved the problem, in a manner of speaking, by using the EndOpen event that is available on the Application.AcadApplication COM object. However, a major downside to using this event is that it only provides the path of the document that was opened, rather than a document object that would allow you to determine exactly which document was opened.

So, for example, this code would fail (sorry if you don't know C#. I don't do VB)

{code}
[CommandMethod("OpenAndModify", CommandFlags.Session | CommandFlags.Modal)]
public void OpenAndModify()
{
// attempt to open read-write
var document = Application.DocumentManager.Open("somfile.dwg", false);

// modify a custom property
var infoBuilder = new DatabaseSummaryInfoBuilder(document.Database.SummaryInfo);
infoBuilder.CustomPropertyTable["foo"] = "bar";

// write the changes back to the document/database (this will likely fail)
document.Database.SummaryInfo = infoBuilder.ToDatabaseSummaryInfo();

// if the above line didn't throw an exception, this one almost certainly will
((AcadDocument)document.AcadDocument).Save();
}
{code}


However, an example such as the one below would work and achieve the result desired in the first example.

{code}
[CommandMethod("OpenAndModify", CommandFlags.Session | CommandFlags.Modal)]
public void OpenAndModify()
{
var comApplication = (AcadApplication) Application.AcadApplication;
comApplication.EndOpen += EndOpenHandler;

// attempt to open read-write
var document = Application.DocumentManager.Open("somfile.dwg", false);

// make document active so that we can easily retrieve it in the event handler
Application.DocumentManager.MdiActiveDocument = document;
}

// handles the AcadApplication.EndOpen event
private void EndOpenHandler(String filename)
{
// if we didn't make the document active previously, this wouldn't work
var document = Application.DocumentManager.MdiActiveDocument;

// modify a custom property
var infoBuilder = new DatabaseSummaryInfoBuilder(document.Database.SummaryInfo);
infoBuilder.CustomPropertyTable["foo"] = "bar";

// write the changes back to the document/database (this will likely fail)
document.Database.SummaryInfo = infoBuilder.ToDatabaseSummaryInfo();

// if the above line didn't throw an exception, this one almost certainly will
((AcadDocument)document.AcadDocument).Save();
}
{code}
0 Likes
Message 5 of 14

Anonymous
Not applicable
{quote}
Move the code for everything that you must do in the open document to a seperate method and apply the CommandMethod attribute to that method, *without* the CommandFlags.Session flag.

Immediately after you call the Open() method, use the DocumentCollection's SendStringToExecute() method to execute the command method.
{quote}

Tony,

Thank you for the reply, but I'm afraid I don't see how this would resolve the issue. First off, SendStringToExecute is an instance method of the Document class, not DocumentCollection, and part of my problem was precisely that I could not run a command against the opened document immediately after calling DocumentCollection.Open() .

For the time being, the only viable option I've found is using the EndOpen event that's only available on the AcadApplication COM object (why this event is not available in the .NET wrapper API I have no idea). As previously mentioned, I wish this event provided a document object rather than a path, but as the overused cliché goes, "it is what it is." For my application, I am able to simply "activate" the document immediately after it's opened, and then use the DocumentCollection.MdiActiveDocument property to access it in the EndOpen event handler. This feels like a bit of a hack, but it's the best solution I've been able to find thus far. Edited by: jholzer on May 19, 2010 10:58 AM
0 Likes
Message 6 of 14

Anonymous
Not applicable
I should also add that when handling an event on an object that will persist for the life of your application (i.e. the Application or Application.DocumentManager objects), you will want to make sure an additional event handler is not added each time a command is executed. To ensure this doesn't happen, you have to unbind the event handler from the event once it's done with whatever tasks it needs to do. Otherwise, each time you run a command, you will add an additional event handler and it will end up executing the same handler method over and over, the number of executions increasing the more times you run the command.

For instance, if you wanted to handle the DocumentCollection.DocumentCreated event from a command, you would need to do something like the following to make sure you aren't executing the same handler method mutliple times when you run a command more than once.

{code}
[CommandMethod("foo", CommandFlags.Modal | CommandFlags.Session)]
public void Foo()
{
// Adds a new event handler each time the command is run.
// If this handler is not unbound, then when you run the command
// for the 2nd time, this handler will be executed 2 times, on the 5th
// run 5 times, 100th run 100 times, and so on.
Application.DocumentManager.DocumentCreated += HandleDocumentCreated;
}

private void HandleDocumentCreated(Object sender, DocumentCollectionEventArgs args)
{
// handle the event
...

// Unbind this handler.
// Otherwise, it will be executed multiple times
// for each additional time you execute the "foo" command
Application.DocumentManager.DocumentCreated -= HandleDocumentCreated;
}
{code} Edited by: jholzer on May 19, 2010 11:47 AM
0 Likes
Message 7 of 14

Anonymous
Not applicable
None of the techniques that I have shown here actually work correctly. If you use the QSAVE command to save a document, rather than calling Document.AcadDocument.Save(), AutoCAD will not crash, but the changes will not actually be saved (at least not during the EndOpen event). I have tested with commands running in the application context (Session mode) as well as commands running in the document context, and the issues are the same in both cases. I have tried every event related to opening a document on the Application and DocumentCollection objects (as well as their interop equivalents), and none of them allowed me to do all the things I need to do.

I am at least able to set custom properties while handling the EndOpen event, but cannot actually save them. As a matter of fact, even if I modify the properties during the EndOpen event and don't save, AutoCAD doesn't even see this as a change and never prompts to save the file when it is closed. As far as I can tell, there doesn't appear to be any particular event that a developer can handle where you will be certain a document has been completely loaded and can be modified and saved.

I'm not really sure how one would even make the determination that a document is completely loaded. Could you check the lock mode? When binding to the LockModeChanged event, it seems that this event is fired dozens of times when a document is opened, though for the life of me I have no idea why. How about a DocumentCollection.DocumentLoaded or DocumentCollection.DocumentLoadComplete event in the next iteration of the .NET API?
0 Likes
Message 8 of 14

Anonymous
Not applicable
--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD
Supporting AutoCAD 2000 through 2011

http://www.acadxtabs.com

Email: string.Format("{0}@{1}.com", "tonyt", "caddzone");

wrote in message news:6393769@discussion.autodesk.com...
{quote}
Move the code for everything that you must do in the open document to a seperate
method and apply the CommandMethod attribute to that method, *without* the
CommandFlags.Session flag.

Immediately after you call the Open() method, use the DocumentCollection's
SendStringToExecute() method to execute the command method.
{quote}

Tony,

Thank you for the reply, but I'm afraid I don't see how this would resolve the
issue. First off, SendStringToExecute is an instance method of the Document
class, not DocumentCollection, and part of my problem was precisely that I could
not run a command against the opened document immediately after calling
DocumentCollection.Open() .

For the time being, the only viable option I've found is using the EndOpen event
that's only available on the AcadApplication COM object (why this event is not
available in the .NET wrapper API I have no idea). As previously mentioned, I
wish this event provided a document object rather than a path, but as the
overused cliché goes, "it is what it is." For my application, I am able to
simply "activate" the document immediately after it's opened, and then use the
DocumentCollection.MdiActiveDocument property to access it in the EndOpen event
handler. This feels like a bit of a hack, but it's the best solution I've been
able to find thus far.

Edited by: jholzer on May 19, 2010 10:58 AM
0 Likes
Message 9 of 14

Anonymous
Not applicable
Are you speculating that SendStringToExecute() will
not work after calling Open() or have you actually tried it?

Is your purpose in opening documents only to modify
their summary information?

--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD
Supporting AutoCAD 2000 through 2011

http://www.acadxtabs.com

Email: string.Format("{0}@{1}.com", "tonyt", "caddzone");

wrote in message news:6394004@discussion.autodesk.com...
None of the techniques that I have shown here actually work correctly. If you
use the QSAVE command to save a document, rather than calling
Document.AcadDocument.Save(), AutoCAD will not crash, but the changes will not
actually be saved (at least not during the EndOpen event). I have tested with
commands running in the application context (Session mode) as well as commands
running in the document context, and the issues are the same in both cases. I
have tried every event related to opening a document on the Application and
DocumentCollection objects (as well as their interop equivalents), and none of
them allowed me to do all the things I need to do.

I am at least able to set custom properties while handling the EndOpen event,
but cannot actually save them. As a matter of fact, even if I modify the
properties during the EndOpen event and don't save, AutoCAD doesn't even see
this as a change and never prompts to save the file when it is closed. As far as
I can tell, there doesn't appear to be any particular event that a developer can
handle where you will be certain a document has been completely loaded and can
be modified and saved.

I'm not really sure how one would even make the determination that a document is
completely loaded. Could you check the lock mode? When binding to the
LockModeChanged event, it seems that this event is fired dozens of times when a
document is opened, though for the life of me I have no idea why. How about a
DocumentCollection.DocumentLoaded or DocumentCollection.DocumentLoadComplete
event in the next iteration of the .NET API?
0 Likes
Message 10 of 14

Mike.Wohletz
Collaborator
Collaborator
Both of the methodes that have been supplied will work, I have done and tested this and it works and I know that Tony's method will also work..

vb.net code "you can convert it"


{code}

Public Sub BatchFiles()

Dim acDialog As New System.Windows.Forms.OpenFileDialog
If acDialog.ShowDialog = System.Windows.Forms.DialogResult.OK Then
For Each i As String In acDialog.FileNames
Dim acDocMgr As DocumentCollection = Application.DocumentManager
Dim acDoc As Document = acDocMgr.Open(i, False)
'you now have the current document to work with
Using doclock As DocumentLock = acDoc.LockDocument
Dim myInfo = New DatabaseSummaryInfoBuilder(acDoc.Database.SummaryInfo)
myInfo.CustomProperties.Item("foo") = "bar"
acDoc.Database.SummaryInfo = myInfo.ToDatabaseSummaryInfo
End Using


acDoc.CloseAndSave(acDoc.Name)



'acDoc.CloseAndDiscard()
Next
End If
End Sub

{code}

Edited by: mike.wohletz on May 19, 2010 7:21 PM
see attached for good code, post messes it up Edited by: mike.wohletz on May 20, 2010 7:29 AM
0 Likes
Message 11 of 14

Anonymous
Not applicable
{quote}
Are you speculating that SendStringToExecute() will not work after calling Open() or have you actually tried it?
{quote}
I am not speculating or even trying to insinuate that this is the case. Now, if I attempt to call SendStringToExectue immediately after calling DocumentCollection.Open then it won't work. However, if I call SendStringToExecute from the EndOpen event handler, it certainly works fine. In my case, I was originally attempting to save a file using the QSAVE command, and while the command would appear to execute without issue, nothing actually got saved. I also attempted to use the Document.Database.SaveAs() method, with the exact same result (code ran fine, but nothing was actually saved).


{quote}
Is your purpose in opening documents only to modify their summary information?
{quote}
No, this is only one step in the process for "opening" a file. Essentially what I would like to happen is:

1. The file is downloaded from the remote server (this is working fine)
2. The file is opened in AutoCAD and made the active document
3. Custom properties are set on the opened file to store information about where it originated from on the remote server
4. The document is automatically saved after the custom properties are set, but remains open and active
0 Likes
Message 12 of 14

Anonymous
Not applicable
I have not tried using the LockDocument method, so I will give that a shot and see if perhaps that was my problem.
0 Likes
Message 13 of 14

Anonymous
Not applicable
Mike,

It turns out that locking the document did the trick. Thanks for the code sample. I don't know if I ever would have figured out that was the problem.

As a general rule of thumb, should you lock any time you wish to make changes to a document?
0 Likes
Message 14 of 14

Anonymous
Not applicable
{quote}

Now, if I attempt to call SendStringToExectue immediately after
calling DocumentCollection.Open then it won't work.

{quote}

You're mistaken on all counts.

- DocumentCollection.Open() is NOT asnynchronous

- Document.SendStringToExecute() works after calling Open().

Your code was not calling SendStringToExecute() with the
correct parameters, which is what causes the eInvalidInput
error.

If all you are doing is modifying the summary information, then
you're going about it incorrectly to start with.

What you should be doing is opening the drawing file externally
using Database.ReadDwgFile(), modify the summary information,
and then use Database.SaveAs() to save the file. After you do
that, you only need to open the file the editor and exit.

While the kludge you're using will work for cases where you
are only modifying the drawing via API calls, it will not work
in cases where you need to execute AutoCAD commands (as
many batch processing scenarios need to do).

--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD
Supporting AutoCAD 2000 through 2011

http://www.acadxtabs.com

Email: string.Format("{0}@{1}.com", "tonyt", "caddzone");

wrote in message news:6394446@discussion.autodesk.com...
{quote}
Are you speculating that SendStringToExecute() will not work after calling
Open() or have you actually tried it?
{quote}
I am not speculating or even trying to insinuate that this is the case. Now, if
I attempt to call SendStringToExectue immediately after calling
DocumentCollection.Open then it won't work. However, if I call
SendStringToExecute from the EndOpen event handler, it certainly works fine. In
my case, I was originally attempting to save a file using the QSAVE command, and
while the command would appear to execute without issue, nothing actually got
saved. I also attempted to use the Document.Database.SaveAs() method, with the
exact same result (code ran fine, but nothing was actually saved).


{quote}
Is your purpose in opening documents only to modify their summary information?
{quote}
No, this is only one step in the process for "opening" a file. Essentially what
I would like to happen is:

1. The file is downloaded from the remote server (this is working fine)
2. The file is opened in AutoCAD and made the active document
3. Custom properties are set on the opened file to store information about
where it originated from on the remote server
4. The document is automatically saved after the custom properties are set,
but remains open and active
0 Likes