雪山飞狐_lzh 发表于 2009-9-7 15:36:00

本帖最后由 作者 于 2009-9-9 8:20:41 编辑

十一、将AC2009嵌入到对话框中
March 27, 2008
Embedding AutoCAD 2009 in a standalone dialog
This post takes a look at another topic outlined in this overview of the new API features in AutoCAD 2009.
AutoCAD 2009 introduces the ability to embed the application in a standalone dialog or form via an ActiveX control. This capability has been around for a number of releases of AutoCAD OEM, but this feature has now been made available in the main AutoCAD product.
The way the control works is to launch an instance of AutoCAD in the background (it should go without saying that AutoCAD needs to be installed on the system, but I've said it, anyway :-) and it then pipes the graphics generated by AutoCAD into the area specified by the bounds of the control. It also then pipes back any mouse movements or keystrokes, to allow the embedded AutoCAD to be controlled. It's pretty neat: you'll see the standard cursor, be able to enter commands via dynamic input, and more-or-less do whatever can be done inside the full product.
The control is especially handy if you want to present a reduced user-interface to the people using the product (which is really what AutoCAD OEM is for, in a nutshell, although the development effort involved in creating a full AutoCAD OEM application makes it inappropriate for quick & easy UI streamlining).
Let's start our look at this control by creating a new C# Windows Application project in Visual Studio 2005 (you can use whatever ActiveX container you like, though - it should even work from a web-page or an Office document):

Once Visual Studio has created the new project, we need to add our control to the toolbox. If you right-click on the toolbox, you'll be able to select "Choose Items...".

From here, there should be an item "AcCtrl" in the list of COM Components. Otherwise you can browse to it in c:\Program Files\Common Files\Autodesk Shared\AcCtrl.dll.

Then you simply need to place the control on your form.

Once we've done that, we're going to add a few more controls - for the drawing path, and a text string for commands we want to try "posting" to the embedded AutoCAD application.

Here's the C# code we'll use to drive the embedded control from the form. You should be able to work out what the various controls have been called in the project by looking at the code.
using System;
using System.Windows.Forms;
namespace EmbedAutoCAD
{
public partial class MainForm : Form
{
    public MainForm()
    {
      InitializeComponent();
    }
    private void browseButton_Click(
      object sender, EventArgs e)
    {
      OpenFileDialog dlg =
      new OpenFileDialog();
      dlg.InitialDirectory =
      System.Environment.CurrentDirectory;
      dlg.Filter =
      "DWG files (*.dwg)|*.dwg|All files (*.*)|*.*";
      Cursor oc = Cursor;
      String fn = "";
      if (dlg.ShowDialog() ==
      DialogResult.OK)
      {
      Cursor = Cursors.WaitCursor;
      fn = dlg.FileName;
      Refresh();
      }
      if (fn != "")
      this.drawingPath.Text = fn;
      Cursor = oc;
    }
    private void loadButton_Click(
      object sender, EventArgs e)
    {
      if (System.IO.File.Exists(drawingPath.Text))
      axAcCtrl1.Src = drawingPath.Text;
      else
      MessageBox.Show("File does not exist");
    }
    private void postButton_Click(
      object sender, EventArgs e)
    {
      axAcCtrl1.PostCommand(cmdString.Text);
    }
}
}Finally, when we run the application and load a drawing via the browse/load buttons, the real fun starts. :-)

Try entering commands via dynamic input, or via the "Post a command" textbox. You might feel a little disorientated due to the lack of a command-line (I do love my command-line ;-), but dynamic input allows you to at least see what you're typing.
Here's the C# project for you to download.

雪山飞狐_lzh 发表于 2009-9-9 08:19:00

十二、可选多种文件格式的文件对话框
August 21, 2009
Allowing a user to select from multiple file formats inside AutoCAD using .NET
This is a topic that I’ve covered to some degree in a couple of previous posts:
Using AutoCAD's file selection dialog from .NET
Replacing AutoCAD's OPEN command using .NET
Neither focused on the question of allowing the user to select from a number of different file format filters (try saying “different file format filters” five times, quickly :-), so I thought I’d compare and contrast the two approaches in this post.
There are two primary mechanisms provided by AutoCAD’s .NET interface for file selection:
   1. Methods from the Editor class:
          * GetFileNameForOpen()
          * GetFileNameForSave()
   2. Classes in the Autodesk.AutoCAD.Windows namespace:
          * OpenFileDialog
          * SaveFileDialog
Within the two mechanisms the open and save choices are broadly similar, the main differences being around prompting the user to overwrite (in the case of save) and the need for a file to exist (in the case of open). In this case we’ll focus on open: providing the same treatment for save is left as an exercise for the reader.
Here’s the C# code that compares the two approaches. You may need to add additional project references to AcWindows.dll and System.Windows.Forms:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;

namespace FileSelectionOptions
{
public class Commands
{
   
    public void SelectFiles()
    {
      Editor ed =
      Application.DocumentManager.MdiActiveDocument.Editor;

      // First let's use the editor method, GetFileNameForOpen()

      PromptOpenFileOptions opts =
      new PromptOpenFileOptions(
          "Select a file using Editor.GetFileNameForOpen()"
      );
      opts.Filter =
      "Drawing (*.dwg)|*.dwg|Design Web Format (*.dwf)|*.dwf|" +
      "All files (*.*)|*.*";
      PromptFileNameResult pr = ed.GetFileNameForOpen(opts);

      if (pr.Status == PromptStatus.OK)
      {
      ed.WriteMessage(
          "\nFile selected was \"{0}\".\n",
          pr.StringResult
      );
      }

      // Now let's create and use an OpenFileDialog object

      OpenFileDialog ofd =
      new OpenFileDialog(
          "Select a file using an OpenFileDialog",
          null,
          "dwg; dwf; *",
          "SelectFileTest",
          OpenFileDialog.OpenFileDialogFlags.DoNotTransferRemoteFiles
      );
      System.Windows.Forms.DialogResult dr = ofd.ShowDialog();
      if (dr == System.Windows.Forms.DialogResult.OK)
      {
      ed.WriteMessage(
          "\nFile selected was \"{0}\".\n",
          ofd.Filename
      );
      }
    }
}
}
Now let’s see what happens when we run the SF command.
Here’s the first dialog displayed using Editor.GetFileNameForOpen():

And here’s the equivalent dialog using OpenFileDialog:

Overall I prefer the way the control you have over the filter list using the GetFileNameForXxx() methods: OpenFileDialog has you provide the extensions and then attempts to determine the appropriate description (which works fine for DWGs but less so for other extensions, as far as I can tell).

雪山飞狐_lzh 发表于 2010-2-3 22:15:00

十三、在工具面板中动态加入项目
Adding items to an AutoCAD tool palette using .NET
This post carries directly on from the last one, which implemented a rudimentary “Quick SaveAs” capability in AutoCAD. Much of the explanation behind the design of today’s code is there, so please do read it first (if you haven’t already).
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows.ToolPalette;
using System.Runtime.InteropServices;
using System.IO;
using System;
namespace QuickSaveAs
{
public class Commands
{
    // Set up static variable for the path to our folder
    // of drawings, as well as the base filename and a
    // counter to make the unique filename
    static string _path = "",
                  _base = "";
    static int _count = 0;
    // Various filename and path-related constants
    const string sfxSep = " ",
               extSep = ".",
               pthSep = "\\",
               lspSep = "/",
               dwgExt = ".dwg",
               scrExt = ".txt",
               bmpExt = ".bmp",
               bmpLoc = "Images",
               scrLoc = "Scripts";
    // Our QuickSaveAs command
   
    public void QuickSaveAs()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      Database db = doc.Database;
      // If this is the first time run...
      if (_path == "" || _base == "")
      {
      // Ask the user for a base file location
      PromptSaveFileOptions opts =
          new PromptSaveFileOptions(
            "Select location to save first drawing file"
          );
      opts.Filter = "Drawing (*.dwg)|*.dwg";
      PromptFileNameResult pr =
          ed.GetFileNameForSave(opts);
      // Delete the file, if it exists
      // (may be a problem if the file is in use)
      
      if (File.Exists(pr.StringResult))
      {
          try
          {
            File.Delete(pr.StringResult);
          }
          catch { }
      }
      if (pr.Status == PromptStatus.OK)
      {
          // If a file was selected, and it contains a path...
          if (pr.StringResult.Contains(pthSep))
          {
            // Separate the path from the file name
            int idx = pr.StringResult.LastIndexOf(pthSep);
            _path =
            pr.StringResult.Substring(0, idx);
            string fullname =
            pr.StringResult.Substring(idx+1);
            
            // If the path has an extension (this should always
            // be the case), extract the base file name
            if (fullname.Contains(extSep))
            {
            _base =
                fullname.Substring(
                  0,
                  fullname.LastIndexOf(extSep)
                );
            // Create folders for our icons and our scripts
            Directory.CreateDirectory(
                _path + pthSep + bmpLoc
            );
            Directory.CreateDirectory(
                _path + pthSep + scrLoc
            );
            }
          }
      }
      }
      // Assuming the path and name were set appropriately...
      if (_path != "" && _base != "")
      {
      string name = _base;
      // Add our suffix if not the first time run
      if (_count > 0)
          name += sfxSep + _count.ToString();
      // Our drawing is located in the base path
      string dwgPath = _path + pthSep + name + dwgExt;
      
      // While our script is in a sub-folder
      string scrPath =
          _path + pthSep + scrLoc + pthSep + name + scrExt;
      // Create a dummy script, so we can make sure we pick
      // up the contents in our dummy execute command
      File.WriteAllText(
          scrPath,
          "This is a dummy script for " + name + "."
      );
      // Now we want to save our drawing and use the image
      // for our tool icon
      // Using either COM or .NET doesn't generate a
      // thumbnail in the resultant file (or its Database)
      // .NET:
      // db.SaveAs(dwgPath, false, DwgVersion.Current, null);
      
      // COM:
      // AcadDocument adoc = (AcadDocument)doc.AcadDocument;
      // adoc.SaveAs(dwgPath, AcSaveAsType.acNative, null);
      // So we'll send commands to the command-line
      // We'll use LISP, to avoid having to set FILEDIA to 0
      object ocmd = Application.GetSystemVariable("CMDECHO");
      string dwgPath2 = dwgPath.Replace(pthSep, lspSep);
      string scrPath2 = scrPath.Replace(pthSep, lspSep);
      string c1 =
          "(setvar \"CMDECHO\" 0)" +
          "(command \"_.SAVEAS\" \"\" \"" + dwgPath2 + "\")";
      string c2 =
          "(setvar \"CMDECHO\" " + ocmd.ToString() + ")" +
          "(tp-create \"" + name + "\" \"" + scrPath2 + "\")" +
          "(princ) ";
      string cmd = c1 + c2;
      if (cmd.Length <= 255)
      {
          doc.SendStringToExecute(cmd, false, false, false);
      }
      else
      {
          doc.SendStringToExecute(c1+" ", false, false, false);
          doc.SendStringToExecute(c2, false, false, false);
      }
      // Print a confirmation message for the DWG save
      // (which actually gets displayed before the queued
      // string gets executed, but anyway)
      ed.WriteMessage("\nSaved to: \"" + dwgPath + "\"");
      _count++;
      }
    }
    // Our LISP-registered continuation function to create a
    // command tool on our tool palette
   
    public ResultBuffer CreateToolPaletteCommand(
      ResultBuffer rb
    )
    {
      const int RTSTR = 5005;
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      if (rb == null)
      {
      ed.WriteMessage("\nError: too few arguments.");
      }
      else
      {
      // We're only interested in the first two arguments
      Array args = rb.AsArray();
      if (args.Length != 2)
      {
          ed.WriteMessage(
            "\nError: wrong number of arguments."
          );
      }
      else
      {
          // First argument is the name, second is the path
          // to the script
          TypedValue tv1 = (TypedValue)args.GetValue(0);
          TypedValue tv2 = (TypedValue)args.GetValue(1);
          if (tv1 != null && tv1.TypeCode == RTSTR &&
            tv2 != null && tv2.TypeCode == RTSTR)
          {
            string name = Convert.ToString(tv1.Value);
            string lspScrPath = Convert.ToString(tv2.Value);
            string scrPath =
            lspScrPath.Replace(lspSep, pthSep);
            bool success =
            CreateCommand(doc.Database, name, scrPath);
            return
            (success ?
                new ResultBuffer(
                  new TypedValue(RTSTR, tv1.Value)
                )
                : null);
          }
      }
      }
      return null;
    }
    // Function to add a command tool to our tool palette to
    // execute the script
    private bool CreateCommand(
      Database db,
      string name,
      string scrPath
    )
    {
      const string catName = "ScriptCatalog";
      const string palName = "Scripts";
      ToolPaletteManager tpm = ToolPaletteManager.Manager;
      // Get the GUID of our dummy custom tool
      Type t = typeof(DummyTool);
      GuidAttribute ga =
      (GuidAttribute)t.GetCustomAttributes(
          typeof(GuidAttribute), false);
      Guid g = new Guid(ga.Value);
      // Instanciate our dummy tool - this will allow us to use
      // its helper functions
      DummyTool tool = new DummyTool();
      Catalog cat;
      Palette pal = null;
      // First we check whether our GUID is in a catalog
      CatalogItem ci = tpm.StockToolCatalogs.Find(g);
      if (ci != null)
      {
      // If it is, search each catalog for our palette
      foreach(CatalogItem ci2 in tpm.Catalogs)
      {
          for (int i = 0; i < ci2.ChildCount; i++)
          {
            CatalogItem ci3 = ci2.GetChild(i);
            if (ci3 != null && ci3.Name == palName)
            {
            pal = ci3 as Palette;
            break;
            }
          }
          if (pal != null)
            break;
      }
      }
      // If we didn't find our palette, create it
      if (pal == null)
      {
      cat = tool.CreateStockTool(catName);
      pal = tool.CreatePalette(cat, palName);
      }
      // To add our command tool instance we need an icon
      ImageInfo ii = new ImageInfo();
      if (db.ThumbnailBitmap != null)
      {
      // Which we create from the Database's thumbnail
      string bmpPath =
          _path + pthSep + bmpLoc + pthSep + name + bmpExt;
      db.ThumbnailBitmap.Save(bmpPath);
      ii.ResourceFile = bmpPath;
      }
      ii.Size = new System.Drawing.Size(65, 65);
      // And then we use our dummy tool to create the
      // command tool
      tool.CreateCommandTool(
      pal,
      name,
      ii,
      "_EXECSCR \"" + scrPath.Replace(pthSep, lspSep) + "\""
      );
      
      // Finally we reload the catalogs to display the change
      tpm.LoadCatalogs();
      
      return true;
    }
    // A dummy command to simulate the execution of our script
    // (which simply reads the contents and displays them on
    // the command-line)
   
    public void ExecuteScript()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      PromptResult pr =
      ed.GetString(
          "\nEnter location of script to execute: "
      );
      if (pr.Status == PromptStatus.OK)
      {
      string path =
          pr.StringResult.Replace(lspSep, pthSep);
      if (File.Exists(path))
      {
          string contents = File.ReadAllText(path);
          ed.WriteMessage(
            "\nDummy script contained: \"{0}\"",
            contents
          );
      }
      }
    }
}
// Our dummy tool which simply derives from CustomToolBase
// (there may be a more straightforward way to get access
// to the helpers in CustomToolBase, but anyway)



public class DummyTool : CustomToolBase
{
}
}
Today we’re taking the code further by automatically creating an item on a tool palette with the thumbnail of the recently-saved drawing which, when used, will run a script created when we saved the drawing.
Some notes on the changes:
Lines 5, 6 and 8 add some additional namespaces.
It's worth noting that you'll need to add an assembly reference to AcTcMgd (depending on the version of AutoCAD you're using), for the updated code to build.
Lines 28-32 add some additional constants related to scripts and icon images.
Lines 95-102 create additional directories for our scripts and images.
Lines 123-134 create a dummy script when we save a drawing.
Lines 154-173 deal with a limitation we have with sending strings to the command-line:
AutoCAD’s command-line input buffer is only 255 characters in size, so if our string is longer (because of a deep file path), we send it in two pieces, terminating the first with a space character. It’s still possible that really deep paths could cause a problem with this code, but splitting the string further is left as an exercise for the reader. :-)
The string also calls a new continuation function (registered via LISP, see below) to create an item on our tool palette.
Lines 185-241 register a continuation function via LISP, so we can get control back in our code once the SAVEAS command has completed.
Lines 243-332 define a function to create an item on our tool palette. This function is called from the above LISP function.
Lines 334-363 simulate the execution of a script, so that when our tool palette is used, something happens.
The command simply reads in the contents of the "script" file and prints the contents to the command-line.
Lines 365-374 define a dummy custom tool, which we use as a shortcut for certain tool palette-related operations.
Now let’s take a look at the results of running this. In the last post we saw an example where a number of drawings get created in a particular folder. If we perform the same operations with this code, the same things happen (no need to show the drawings or the command-line output, they should be the same), but in addition we see a tool palette populated with images of our model at various stages:

You may have to right-click the stacked tool palette tabs to locate the “Scripts” tool palette (I haven’t found a way of doing this automatically, as it wasn’t really essential for my particular application).
When we select the items on the tool palette in sequence, we see our EXECSCR command is called with the location of the script created for each DWG file, which then gets read and printed to the command-line:
Command: _EXECSCR
Enter location of script to execute:
"C:/QSaveAs Test/Scripts/Solid model.txt"
Dummy script contained: "This is a dummy script for Solid model."
Command: _EXECSCR
Enter location of script to execute:
"C:/QSaveAs Test/Scripts/Solid model 1.txt"
Dummy script contained: "This is a dummy script for Solid model 1."
Command: _EXECSCR
Enter location of script to execute:
"C:/QSaveAs Test/Scripts/Solid model 2.txt"
Dummy script contained: "This is a dummy script for Solid model 2."
Command: _EXECSCR
Enter location of script to execute:
"C:/QSaveAs Test/Scripts/Solid model 3.txt"
Dummy script contained: "This is a dummy script for Solid model 3."
Command: _EXECSCR
Enter location of script to execute:
"C:/QSaveAs Test/Scripts/Solid model 4.txt"
Dummy script contained: "This is a dummy script for Solid model 4."
Command: _EXECSCR
Enter location of script to execute:
"C:/QSaveAs Test/Scripts/Solid model 5.txt"
Dummy script contained: "This is a dummy script for Solid model 5."
While clearly not actually doing anything useful, the script file that we’ve created (and I don’t mean script in the AutoCAD sense of the term – I’m using it in a more generic sense) could actually be regenerating the drawing contents (for instance). With a little more work. :-)

雪山飞狐_lzh 发表于 2010-2-3 22:21:00

十四、简化的QuickSaveAs命令(2010版本)
October 30, 2009
Streamlined QuickSaveAs command for AutoCAD 2010
A big thanks to Tony Tanzillo for providing some tips to help improve the implementation of the application we saw in these previous posts: in particular Tony pointed out the ability of AutoCAD 2010 to generate a thumbnail image for a document in the editor programmatically (something I had forgotten was possible… at least I think I knew it existed – it certainly seemed familiar once I saw it :-S). Anyway, the version of the code in this post will only work from AutoCAD 2010 onwards because of the use of this function, Document.CapturePreviewImage().
Tony’s code also showed some interesting capabilities of the .NET Framework related to filename and path manipulation, so I also borrowed some of those techniques to avoid some ugly string parsing/manipulation.
Because of this ability to generate thumbnails – something I really wanted from the beginning – we can avoid the use of SAVEAS and simply use Document.SaveAs(), which will save a copy of the drawing without renaming the version in the editor (which in my particular situation is desirable). And clearly there’s no longer any need for a continuation function (whether or not you believe that was an appropriate way to implement the application in the first place).
Here’s the updated C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows.ToolPalette;
using System.Runtime.InteropServices;
using System.Drawing;
using System.IO;
using System;

namespace QuickSaveAs
{
public class Commands
{
    // Set up static variable for the path to our folder
    // of drawings, as well as the base filename and a
    // counter to make the unique filename

    static string _path = "",
                  _base = "";
    static int _count = 0;

    // Various filename and path-related constants

    const string sfxSep = " ",
                pthSep = "\\",
                lspSep = "/",
                dwgExt = ".dwg",
                scrExt = ".txt",
                bmpExt = ".bmp",
                bmpLoc = "Images",
                scrLoc = "Scripts";

    // Our QuickSaveAs command

   
    public void QuickSaveAs()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      Database db = doc.Database;

      // If this is the first time run...

      if (_path == "" || _base == "")
      {
      // Ask the user for a base file location

      PromptSaveFileOptions opts =
          new PromptSaveFileOptions(
            "Select location to save first drawing file"
          );
      opts.Filter = "Drawing (*.dwg)|*.dwg";
      PromptFileNameResult pr =
          ed.GetFileNameForSave(opts);

      if (pr.Status == PromptStatus.OK)
      {
          // If a file was selected, and it contains a path...

          // Separate the path from the file name

          _base =
            Path.GetFileNameWithoutExtension(pr.StringResult);
          _path =
            Path.GetDirectoryName(pr.StringResult);

          // Create folders for our icons and our scripts

          Directory.CreateDirectory(
            _path + pthSep + bmpLoc
          );
          Directory.CreateDirectory(
            _path + pthSep + scrLoc
          );
      }
      }

      // Assuming the path and name were set appropriately...

      if (_path != "" && _base != "")
      {
      string name = _base,
            dwgFile;

      // Add our suffix if not the first time run

      do
      {
          if (_count > 0)
            name += sfxSep + _count.ToString();

          // Our drawing is located in the base path

          dwgFile = _path + pthSep + name + dwgExt;
          _count++;
      }
      while (File.Exists(dwgFile));

      // While our script is in a sub-folder

      string scrPath =
          _path + pthSep + scrLoc + pthSep + name + scrExt;

      // Create a dummy script, so we can make sure we pick
      // up the contents in our dummy execute command

      File.WriteAllText(
          scrPath,
          "This is a dummy script for " + name + "."
      );

      // Now we want to save our drawing and use the image
      // for our tool icon

      if (!string.IsNullOrEmpty(dwgFile))
      {
          Bitmap thumb = doc.CapturePreviewImage(320, 240);
          doc.Database.ThumbnailBitmap = thumb;

          doc.Database.SaveAs(dwgFile, DwgVersion.Current);
          ed.WriteMessage(
            "\nCopy of current document saved to {0}",
            Path.GetFileName(dwgFile)
          );

          CreateCommand(thumb, name, scrPath);
      }
      }
    }

    // Function to add a command tool to our tool palette to
    // execute the script

    private void CreateCommand(
      Bitmap thumb,
      string name,
      string scrPath
    )
    {
      const string catName = "ScriptCatalog";
      const string palName = "Scripts";

      ToolPaletteManager tpm = ToolPaletteManager.Manager;

      // Get the GUID of our dummy custom tool

      Type t = typeof(DummyTool);
      GuidAttribute ga =
      (GuidAttribute)t.GetCustomAttributes(
          typeof(GuidAttribute), false);
      Guid g = new Guid(ga.Value);

      // Instanciate our dummy tool - this will allow us to use
      // its helper functions

      DummyTool tool = new DummyTool();
      Catalog cat;
      Palette pal = null;

      // First we check whether our GUID is in a catalog

      CatalogItem ci = tpm.StockToolCatalogs.Find(g);
      if (ci != null)
      {
      // If it is, search each catalog for our palette

      foreach(CatalogItem ci2 in tpm.Catalogs)
      {
          for (int i = 0; i < ci2.ChildCount; i++)
          {
            CatalogItem ci3 = ci2.GetChild(i);
            if (ci3 != null && ci3.Name == palName)
            {
            pal = ci3 as Palette;
            break;
            }
          }
          if (pal != null)
            break;
      }
      }

      // If we didn't find our palette, create it

      if (pal == null)
      {
      cat = tool.CreateStockTool(catName);
      pal = tool.CreatePalette(cat, palName);
      }

      // To add our command tool instance we need an icon

      ImageInfo ii = new ImageInfo();
      if (thumb != null)
      {
      // Which we create from the Database's thumbnail

      string bmpPath =
          _path + pthSep + bmpLoc + pthSep + name + bmpExt;
      thumb.Save(bmpPath);
      ii.ResourceFile = bmpPath;
      }
      ii.Size = new System.Drawing.Size(65, 65);

      // And then we use our dummy tool to create the
      // command tool

      tool.CreateCommandTool(
      pal,
      name,
      ii,
      "_EXECSCR \"" + scrPath.Replace(pthSep, lspSep) + "\""
      );

      // Finally we reload the catalogs to display the change

      tpm.LoadCatalogs(
      CatalogTypeFlags.Catalog,
      LoadFlags.LoadImages
      );
    }

    // A dummy command to simulate the execution of our script
    // (which simply reads the contents and displays them on
    // the command-line)

   
    public void ExecuteScript()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      PromptResult pr =
      ed.GetString(
          "\nEnter location of script to execute: "
      );
      if (pr.Status == PromptStatus.OK)
      {
      string path =
          pr.StringResult.Replace(lspSep, pthSep);
      if (File.Exists(path))
      {
          string contents = File.ReadAllText(path);
          ed.WriteMessage(
            "\nDummy script contained: \"{0}\"",
            contents
          );
      }
      }
    }
}

// Our dummy tool which simply derives from CustomToolBase
// (there may be a more straightforward way to get access
// to the helpers in CustomToolBase, but anyway)




public class DummyTool : CustomToolBase
{
}
}
The QSAVEAS command executes more quickly and cleanly, providing results comparable to the previous version’s:

雪山飞狐_lzh 发表于 2012-7-21 14:14:55

本帖最后由 雪山飞狐_lzh 于 2012-7-21 14:16 编辑

十五 从AutoCAD的可绑定对象层获取数据
Dumping data from AutoCAD’s Bindable Object Layer using .NET
Some time ago, I posted code that used the Autodesk.AutoCAD.Windows.Data namespace to list the hatch patterns in the current drawing.

Fenton Webb posted a follow-up on the AutoCAD DevBlog that took this further, extracting additional data from AutoCAD and using it to populate an Excel spreadsheet. Within that post, Fenton showed the technique required to access and iterate across other data collections – something I hadn’t managed to do when creating my original post.

Rather than repeat exactly what Fenton has put together – which is really nice, do take a look at it – I’m just taking a small amount of Fenton’s code and replicating it here – along with some code using reflection, to keep the code succinct – to dump some data to the command-line:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows.Data;



public class DataStuff

{



static public void GetHatchPatterns()

{

    Editor ed =

      Application.DocumentManager.MdiActiveDocument.Editor;

    foreach (

      string str in HatchPatterns.Instance.AllPatterns)

      ed.WriteMessage("\n" + str);

}





static public void DumpBindableObjects()

{

    Editor ed =

      Application.DocumentManager.MdiActiveDocument.Editor;



    // Get our set of collections



    var cols = Application.UIBindings.Collections;



    // Use reflection to determine the properties of type

    // DataItemCollection (could extend to support others, too)



    var t = cols.GetType();

    var tprops = t.GetProperties();

    foreach (var tprop in tprops)

    {

      // Make sure we only deal with DataItemCollection properties

      // that do not take any parameters



      if (

      tprop.PropertyType == typeof(DataItemCollection) &&

      tprop.GetGetMethod().GetParameters().Length == 0

      )

      {

      // Dump our the property name first



      ed.WriteMessage("\n{0}:", tprop.Name);



      // Get the collection itself and iterate through it



      var col = (DataItemCollection)tprop.GetValue(cols, null);

      foreach (var desc in col)

      {

          // Get the collection's property descriptors



          var props = desc.GetProperties();

          try

          {

            // Dump the value of each "Name" property



            ed.WriteMessage(

            " \"{0}\"", props["Name"].GetValue(desc)

            );

          }

          catch

          {

            // Just in case no "Name" property exists, we try-catch

          }

      }

      }

    }

}

}
The main trick is actually to get data from the BOL – AutoCAD’s Bindable Object Layer – via the various properties inside Application.UIBindings.Collections.

We’re using reflection to avoid hardcoding the various properties we want to access: we just loop through the main collection set’s properties, looking for any of type DataItemCollection. We might also extend the code to support other property types – such as EnumItemCollection – but that’s left as an exercise for the reader.

Here’s the command-line output we see in a blank drawing when we run the DUMPBOL command:

Command: DUMPBOL

UcsPlanes:

PlotStyles:

RenderPresets: "" "" "" "" ""

TableCellStyles:

NamedViews:

Linetypes: "ByBlock" "ByLayer" "Continuous" "PHANTOM2"

LayerFilters: "All" "All Used Layers" "Unreconciled New Layers" "Viewport Overrides"

LayerStates:

Layers: "0"

VisualStyles: "2D Wireframe" "Conceptual" "Hidden" "Realistic" "Shaded" "Shaded with edges" "Shades of Gray" "Sketchy" "Wireframe" "X-Ray"

TextStyles: "Standard" "Annotative"

DimensionStyles: "Standard" "Annotative"

DetailViewStyles: "Imperial24"

SectionViewStyles: "Imperial24"

MleaderStyles: "Annotative" "Standard"

TableStyles: "Standard"

雪山飞狐_lzh 发表于 2012-7-21 14:22:43

十六 利用绑定对象层监控图层变化
Finding out about changes to AutoCAD layers via the Bindable Object Layer using .NET
In the last few posts on this topic, we saw some examples of getting information from and controlling AutoCAD via its Bindable Object Layer.

In this post, we’re going to look at a way to find out when changes are made to AutoCAD’s layer table: when layers are added, changed or removed.

There are certainly other ways to do this: you can use Database.ObjectAppended(), ObjectModified() and ObjectErased() to find out about changes to LayerTableRecords, for instance, but this is an alternative approach that may be interesting to some people.

In this implementation, we attach some event handlers to keep an eye on what’s happening in the BOL collection bound to the layer table. We also maintain a list of layer names so we have good information on what has changed (in terms of what layers have been removed, at least – if we wanted information on specific changes we’d need to cache more information than that). From this starting point, it should be possible for developers to get a more comprehensive mechanism that provides information on when commands create or remove multiple layers (something this particular implementation hasn’t specifically been designed to deal with or tested against).

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows.Data;

using System.Collections.Specialized;

using System.Collections.Generic;

using System.ComponentModel;



public class BolInfo

{

static private List<string> _layerNames = null;





static public void GetNotifiedOnLayerChange()

{

    // Get our layer list and extract the initial layer names



    var layers = Application.UIBindings.Collections.Layers;

    UpdateStoredLayerNames();



    // Attach event handlers to the layer list...



    // Find out when items are added or removed from the

    // collection



    layers.CollectionChanged +=

      (s, e) =>

      {

      Editor ed =

          Application.DocumentManager.MdiActiveDocument.Editor;



      ed.WriteMessage("\nCollection changed: {0}", e.Action);



      if (

          e.Action == NotifyCollectionChangedAction.Add &&

          e.NewStartingIndex == -1

      )

      {

          // What happens for commands that create >1 layers?



          var newLays = Application.UIBindings.Collections.Layers;

          ed.WriteMessage(

            "\nNew item: \"{0}\"",

            GetItemValue(newLays)

          );

      }

      else if (

          e.Action == NotifyCollectionChangedAction.Remove

      )

      {

          ed.WriteMessage(

            "\nRemoved item: \"{0}\"",

            _layerNames

          );

      }



      // As we can't access data in e.NewItems or e.OldItems

      // (they contain NewDataItem objects - a class that isn't

      // exposed) get the collection again and list it



      ed.WriteMessage("\nUpdated collection: ");



      foreach (

          var item in Application.UIBindings.Collections.Layers

      )

      {

          ed.WriteMessage(" \"{0}\"", GetItemValue(item));

      }

      UpdateStoredLayerNames();

      };



    // Find out when items have been changed in the collection

    // (although not what specifically has changed)



    layers.ItemsChanged +=

      (s, e) =>

      {

      Editor ed =

          Application.DocumentManager.MdiActiveDocument.Editor;



      ed.WriteMessage("\nItem(s) changed.");

      UpdateStoredLayerNames();

      };



    // Find out when properties of the collection (typically

    // the Count, for instance) have changed



    layers.PropertyChanged +=

      (s, e) =>

      {

      Editor ed =

          Application.DocumentManager.MdiActiveDocument.Editor;



      ed.WriteMessage(

          "\nCollection property changed: {0}", e.PropertyName

      );

      };

}



// Store a cache of the layer names



private static void UpdateStoredLayerNames()

{

    var layers = Application.UIBindings.Collections.Layers;



    _layerNames = new List<string>(layers.Count);

    foreach (var layer in layers)

    {

      _layerNames.Add(GetItemValue(layer));

    }

}



// Extract the name of an item from the item descriptor



private static string GetItemValue(ICustomTypeDescriptor item)

{

    return (string)item.GetProperties()["Name"].GetValue(item);

}

}

When we run the custom LAYMODS command and then use the standard LAYER command to add, change and remove some layers, we can see the level of information provided:Command: LAYMODS

Command: LAYER

Command:

Command:

Collection changed: Add

New item: "Layer1"

Updated collection:"0" "Layer1"

Collection property changed: Count

Command:

Collection changed: Add

New item: "Layer2"

Updated collection:"0" "Layer1" "Layer2"

Collection property changed: Count

Command:

Collection changed: Add

New item: "Layer3"

Updated collection:"0" "Layer1" "Layer2" "Layer3"

Collection property changed: Count

Command:

Collection changed: Add

New item: "Layer4"

Updated collection:"0" "Layer1" "Layer2" "Layer3" "Layer4"

Collection property changed: Count

Command:

Collection changed: Add

New item: "Layer5"

Updated collection:"0" "Layer1" "Layer2" "Layer3" "Layer4" "Layer5"

Collection property changed: Count

Command:

Command:

Collection changed: Add

New item: "Layer7"

Updated collection:"0" "Layer1" "Layer2" "Layer3" "Layer4" "Layer5" "Layer6" "Layer7"

Collection property changed: Count

Command:

Collection changed: Add

New item: "Layer8"

Updated collection:"0" "Layer1" "Layer2" "Layer3" "Layer4" "Layer5" "Layer6" "Layer7" "Layer8"

Collection property changed: Count

Command:

Collection changed: Remove

Removed item: "Layer3"

Updated collection:"0" "Layer1" "Layer2" "Layer4" "Layer5" "Layer6" "Layer7" "Layer8"

Collection property changed: Count

Command:

Collection changed: Remove

Removed item: "Layer4"

Updated collection:"0" "Layer1" "Layer2" "Layer5" "Layer6" "Layer7" "Layer8"

Collection property changed: Count

Command:

Collection changed: Remove

Removed item: "Layer5"

Updated collection:"0" "Layer1" "Layer2" "Layer6" "Layer7" "Layer8"

Collection property changed: Count

Command:

Collection changed: Remove

Removed item: "Layer6"

Updated collection:"0" "Layer1" "Layer2" "Layer7" "Layer8"

Collection property changed: Count

Command:

Item(s) changed.

Command:

Item(s) changed.

Command:

Command:

Collection changed: Add

New item: "Layer4"

Updated collection:"0" "Layer1" "Layer2" "Layer7" "Layer8" "Layer3" "Layer4"

Collection property changed: Count

Command:

Collection changed: Add

New item: "Layer5"

Updated collection:"0" "Layer1" "Layer2" "Layer7" "Layer8" "Layer3" "Layer4" "Layer5"

Collection property changed: Count

Command:

Collection changed: Add

New item: "Layer6"

Updated collection:"0" "Layer1" "Layer2" "Layer7" "Layer8" "Layer3" "Layer4" "Layer5" "Layer6"

Collection property changed: Count
页: 1 [2]
查看完整版本: Kean专题(15)—User_Interface