LINQ and the AutoCAD .NET API (Part 4)

From tables to dictionaries

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.

Introduction

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.

Dictionaries

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).

Implementation

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);
  }
}

Listing 1: GetItems

That was 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];
  }
}

Listing 2: GetDictItems

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 readonly Database db;
  private readonly Transaction tr;

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

  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.MLeaderStyleDictionary); }
  }

  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
}

Listing 3: GeneralHelper


The power of abstraction

The great thing about the GeneralHelper 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.