Populating a tree view inside AutoCAD with sheet set data using .NET

A big thank you to Fenton Webb, from DevTech Americas, for providing the code which was the basis for this post. Thanks, Fents! 🙂

Fenton sent a version of this code recently to an ADN member who was interested in duplicating the information shown in AutoCAD's Sheet Set Manager inside a custom, palette-hosted tree-view dialog. Fenton's version made use of WPF: I've dumbed it down a little to use WinForms, but may do a follow-up post using WPF (although the WPF TreeView doesn't appear to support data-binding, so I may well decide not to bother).

I have made some other changes to Fenton's code, and if there are any bugs they're likely to be mine rather than his. I fleshed out some of the various item types, but as I haven't yet been able to see them in action (I'm not even sure there was any real need to do so), they may need some further work or even stripping out.

Anyway, here's the C# code. To make it work your project will need a reference to acsmcomponents18.tlb (or acsmcomponents17.tlb if using AutoCAD 2007-2009) as well as AcMgd.dll, AcDbMgd.dll and the standard .NET components that Visual Studio may be looking for when building the project.

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows;

using acApp =

  Autodesk.AutoCAD.ApplicationServices;

using ACSMCOMPONENTS18Lib;

using System.Windows.Forms;

using System;

 

namespace MyApplication

{

  public class Commands

  {

    static PaletteSet ps = null;

    static UserControl1 userControl = null;

 

    [CommandMethod("SSTREE")]

    public void PopulateCustomSheetTree()

    {

      // Check the state of the paletteset

 

      if (ps == null)

      {

        // Then create it

 

        ps = new PaletteSet("Custom Sheet Tree");

        userControl = new UserControl1();

        ps.Add("MySheetView", userControl);

      }

 

      ps.Visible = true;

 

      // Get the AutoCAD Editor

 

      Editor ed =

        acApp.Application.DocumentManager.MdiActiveDocument.Editor;

 

      // Get the SheetSet Manager

 

      AcSmSheetSetMgr mgr = new AcSmSheetSetMgr();

 

      // Create a new SheetSet Database

 

      AcSmDatabase db = new AcSmDatabase();

 

      // Try and load a default DST file...

 

      try

      {

        db =

          mgr.OpenDatabase(

            @"C:\Program Files\Autodesk\AutoCAD 2011\Sample\" +

            @"Sheet Sets\Architectural\IRD Addition.dst",

            true

          );

      }

      catch (System.Exception ex)

      {

        ed.WriteMessage(ex.ToString());

        return;

      }

 

      // Lock the db for processing

 

      db.LockDb(db);

 

      AcSmSheetSet ss = db.GetSheetSet();

 

      // Create a root item in the TreeView,

      // the name of the SheetSet

 

      TreeNode root = new TreeNode(ss.GetName());

      userControl.treeView1.Nodes.Add(root);

 

      ProcessEnumerator(ss.GetSheetEnumerator(), root);

 

      db.UnlockDb(db, true);

      mgr.Close(db);

    }

 

    void ProcessEnumerator(IAcSmEnumComponent iter, TreeNode root)

    {

      IAcSmComponent item = iter.Next();

      while (item != null)

      {

        string type = item.GetTypeName();

        switch (type)

        {

          case "AcSmSubset":

            t
ry

            {

              AcSmSubset subset = (AcSmSubset)item;

              string subName = subset.GetName();

 

              if (!String.IsNullOrEmpty(subName))

              {

                TreeNode tn = AddTreeNode(root, subName);

                IAcSmEnumComponent enumerator =

                  (IAcSmEnumComponent)subset.GetSheetEnumerator();

                ProcessEnumerator(enumerator, tn);

              }

            }

            catch { }

            break;

 

          case "AcSmSheet":

            try

            {

              AcSmSheet sh = (AcSmSheet)item;

              string shName = sh.GetName();

 

              if (!String.IsNullOrEmpty(shName))

              {

                TreeNode tn = AddTreeNode(root, shName);

                IAcSmEnumComponent enumerator =

                  (IAcSmEnumComponent)sh.GetSheetViews();

                ProcessEnumerator(enumerator, tn);

              }

            }

            catch { }

            break;

 

          case "AcSmSheetViews":

            try

            {

              AcSmSheet sh = (AcSmSheet)item;

              string shName = sh.GetName();

 

              if (!String.IsNullOrEmpty(shName))

              {

                TreeNode tn = AddTreeNode(root, shName);

                IAcSmEnumComponent enumerator =

                  (IAcSmEnumComponent)sh.GetSheetViews();

                ProcessEnumerator(enumerator, tn);

              }

            }

            catch { }

            break;

 

          case "AcSmSheetView":

            try

            {

              AcSmSheetView sv = (AcSmSheetView)item;

              string svName = sv.GetName();

 

              if (!String.IsNullOrEmpty(svName))

                AddTreeNode(root, svName);

            }

            catch { }

            break;

 

          case "AcSmCustomPropertyValue":

            AcSmCustomPropertyValue pv =

              (AcSmCustomPropertyValue)item;

 

            AddTreeNode(

              root,

              "Custom property value: " + pv.GetValue().ToString()

            );

            break;

 

          case "AcSmObjectReference":

            AcSmObjectReference or =

              (AcSmObjectReference)item;

 

            AddTreeNode(

        
0;     root,

              "Object reference: " +

              or.GetReferencedObject().GetTypeName()

            );

            break;

 

          case "AcSmCustomPropertyBag":

            try

            {

              AcSmCustomPropertyBag bag =

                (AcSmCustomPropertyBag)item;

 

              TreeNode tn = AddTreeNode(root, "Custom property bag");

              IAcSmEnumComponent enumerator =

                (IAcSmEnumComponent)bag.GetPropertyEnumerator();

              ProcessEnumerator(enumerator, tn);

            }

            catch { }

            break;

 

          case "AcSmAcDbLayoutReference":

            AcSmAcDbLayoutReference lr =

              (AcSmAcDbLayoutReference)item;

 

            AddTreeNode(

              root,

              "Layout reference: " + lr.GetName()

            );

            break;

 

          case "AcSmFileReference":

            AcSmFileReference fr = (AcSmFileReference)item;

 

            AddTreeNode(

              root,

              "Layout reference: " + fr.GetFileName()

            );

            break;

 

          case "AcSmAcDbViewReference":

            AcSmAcDbViewReference vr = (AcSmAcDbViewReference)item;

 

            AddTreeNode(

              root,

              "View reference: " + vr.GetName()

            );

            break;

 

          case "AcSmResources":

            try

            {

              AcSmResources res = (AcSmResources)item;

 

              TreeNode tn = AddTreeNode(root, "Resources");

              IAcSmEnumComponent enumerator =

                (IAcSmEnumComponent)res.GetEnumerator();

              ProcessEnumerator(enumerator, tn);

            }

            catch { }

            break;

 

          default:

            Document doc =

              acApp.Application.DocumentManager.MdiActiveDocument;

            Editor ed = doc.Editor;

            ed.WriteMessage("\nMissed Type = " + type);

            break;

        }

        item = iter.Next();

      }

    }

 

    private TreeNode AddTreeNode(TreeNode root, string name)

    {

      // Create a new node on the tree view

 

      TreeNode node = new TreeNode(name);

 

      // Add it to what we have

 

      root.Nodes.Add(node);

      return node;

    }

  }

}

You'll need to add a new UserControl to your project and a TreeView to that, setting the Dock property to fill. Be sure to keep the default names as UserControl1 and treeView1. Other than than, no UI work is needed.

Here's what happens when we run the SSTREE command along and load the hard-coded DST file into the SheetSet Manager (displayed with the SSM command) for comparison:

 

Custom Sheet Tree vs. Sheet Set Manager

Clearly more could be done to make the UI look the same (icons, etc.), but this post is more about recursively parsing a SheetSet's tree rather than anything else. If you come across any issues as you try this out with other data stored in DST files, be sure to post a comment.

15 responses to “Populating a tree view inside AutoCAD with sheet set data using .NET”

  1. Lee Ambrosius Avatar
    Lee Ambrosius

    Nice example... I was just thinking about the same thing the other day since I did a class at AU last year on the Sheet Set API and using VB.NET/C#. Most of my content focused on how to simply manipulate sheet sets and not emulating the UI, but did get a few questions on if it could be done.

  2. Tony Tanzillo Avatar
    Tony Tanzillo

    "(although the WPF TreeView doesn’t appear to support data-binding, so I may well decide not to bother)"

    Um.... Not sure what gives you that impression. In WPF, pretty much everything supports data binding both directly, and via templates. It's just a more sophisticated form of Data Binding.

    The template most commonly used to bind data to Treeview items is the HierarchicalDataTemplate.

    This article has a good introduction to the basic concepts:

    msdn.microsoft.com/en-us/magazine/cc700358.aspx

  3. Kean Walmsley Avatar

    Yes - my mistake. I Googled it and came across an MSDN article including a section on "Designing a Data-Bound Tree View" but didn't spot that a) it had been written in 2002 and b) it was about WinForms, not WPF.

    Thanks for the clarification - I'll follow up with a WPF version when I get the chance.

    Kean

  4. Harold J Comerro Avatar
    Harold J Comerro

    Hello and thanks for this nice code.

    I have tried it and found that it does not show all of the items, just the sheets and subsets.

    What I was hoping to see is similar to what appears in the screenshot image posted in this AUGI thread:

    forums.augi.com/showthread.php?t=115224

    Any advice would be appreciated.

  5. Kean Walmsley Avatar

    Hello Harold,

    That's right: it only shows a limited set of the data from the DST.

    As I'm not an AUGI member I don't have access to the screenshot image, but I've put together a post that will be published on Friday that should help.

    Regards,

    Kean

  6. Tony Tanzillo Avatar

    That screenshot shows a tool that has a 'debug' view option, that was active when the image was grabbed.

    That's definitiely not a user-oriented presentation, and was intended mainly for use by sheetset API programmers, to visualize the 'raw' sheetset database graph.

    Copies of that screenshot and another, showing the non-debug mode that is how the tool presents data to the user can be found here:

    caddzone.com/SSMExplorer.png

    caddzone.com/SSMExplorer2.png

    Perhaps the second image is more like what you have in mind? If so, it's not too difficult to extend what Kean posted to do that, but if you plan on adding a lot of your own functionality to it, taking the 'big switch' approach to populating the control's content probably isn't the best approach, because that can easily turn into spaghetti.

    What you might consider doing is studying the MVM (Model-View-Mapping) and MVVM (Model-View-ViewModel) design patterns, which provide a way to establish a clean seperation between business objects and a UI, and also promote the use of more structured, data-driven approaches to providing a UI with content.

    While MVM and MVVM are core concepts in WPF, you can also use them with WinForms-based applications too, although WinForms has no built-in support for those patterns, as WPF does.

    The app you see in the referenced screenshots uses MVVM to deliver sheetset object data to the UI layer.

  7. Thank you. I have also looked at the new post and will be digesting that too, it looks like it will be very helpful, much appreciated.

  8. Hello. Wow. Wasn't expecting the author of that AUGI post to respond, but I'm glad you did, thanks.

    Yep, the second screenshot is pretty darn close to what I envision for my own needs, with some additional stuff added which we store in custom properties shown in the tree.

    I think Kean's follow up will be helpful for that, and I'll also look into the MVMVVM (or whatever that was... lol) stuff too.

    Also curious as to how you are able to get your app's data to appear in Vista's Explorer. That's pretty cool.

    I've searched on that but all I found was a control requiring Windows 7, that shows the Explorer's file list in a .NET form, but I think that's the opposite of what your app is doing.

    I asked a colleage of mine about that and he mentioned something about 'shell namespace extensions'. I researched them and found some info but it seemed to pertain to C++ and COM, and was all 'Chinese' to me 🙂

    Thanks again

  9. Tony Tanzillo Avatar

    That's not Vista Explorer, and there's no shell namespace extensions involved. The app was built using a library of navigation UI controls that were developed in-house.

    I looked into using shell namespace extensions too but found that they're of little use for AutoCAD based apps because AutoCAD's API can't be used from any process like Explorer and other apps that load namespace extensions (e.g., any app that uses a standard Windows file dialog).

  10. I have added a reference to the ACSMCOMPONENTS18Lib and it works fine. I am getting the following error:

    Type 'UserControl1' is not defined ..........???

  11. Kean Walmsley Avatar

    Hi Hugh,

    Yes - if you follow the instructions just after the code (to add a user control to your peoject with a tree view inside), then that should solve the issue.

    Regards,

    Kean

  12. I am interested in creating a tree view structure of a sheet set file without AutoCAD can this be done?

  13. Kean Walmsley Avatar

    I don't know if there's a technical dependency on the sheetset access API on core AutoCAD - it should be easy enough for you to find out. That said, AutoCAD would need to be installed on the same system, I would think.

    Kean

  14. I'm trying to get information out of a sheet set. Using your code I can get the subsets, sheets and custom properties of the sheets, But I can't get the hardwired properties of the sheets.
    Sheet.GetNumber gets the sheet number
    Sheet.GetTitle get the title of the sheet
    Sheet.Name gets a hybrid of the two
    But I'm after the revision. I assume is somewhere in Sheet.GetDirectlyOwnedObjects, but I have no idea how the data is stored in there. It returns an array of System.___Comobject that I have no idea how to look in.

  15. Kean Walmsley Avatar

    Hi Chris,

    I borrowed the approach from Fenton Webb, so the best would be to ping ADN on this (either via the ADN site or the AutoCAD .NET Discussion Group).

    Regards,

    Kean

Leave a Reply

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