Run a sample command (with parameters) using c# .NET

Run a sample command (with parameters) using c# .NET

Kh.mbkh
Advocate Advocate
3,384 Views
9 Replies
Message 1 of 10

Run a sample command (with parameters) using c# .NET

Kh.mbkh
Advocate
Advocate

I am trying to call a command "-PDFIMPORT" in a button_click event, but got an "invalid input".

 

I am new on .Net (I used to work with Autolisp). Thanks !

 

private void BtImport_Click(object sender, EventArgs e)
{
    Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;
string pageText = TxPages.Text;
string PdfPath = TxPdfPath.Text;
Point3d startPoint = new Point3d(0, 0, 0); 
ed.Command("-PDFATTACH", PdfPath , pageText , startPoint.X, startPoint.Y, "1", "0");
}   

 

0 Likes
Accepted solutions (3)
3,385 Views
9 Replies
Replies (9)
Message 2 of 10

_gile
Consultant
Consultant

Hi,

The Editor.Command method takes the same argument types à the AutoLISP command function.

AutoLISP:

(command "_-PDFATTACH" PdfPath pageText '(0. 0. 0.) 1.0 0.0)

C#:

ed.Command("_-PDFATTACH", PdfPath, pageText, startPoint, 1.0, 0.0);

 

Iif you want to pass all the arguments as strings, you have to convert the point X and Y coordinates into strings.

C#

ed.Command("_-PDFATTACH", PdfPath , pageText , startPoint.X.ToString() + "," + startPoint.Y.ToString(), "1", "0");

or

ed.Command("_-PDFATTACH", PdfPath , pageText , $"{startPoint.X},{startPoint.Y}", "1", "0");

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 3 of 10

Kh.mbkh
Advocate
Advocate

Thanks Gile, 

It's still not working (Tried it with sample command ed.Command("_Line", startPoint, EndPoint); ) 

Khmbkh_0-1740332563077.png

 

0 Likes
Message 4 of 10

_gile
Consultant
Consultant

You should show the code which shows the dialog because  you can only call Editor.Command in the document context (i.e., a modal dialog) not in the application context (i.e., a modeless dialog).

About the basic of AutoCAD and User Interfaces (dialog boxes, palettes), you can read this 'tutotrial'.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 5 of 10

kerry_w_brown
Advisor
Advisor

@Kh.mbkh wrote:

Thanks Gile, 

It's still not working (Tried it with sample command ed.Command("_Line", startPoint, EndPoint); ) 

Khmbkh_0-1740332563077.png

 


I'm guessing the problem is elsewhere ( not the code shown here ).

More details will be required.

 

Perhaps ( temporarily )replace that code with a simple WriteMessage

and adding a try, catch block around your code.

Perhaps the "Location" displayed in your message is an indicator.

 

Regards,

 


// Called Kerry or kdub in my other life.

Everything will work just as you expect it to, unless your expectations are incorrect. ~ kdub
Sometimes the question is more important than the answer. ~ kdub

NZST UTC+12 : class keyThumper<T> : Lazy<T>;      another  Swamper
0 Likes
Message 6 of 10

Kh.mbkh
Advocate
Advocate

It's a UserControl that is used in a PaletteSet (see code bellow), so I am trying to call Editor.Command() method from a button event of that UserControl. (Maybe this is why it's not possible ?)

 

namespace TestForms2
{
    public class Class1
    {
        [CommandMethod("ShowMyPalette")]
        public void ShowMyPalette()
        {
            // Create a new instance of your user control
            UserControl1 myControl = new UserControl1();

            // Create a new PaletteSet and add the control
            PaletteSet myPaletteSet = new PaletteSet("My Custom Palette");
            myPaletteSet.Add("My Control", myControl);

            // Set the size of the palette
            myPaletteSet.Size = new System.Drawing.Size(300, 200);

            // Show the palette
            myPaletteSet.Visible = true;
        }


    }
}

 

 

 

0 Likes
Message 7 of 10

_gile
Consultant
Consultant
Accepted solution

A Palette is a modeless user interface which runs in the application context. As explained in this part of the upper linked tutorial, from a palette set, you need to use the DocumentCollection.ExecuteInCommandContextAsync method to force the document context to be able to call Editor.Command method.

Something like this:

private async void BtImport_Click(object sender, EventArgs e)
{
    DocumentCollection docs = Autodesk.AutoCAD.ApplicationServices.Core.Application.DocumentManager;
    Document doc = docs.MdiActiveDocument;
    Editor ed = doc.Editor;
    string pageText = TxPages.Text;
    string PdfPath = TxPdfPath.Text;
	Point3d startPoint = Point3d.Origin;
    await docs.ExecuteInCommandContextAsync(_ =>
    {
        ed.Command("_-PDFATTACH", PdfPath, pageText, startPoint, 1.0, 0.0);
        return Task.CompletedTask;
    },
    null);
}

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 8 of 10

ActivistInvestor
Mentor
Mentor
Accepted solution

@_gile shows an example of how to call Command() from a click handler on a PaletteSet, but if you have many buttons on a paletteSet or other modeless UI that need to do this, then you should try to avoid the "copy and paste" style of programming that replicates all of that code in every button click event handler you need to use it from, and instead consolidate the code into a reusable method that makes things a bit simpler at the click-handler level, and also safer.

 

Another thing, is that I never recommend the use of ExecuteInCommandContextAsync() without issuing a stern caveat emptor, which is that this is a highly-volatile API that can crash AutoCAD very easily (any exception that's thrown by the delegate will do that).

 

So with that, below is a class with an extension method you can add to your project, that makes calling Command() from the click-handler of a button both easier and safer, that I've relied on for some time:

 

public static partial class DocumentCollectionExtensions
{
   /// <summary>
   /// A version of the Editor's Command() method that
   /// can be safely called from the application context. 
   /// 
   /// This method targets the DocumentCollection and 
   /// always operates on the active document. 
   /// 
   /// Calls to the method can be awaited to execute 
   /// code that follows after the command sequence 
   /// has executed.
   ///
   /// Important:
   ///
   /// Calls to this method should _always_ be wrapped
   /// in a try{} block, that's followed by a catch{}
   /// block that handles any exception that may be
   /// thrown by the Editor's Command() method (e.g.,
   /// ErrorStatus.InvalidInput).
   ///
   /// Failing to catch exceptions thrown by this method,
   /// or by any statements that follow an await'ed call 
   /// to this method will most-likely crash AutoCAD.
   ///
   /// </summary>
   /// <param name="args">The command arguments.</param>

   public static async Task CommandAsync(this DocumentCollection docs,
      params object[] args)
   {
      if(docs == null)
         throw new ArgumentNullException(nameof(docs));
      if(!docs.IsApplicationContext)
         throw new InvalidOperationException("invalid context");
      Document doc = docs.MdiActiveDocument;
      Task task = Task.CompletedTask;
      await docs.ExecuteInCommandContextAsync(
         delegate (object unused)
         {
            try
            {
               doc.Editor.Command(args);
               return task;
            }
            catch(System.Exception ex)
            {
               return task = Task.FromException(ex);
            }
         },
         null
      );
      if(task.IsFaulted)
      {
         throw task.Exception ??
            new AggregateException(
               new InvalidOperationException("Unspecified error"));
      }
   }
}

 

With the above class added to your project, executing commands from modeless button click handlers is no more complicated than this:

 

private async void MyButton_Click(object sender, EventArgs e)
{
   DocumentCollection docs = Application.DocumentManager;
   await docs.CommandAsync("_-PDFATTACH", "path", "pagetext", Point3d.Origin, 1.0, 0.0);
}

 

What is shown above is the absolute bare-minimum amount of code needed to execute commands, but is not the recommended way of using it, because of the danger of an exception, which will crash AutoCAD if left unhandled.

 

This is a safer and more-correct implementation:

 

private async void MyButton_Click(object sender, EventArgs e)
{
   DocumentCollection docs = Application.DocumentManager;

   try    // <- this is NOT optional!
   {
      await docs.CommandAsync("_-PDFATTACH", "path", "pagetext", Point3d.Origin, 1.0, 0.0);
      
      // TODO: add code here that runs *after* the commands have executed
   }
   catch(System.Exception ex)
   {
      docs.MdiActiveDocument.Editor.WriteMessage($"Operation failed: {ex.Message}");
   }
}

 

 

 

 

 

Message 9 of 10

Kh.mbkh
Advocate
Advocate

Thanks @_gile @ActivistInvestor Both those 2 methods fixed the issue, However, whene I am trying to do more things with PDFunderlay just added by this command, I noticed that I can not access the DBDictionary pdfDict (eLockViolation  error given)

DBDictionary pdfDict= Tx.GetObject(nod.GetAt(defDictKey), OpenMode.ForWrite) as DBDictionary;

 

My code was:

 

 

foreach (int Page in pageNumbers)
{
    startPoint = new Point3d(X, Y, 0);
    try    
    { 
        await docs.ExecuteInCommandContextAsync(_ =>
        {
            ed.Command("_-PDFATTACH", PdfPath, Page.ToString(), Point3d.Origin, 1.0, 0.0);
            ObjectId? result = PdfHandle.LoadUnloadPDFunderlay(PdfPath, Page.ToString(), "Unload");
            return Task.CompletedTask;
            },
        null);
    } 
    catch (System.Exception ex)
    {
        docs.MdiActiveDocument.Editor.WriteMessage($"Operation failed: {ex.Message}");
    }
}

 

 

 

And the  LoadUnloadPDFunderlay method I am trying to call is in another class with something like:

 

public class PdfHandle
{
    public static ObjectId? LoadUnloadPDFunderlay(string pdfPath, string pageNumber, string loadOrUnload)
    {
        Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
        Database db = doc.Database;
        Editor ed = doc.Editor;
        if (string.IsNullOrEmpty(pdfPath) || string.IsNullOrEmpty(pageNumber) || string.IsNullOrEmpty(loadOrUnload))
        {
            ed.WriteMessage("Invalid arguments. Provide PDF path, page number, and load/unload action.");
            return null;
                }
        using (Transaction Tx = db.TransactionManager.StartTransaction())
        {
            // Get Named Objects Dictionary (NOD)
            DBDictionary nod = Tx.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForRead) as DBDictionary;
            string defDictKey = UnderlayDefinition.GetDictionaryKey(typeof(PdfDefinition));
            if (!nod.Contains(defDictKey))
            {
                ed.WriteMessage("No PDF underlays found.");
                return null;
            }
     
            DBDictionary pdfDict = Tx.GetObject(nod.GetAt(defDictKey), OpenMode.ForWrite) as DBDictionary; // Here got eLockViolation  error

....

 


N.B: When I use this LoadUnloadPDFunderlay methos elsewhere it's working well.

0 Likes
Message 10 of 10

ActivistInvestor
Mentor
Mentor
Accepted solution

You're not showing the point in the first method you show, where you access the second method, but the problem is most-likely because your code is running in the application context, and you're trying to access the document without locking it, which triggers a lock violation.

 

You need to lock the document before you access elements in it:

 

Document doc = Application.DocumentManager.MdiActiveDocument;
using(doc.LockDocument())
{
   // Here, the document is locked and can
   // be safely accessed.
}
// Here, the document is no-longer locked.

 

The other issue you have is that your code is using ExecuteInCommandContextAsync() directly, and without any exception handling in the delegate. If any code in the delegate throws an exception, the catch{} handler will not catch it, and it will crash AutoCAD.

 

The solution I posted earlier was specifically for executing commands from the application context. Here is the more-general solution I use as an alternative to ExecuteInCommandContextAsync() to execute code in the document context, without running the risk of crashing AutoCAD:

 

// add this at the top of the code file:
using AcRx = Autodesk.AutoCAD.Runtime;

public static partial class AsyncDocExtensions
{
   public static async Task InvokeAsCommandAsync(this DocumentCollection docs, 
      Action<Document> action)
   {
      if(docs == null)
         throw new ArgumentNullException(nameof(docs));
      if(action == null)
         throw new ArgumentNullException(nameof(action));
      Document doc = docs.MdiActiveDocument;
      if(doc == null)
         throw new AcRx.Exception(AcRx.ErrorStatus.NoDocument);
      if(!docs.IsApplicationContext)
         throw new AcRx.Exception(AcRx.ErrorStatus.InvalidContext);
      Task task = Task.CompletedTask;
      await docs.ExecuteInCommandContextAsync(
         delegate (object unused)
         {
            try
            {
               action(doc);
               return task;
            }
            catch(System.Exception ex)
            {
               return task = Task.FromException(ex);
            }
         },
         null
      );
      if(task.IsFaulted)
      {
         throw task.Exception;
      }
   }
}

 

 

Using the above extension provides a safety net that prevents AutoCAD from crashing if an exception occurs in the code you pass into the method. If you use ExecuteInCommandContextAsync() as your above code does, and the delegate throws an exception, your catch{} handler will not catch it, and AutoCAD will crash. With the InvokeAsCommandAsync() method, your catch{} handler will catch any exception thrown, including one thrown by the delegate you pass it.

 

Here is how you would adapt your code to use it:

 

   startPoint = new Point3d(X, Y, 0);
   try    
   { 
     await docs.InvokeAsCommandAsync(doc =>
     {
         ed.Command("_-PDFATTACH", PdfPath, Page.ToString(), Point3d.Origin, 1.0, 0.0);
         ObjectId? result = PdfHandle.LoadUnloadPDFunderlay(PdfPath, Page.ToString(), "Unload");
     });
   } 
   catch (System.Exception ex)
   {
     docs.MdiActiveDocument.Editor.WriteMessage($"Operation failed: {ex.Message}");
   }
   

 

 

 

 

 

0 Likes