LINQ and the AutoCAD .NET API (Part 4)

This is the fourth in a series of posts on LINQ an the AutoCAD .NET API. Here’s a complete list of posts in this series.

In part three of our series on LINQ and AutoCAD we created a neat way to access all tables of the drawing database. In this post we’ll have a look at how to add AutoCAD dictionaries to our GeneralHelper class.

Which dictionaries of interest do we have in the drawing database? Figuring this out is almost the same like for tables: the database object has some properties that end with DictionaryId. And again, like for tables, there is one element type for each dictionary. Let’s bring it all together in tabular form:

ID property Dictionary entry type
ColorDictionaryId DBObject
DataLinkDictionaryId DataLink
DetailViewStyleDictionaryId DetailViewStyle
GroupDictionaryId Group
LayoutDictionaryId Layout
MaterialDictionaryId Material
MLeaderStyleDictionaryId MLeaderStyle
MLStyleDictionaryId MlineStyle
NamedObjectsDictionaryId DBObject
PlotSettingsDictionaryId PlotSettings
PlotStyleNameDictionaryId PlaceHolder
SectionViewStyleDictionaryId SectionViewStyle
TableStyleDictionaryId TableStyle
VisualStyleDictionaryId DBVisualStyle

A few comments:

  • I couldn’t figure out the data type of entries of the color dictionary. Let’s stick with DbObject for the moment.
  • The named objects dictionary is kind of a “meta” container. It holds all dictionaries of this list and some other objects. So from a Add-In developer’s perspective, this dictionary is not really of interest, so it’s safe to leave it out.
  • The plot style names dictionary simply contains the plot style names, so there’s no need to use GetDictItems, but rather we return the dictionary keys, which are the plot style names (we’ll see that in Listing 3).

Alright, on to the implementation! First let’s recap how we get records from AutoCAD tables:

private IEnumerable<T> GetItems<T>(ObjectId tableID)
  where T : SymbolTableRecord
{
  var table = (IEnumerable)tr.GetObject(tableID, OpenMode.ForRead);

  foreach (ObjectId id in table)
  {
    yield return (T)tr.GetObject(id, OpenMode.ForRead);
  }
}

Easy. Getting dictionary entries is quite similar:

private IEnumerable<T> GetDictItems<T>(ObjectId dictID)
  where T : DBObject
{
  if (dictID.IsValid)
  {
    var dict = (IEnumerable)tr.GetObject(dictID, OpenMode.ForRead);
   
    foreach (DBDictionaryEntry entry in dict)
    {
      yield return (T)tr.GetObject((ObjectId)entry.Value, OpenMode.ForRead);
    }
  }
  else
  {
    return new T[0];
  }
}

At first we get the IEnumerable to iterate the IDs. But here’s the difference between dictionaries and tables: each entry of the dictionary’s iterator is a DBDictionaryEntry, which holds the object’s name as the key, and the ObjectId as the value. So we have to cast the value to ObjectId. And to be on the safe side we first check wheather the dictionary ID is valid at all (line 4).

Now we can add the GetDictItems() method to our GeneralHelper class. To not confuse the two getter methods, we rename GetItems() – which returns records from a table – to GetTableItems(). Finally, for each dictionary in our list above (except the named objects dictionary), we add a property that calls GetDictItems() to the GeneralHelper.

And so our GeneralHelper finally looks like this:

public class GeneralHelper : IDisposable
{
  private Database db;
  private Transaction tr;

  public GeneralHelper()
  {
    db = Application.DocumentManager.MdiActiveDocument.Database;
    tr = db.TransactionManager.StartTransaction();
  }

  public void Dispose()
  {
    if (tr != null && !tr.IsDisposed)
    {
      tr.Dispose();
    }
  }

  private IEnumerable<T> GetTableItems<T>(ObjectId tableID)
    where T : SymbolTableRecord
  {
    if (tableID.IsValid)
    {
      var table = (IEnumerable)tr.GetObject(tableID, OpenMode.ForRead);

      foreach (ObjectId id in table)
      {
        yield return (T)tr.GetObject(id, OpenMode.ForRead);
      }
    }
    else
    {
      yield break;
    }
  }

  private IEnumerable<T> GetDictItems<T>(ObjectId dictID)
    where T : DBObject
  {
    if (dictID.IsValid)
    {
      var dict = (IEnumerable)tr.GetObject(dictID, OpenMode.ForRead);

      foreach (DBDictionaryEntry entry in dict)
      {
        yield return (T)tr.GetObject((ObjectId)entry.Value, OpenMode.ForRead);
      }
    }
    else
    {
      yield break;
    }
  }

  #region Tables

  public IEnumerable<BlockTableRecord> Blocks
  {
    get { return GetTableItems<BlockTableRecord>(db.BlockTableId); }
  }

  public IEnumerable<DimStyleTableRecord> DimStyles
  {
    get { return GetTableItems<DimStyleTableRecord>(db.DimStyleTableId); }
  }

  public IEnumerable<LayerTableRecord> Layers
  {
    get { return GetTableItems<LayerTableRecord>(db.LayerTableId); }
  }

  public IEnumerable<LinetypeTableRecord> Linetypes
  {
    get { return GetTableItems<LinetypeTableRecord>(db.LinetypeTableId); }
  }

  public IEnumerable<RegAppTableRecord> RegApps
  {
    get { return GetTableItems<RegAppTableRecord>(db.RegAppTableId); }
  }

  public IEnumerable<TextStyleTableRecord> TextStyles
  {
    get { return GetTableItems<TextStyleTableRecord>(db.TextStyleTableId); }
  }

  public IEnumerable<UcsTableRecord> Ucss
  {
    get { return GetTableItems<UcsTableRecord>(db.UcsTableId); }
  }

  public IEnumerable<ViewportTableRecord> Viewports
  {
    get { return GetTableItems<ViewportTableRecord>(db.ViewportTableId); }
  }

  public IEnumerable<ViewTableRecord> Views
  {
    get { return GetTableItems<ViewTableRecord>(db.ViewTableId); }
  }

  #endregion

  #region Dictionaries

  public IEnumerable<DBObject> Colors
  {
    get { return GetDictItems<DBObject>(db.ColorDictionaryId); }
  }

  public IEnumerable<DataLink> DataLinks
  {
    get { return GetDictItems<DataLink>(db.DataLinkDictionaryId); }
  }

  public IEnumerable<DetailViewStyle> DetailViewStyles
  {
    get { return GetDictItems<DetailViewStyle>(db.DetailViewStyleDictionaryId); }
  }

  public IEnumerable<Group> Groups
  {
    get { return GetDictItems<Group>(db.GroupDictionaryId); }
  }

  public IEnumerable<Layout> Layouts
  {
    get { return GetDictItems<Layout>(db.LayoutDictionaryId); }
  }

  public IEnumerable<Material> Materials
  {
    get { return GetDictItems<Material>(db.MaterialDictionaryId); }
  }

  public IEnumerable<MLeaderStyle> MLeaderStyles
  {
    get { return GetDictItems<MLeaderStyle>(db.MLeaderStyleDictionaryId); }
  }

  public IEnumerable<MlineStyle> MlineStyles
  {
    get { return GetDictItems<MlineStyle>(db.MLStyleDictionaryId); }
  }

  public IEnumerable<PlotSettings> PlotSettings
  {
    get { return GetDictItems<PlotSettings>(db.PlotSettingsDictionaryId); }
  }

  public IEnumerable<string> PlotStyleNames
  {
    get
    {
      var dict = (IEnumerable)tr.GetObject(db.PlotStyleNameDictionaryId, OpenMode.ForRead);

      foreach (DBDictionaryEntry entry in dict)
      {
        yield return (string)entry.Key;
      }
    }
  }

  public IEnumerable<SectionViewStyle> SectionViewStyles
  {
    get { return GetDictItems<SectionViewStyle>(db.SectionViewStyleDictionaryId); }
  }

  public IEnumerable<TableStyle> TableStyles
  {
    get { return GetDictItems<TableStyle>(db.TableStyleDictionaryId); }
  }

  public IEnumerable<DBVisualStyle> VisualStyles
  {
    get { return GetDictItems<DBVisualStyle>(db.VisualStyleDictionaryId); }
  }

  #endregion
}

The power of abstraction

The great thing about the GetHelper class is that it abstracts away all details about how and where items are stored in the drawing database. The class provides one uniform way to access tables and dictionaries, no knowledge about details necessary. From the client’s perspective, each “container” is of the most general container type available in .NET: IEnumerable<T>. And this gives us all the benefits of LINQ “out of the box”.

In the next post we’ll have a look at some special object of the AutoCAD API, like the model space and paper space. We integrate these objects into the GeneralHelper, to make them easily querable using LINQ.

Advertisements

2 thoughts on “LINQ and the AutoCAD .NET API (Part 4)

  1. Hi Mr Wolfgang,

    this is very interesting code. I’ve been wanting this – something like this – but I couldn’t quite articulate it.

    Is it possible to further genericise (if that’s even a word) all the XTableRecords and dictionary items into just two generic methods………and use that instead of the properties?

    also Tony Tanzillo has similar code – are you familiar with some of his methods?

    rgds

    Ben

    • Hi Ben,

      I think what you mean are the two methods GetTableItems and GetDictItems. All properties call one of these two methods. The properties are just convenience wrappers for each type.

      I’m sure there are other AutoCAD LINQ implementations out there. But I didn’t look for other implementations, in the beginning I just wanted to play around with some ideas and ended up writing this blog series.

      -Wolfgang

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s