Per-document data in AutoCAD .NET applications – Part 4

In yesterday's post, we looked at a mechanism that has been introduced in AutoCAD 2015 to simplify associating custom data with a Document. In today's post we're going to swap out the custom manager class to make use of the standard UserData mechanism, showing how the PerDocumentClass attribute and the UserData property are actually highly complementary.

Here's the updated C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.Runtime;

using System;

using System.Collections.Generic;

 

// The all-important attribute telling AutoCAD to instantiate

// the PerDocData class for each open or new document

 

[assembly: PerDocumentClass(typeof(PerDocSample.PerDocData))]

 

namespace PerDocSample

{

  public class Commands

  {

    // A simple command to write the contents of our per-document

    // data to the command-line

 

    [CommandMethod("PPDD")]

    public void PrintPerDocData()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      var perDocData = doc.UserData["TtifData"] as PerDocData;

      doc.Editor.WriteMessage(

        perDocData == null ?

        "\nNo user data found." :

        perDocData.OpenDateTime.ToString()

      );

    }

  }

 

  // Our per-document data class. This will get instanciated for

  // each existing or new document: we get the creation

  // notification via either the static Create() method or via

  // the public constructor that takes a Document argument

 

  public class PerDocData

  {

    // We will store the time the document was opened or created

 

    private DateTime _openDateTime;

 

    // Provide a public read-only property for that

 

    public DateTime OpenDateTime

    {

      get { return _openDateTime; }

    }

 

    // Public constructor taking a Document

 

    public PerDocData(Document doc)

    {

      _openDateTime = DateTime.Now;

      doc.UserData.Add("TtifData", this);

    }

 

    // Static Create method: this is the first approach tried

    // (to differentiate we're adding an hour to the current

    // time, so it's clear this method is being called)

 

    public static PerDocData Create(Document doc)

    {

      var pdd = new PerDocData(doc);

      pdd._openDateTime += TimeSpan.FromHours(1);

      return pdd;

    }

  }

}

You'll see that we've simplified the code quite a bit by using UserData, which is really just a per-document map that associates custom application data with an application ID. As the map disappears when the owning Document gets destroyed we no longer have to worry about removing data from a central map as we did last time. And as mentioned last time, Dispose() will be called on any objects implementing it, simplifying the work needed to clean up after ourselves.

For simplicity's sake I've chosen to hardcode the application ID ("TtifData") in a couple
of places in the above code. It's clearly better to have this defined centrally, but I've left that as an exercise for the user (it's being used across two separate classes, which complicates things slightly: obvious ways to share the definition would be via a common "utils" class or even to combine the two classes into one containing a single definition of the constant).

The code should work exactly as the code in the last post did: try loading it into AutoCAD and running the PPDD command in a few different documents.

It's simple and works well: hopefully it's clear that PerDocumentClass in combination with UserData greatly simplifies the work needed to associate data at a per-document level in AutoCAD.

6 responses to “Per-document data in AutoCAD .NET applications – Part 4”

  1. the disqus adds a big chunck of adds above the comment box. Not sure if that was intended when you switched.
    My real comment is about using modeless dialogs, which would naturally want to properly react to document changes and read per document data. Also, they involve document locking, and dealing with users interrupting lisp and commands to pick stuff on the dialog. I once asked "should I always make routines that draw stuff, lock the document in case being used from a modeless?" I'm still not sure what best practices are. thx

    1. Thanks, James. I think I just found out how to nuke them (can you check and let me know?).

      If working from a modeless dialog then you can either deal with manual locking of the document(s) you want to access or you can fire off commands that lock the active document (this is automatic for modal commands). It's easier than ever to call commands and I've tended towards this option myself in these cases.

      But then there are lots of good reasons for doing the extra work to implement manual locking, such as if you need a tighter integration between your dialog and the information stored in one or more documents.

      Kean

      1. wow, reverting to commands script style. Only issue there is speed, as well as interface consistency less than programmatic entity creation. When you say "deal with manual locking", that pretty much means always locking the doc for any tools that draw things. Is there any downside to locking a doc, even when not necessary?
        the adds are gone, I think you got it.
        Each reply with this format indents a bit, which really squeezes the part where I enter my name and email...

        1. Have you tried the new APIs to call commands in 2015? I think you'll be pleasantly surprised.

          There should be no noticeable performance impact from locking a document: just lots of errors when you forget - some of them annoyingly obscure - when you don't.

          Kean

  2. Is there any real advantage of using this new attribute over binding to document creation events?

    (P.S. Thank you for all the good information you post. Since I've starting working on old and new AutoCAD addon, your blog has been a life saver in countless situations.)

    1. It's just simpler and reduces the chance of unwittingly introduced bugs.

      Happy to know the blog has been useful to you!

      Kean

Leave a Reply to James Maeding Cancel reply

Your email address will not be published. Required fields are marked *