Switching Contexts

Switching Contexts

BrentBurgess1980
Collaborator Collaborator
4,191 Views
14 Replies
Message 1 of 15

Switching Contexts

BrentBurgess1980
Collaborator
Collaborator

I am trying to run some code/commands from my a palette, however, I need to manually switch from Application Context to Document Context. Does anyone know of a way this can be forced? I have seen it in a finished product but haven't seen code behind it.

 

I am thinking using a delegate could possibly work, but not sure how I would implement it.

 

Any advice would be great.

 

Cheers,

 

Brent

 

 

0 Likes
Accepted solutions (2)
4,192 Views
14 Replies
Replies (14)
Message 2 of 15

_gile
Consultant
Consultant

Hi,

 

From Kean Walmsley's blog:

"Once again there’s our important rule of thumb when it comes to implementing a modeless UI: rather than manually locking the current document, it’s safer to define a command – which will implicitly lock the current document – and call that from the UI via SendStringToExecute()."

 

Another benefit of this way is the ability to directly call the command or hit enter to re-call it.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 3 of 15

_gile
Consultant
Consultant

If you do not want to use SendStringToExecute, you have to set the focus to AutoCAD window and texplicitly lock the document:

 

        private void btnCmd_Click(object sender, EventArgs e)
        {
            var doc = AcAp.DocumentManager.MdiActiveDocument;
            if (doc != null)
            {
                // set the focus to d'AutoCAD window 
                // before AutoCAD 2015, use: 
                // Autodesk.AutoCAD.Internal.Utils.SetFocusToDwgView();
                AcAp.MainWindow.Focus();
                
                // Lock the document
                using (doc.LockDocument())
                {
                    // do your stuff here
doc.Editor.UpdateScreen(); } } }


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 4 of 15

ActivistInvestor
Mentor
Mentor
Accepted solution

@BrentBurgess1980 wrote:

I am trying to run some code/commands from my a palette, however, I need to manually switch from Application Context to Document Context. Does anyone know of a way this can be forced? I have seen it in a finished product but haven't seen code behind it.

 

I am thinking using a delegate could possibly work, but not sure how I would implement it.

 

Any advice would be great.

 

Cheers,

 

Brent

 

 


 

Here is some code that will simplify what you want to do, along with an example:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Windows;

namespace Autodesk.AutoCAD.ApplicationServices
{
   public static class DocumentContextHelper
   {
      static Action action = null;

      const CommandFlags flags = CommandFlags.NoHistory
         | CommandFlags.NoMultiple
         | CommandFlags.NoActionRecording;

      /// <summary>
      /// Initiates execution of code in the document context
      /// from the application context. The code to be executed 
      /// in the document context is passed in the delegate. 
      ///
      /// If this API is called from the document context, the 
      /// delegate will execute synchronously. If called from 
      /// the application context, the delegate will execute 
      /// asynchronously when control returns to AutoCAD.
      /// </summary>
      /// <param name="docs">The Document, which must be the
      /// active document in the editor</param>
      /// <param name="callback">A delegate that takes no arguments</param>
      
      public static void Invoke(this Document doc, Action callback)
      {
         if(doc == null)
            throw new ArgumentNullException("doc");
         if(callback == null)
            throw new ArgumentNullException("action");
         if(action != null)
            throw new InvalidOperationException("reentry not supported");
         var docs = Application.DocumentManager;
         if(doc != docs.MdiActiveDocument)
            throw new InvalidOperationException("Document is not active");
         if(docs.IsApplicationContext)
         {
            action = callback;
            doc.SendStringToExecute("ANONYMOUS_DOCUMENT_COMMAND\n", true, false, false);
         }
         else
         {
            callback();
         }
      }

      /// <summary>
      /// An extension method for the DocumentCollection that executes 
      /// the given command input in the document context. The command
      /// input is executed in the active document. If there is no active
      /// document, an exception is raised. 
      ///
      /// Do NOT call this from the delegate passed to Invoke(). This API 
      /// is intended as an alternative to the Invoke() method, that you 
      /// can use if you simply need to execute a series of commands from 
      /// the application context and don't need to do anything else after 
      /// the commands have been executed.
      /// </summary>
      /// <param name="docs">The DocumentCollection</param>
      /// <param name="args">The command arguments</param>
      
      public static void Command(this DocumentCollection docs, params object[] args)
      {
         Document doc = Application.DocumentManager.MdiActiveDocument;
         if(doc == null)
            throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoDocument);
         Invoke(doc, () => doc.Editor.Command(args));
      }

      /// <summary>
      /// Controls the value of the CMDECHO system variable 
      /// while the delegate passed to Invoke() is running. 
      /// This can be set to true for debugging calls to 
      /// Editor.Command() (it is false by default).
      /// </summary>
      
      public static bool CmdEcho
      {
         get;
         set;
      }

      [CommandMethod("ANONYMOUS_DOCUMENT_COMMAND", flags)]
      public static void commandHandler()
      {
         if(action != null)
         {
            object cmdecho = Application.GetSystemVariable("CMDECHO");
            Application.SetSystemVariable("CMDECHO", CmdEcho ? 1 : 0);
            try
            {
               action();
            }
            finally
            {
               Application.SetSystemVariable("CMDECHO", cmdecho);
               action = null;
            }
         }
      }
   }
}

/// USAGE EXAMPLE

namespace InvokeExample
{
   using Autodesk.AutoCAD.ApplicationServices;
   using Application = Autodesk.AutoCAD.ApplicationServices.Application;
   using System.Windows.Forms;

   /// <summary>
   /// An example UserControl hosted by the PalletteSet below.
   /// 
   /// This example shows how to execute code in the document context,
   /// from the application context. The button on the UserControl
   /// calls the Invoke() method and passes a delegate that will 
   /// execute in the document context asynchronously. 
   /// 
   /// When the button is clicked, the delegate executes and
   /// calls the Editor's Command() method to draw a circle.
   /// </summary>
      
   public class MyUserControl : UserControl
   {
      Button button;

      public MyUserControl()
      {
         button = new Button();
         button.Text = "Draw a Circle";
         button.Left = 10;
         button.Top = 10;
         button.AutoSize = true;
         button.Click += new EventHandler(button_Click);
         this.Controls.Add(button);
      }

      void button_Click(object sender, EventArgs e)
      {
         Document doc = Application.DocumentManager.MdiActiveDocument;
         if(doc != null && doc.Editor.IsQuiescent)
         {
            this.Enabled = false;
            doc.Invoke(
               delegate()
               {
                  doc.Editor.Command("CIRCLE", "10,10", "5");
                  this.Enabled = true;
               }
            );
         }
      }
   }

   // A PaletteSet that hosts the above UserControl

   public class MyPaletteSet : PaletteSet
   {
      MyUserControl control;

      public MyPaletteSet()
         : base("MyPaletteSet")
      {
         control = new MyUserControl();
         Add("MyUserControl", control);
         this.DockEnabled = DockSides.None;
         this.Visible = true;
         this.Dock = DockSides.None;
      }

      protected override void Dispose(bool disposing)
      {
         if(control != null)
         {
            control.Dispose();
            control = null;
         }
         base.Dispose(disposing);
      }

      /// <summary>
      /// A command to create/show the example PaletteSet
      /// </summary>
      
      static MyPaletteSet instance;

      [CommandMethod("SHOWMYPALETTESET", CommandFlags.Session)]
      public static void ShowMyPaletteSet()
      {
         if(instance == null)
         {
            instance = new MyPaletteSet();
         }
         instance.Visible = true;
      }
   }
}
Message 5 of 15

BrentBurgess1980
Collaborator
Collaborator

I can see how it is meant to work, but when it gets to 

 

action = callback;
doc.SendStringToExecute("ANONYMOUS_DOCUMENT_COMMAND\n", true, false, false);

 

I get

 

Unknown command "ANONYMOUS_DOCUMENT_COMMAND".  Press F1 for help.

 

Any ideas on this?

0 Likes
Message 6 of 15

ActivistInvestor
Mentor
Mentor

@BrentBurgess1980 wrote:

I can see how it is meant to work, but when it gets to 

 

action = callback;
doc.SendStringToExecute("ANONYMOUS_DOCUMENT_COMMAND\n", true, false, false);

 

I get

 

Unknown command "ANONYMOUS_DOCUMENT_COMMAND".  Press F1 for help.

 

Any ideas on this?


Did you convert that code to VB or something?

 

The ANONYMOUS_DOCUMENT_COMMAND is just a regular command defined using the CommandMethod attribute.

 

You can type its name into the command prompt, and if it is defined, there should be no Unknown command message.

 

If there is, it means the command is not being recognized (possibly because there's an exception being thrown when the assembly is loaded).

 

 

 

 

0 Likes
Message 7 of 15

BrentBurgess1980
Collaborator
Collaborator

Not converted. Using C#. Not sure what I was doing, but it recognises it now.

 

 

I have noticed that the Invoke callback is being set to the button click, rather than what I assumed would be the doc.Editor.SendCommand(......)

 

Capture.PNG

 

This is throwing an exception

Capture1.PNG

 

Have I done something wrong with the implementation?

 

Thanks

 

Brent

0 Likes
Message 8 of 15

ActivistInvestor
Mentor
Mentor
Accepted solution

@BrentBurgess1980 wrote:

Not converted. Using C#. Not sure what I was doing, but it recognises it now.

 

 

I have noticed that the Invoke callback is being set to the button click, rather than what I assumed would be the doc.Editor.SendCommand(......)

 

 

 

This is throwing an exception

 

 

Have I done something wrong with the implementation?

 

Thanks

 

Brent


What release of AutoCAD are you using?

 

I very quickly ripped that code example from a much more complicated code base, and hastily forget to include this:

 


/// Important: If and only if the containing assembly uses the /// CommandClass attribute on classes that have command /// methods, the following line MUST not be commented out. // [assembly: CommandClass(typeof(Autodesk.AutoCAD.ApplicationServices.DocumentContextHelper))]

The above might explain why the ANONYMOUS_DOCUMENT_COMMAND isn't recognized, but I see you've fixed that.

Message 9 of 15

BrentBurgess1980
Collaborator
Collaborator

was using 2014, but got it to work on 2017. Thanks heaps for the help!!!

0 Likes
Message 10 of 15

Anonymous
Not applicable

Hi @ActivistInvestor

 

I'm facing one compiling issue with your example code in

        public static void Command(this DocumentCollection docs, params object[] args)
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            if (doc == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoDocument);
            Invoke(doc, () => doc.Editor.Command(args));
        }

I get "doc.Editor" red underlined with the  following description:
Error CS1929 'Editor' does not contain a definition for 'Command' and the best extension method overload 'DocumentContextHelper.Command(DocumentCollection, params object[])' requires a receiver of type 'DocumentCollection'

 

everything else is like your code, from "using System;" down to the last "}" preceding "/// USAGE EXAMPLE"

 

I'm running VS 2015 Community, targeting NET 4.0

 

Could you guess what's happening?

thanks

0 Likes
Message 11 of 15

_gile
Consultant
Consultant

Hi,

 

Editor.Command() method came with AutoCAD 2015. For prior version, you can use Tony "DiningPhilosopher" Tanzillo's wrapper for the non-public RunCommand() method (second reply of this thread).



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 12 of 15

Anonymous
Not applicable

thanks Gile

 

I'm on my nth start with NET, pushed by the threat of VBA being abandoned sometime in the (next?) future

maybe this is the good one!

 

 

back to the present issue, your piece of advice let me have a compiling code, along with the addition of 

using System.Reflection;
using System.Linq.Expressions;

and I'll go on and watch what happens...

 

meanwhile, since I was brought here by this threadI'd like to know your feedback on ActivistInvestor post #11 where he proposed his PaletteSet managing pattern as opposed to yours and Juergen_Becke one: do you agree with him? If not, why?

Finally do you think that the PaletteSet pattern is the best way to build some tool and make people use some AutoCAD "custom program"?

 

thank you

 

 

0 Likes
Message 13 of 15

_gile
Consultant
Consultant

ActivistInvestor is a very experimented programmer and, from my part I always pay attention to its advices.

In the topic you linked, despite the fact both Juergen_Becke's and mine do work, ActivistInvestor's implementation is a 'better practice'.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 14 of 15

ActivistInvestor
Mentor
Mentor

@Anonymous wrote:

thanks Gile

 

I'm on my nth start with NET, pushed by the threat of VBA being abandoned sometime in the (next?) future

maybe this is the good one!

 

 

back to the present issue, your piece of advice let me have a compiling code, along with the addition of 

using System.Reflection;
using System.Linq.Expressions;

and I'll go on and watch what happens...

 

meanwhile, since I was brought here by this threadI'd like to know your feedback on ActivistInvestor post #11 where he proposed his PaletteSet managing pattern as opposed to yours and Juergen_Becke one: do you agree with him? If not, why?

Finally do you think that the PaletteSet pattern is the best way to build some tool and make people use some AutoCAD "custom program"?

 

thank you

 

 


The patterns that you see repeated in 'basic examples' are often a result of someone else using the pattern in earlier examples. They tend to propagate,

 

In the case of the PaletteSet, I would give through-the-interface, and possibly others from Autodesk the credit for the very first examples, none of which used what I consider the 'best practice' of inheritance (all of the PaletteSet-based UI's that come in the box, like the ribbon, do use inheritance). But in their defense, many of those early published examples were written back when the PaletteSet class was sealed, which means that deriving classes from it was not possible. As I mentioned in the thread you referenced, around the time the Ribbon appeared, the PaletteSet class was unsealed, and also had some of its members made virtual/overridable, which was done largely in support of the ribbon (although IMO, the PaletteSet shouldn't have been sealed from the beginning).

 

A PaletteSet is not too different from a Windows Form in terms of its purpose and use. When you build a Windows Form, the IDE generates the derived class for you, and places its initialization code in a method called InitializeComponent(), which is called from the constructor of the derived class. That's really how a component should be implemented, so that it has no dependence on other code outside its class. Because the PaletteSet class is not sealed, it also has virtual, overridable members which can only be leveraged through inheritance, so the intent of the design was clearly that the consumer of the PaletteSet class use inheritance as the means of gaining full access to the functionality it provides.

 

You might also want to see this thread for a more-complete example that includes the code you where having problems with above.

 

 

Message 15 of 15

ActivistInvestor
Mentor
Mentor

Please have a look at >this post<

 


@Anonymous wrote:

Hi @ActivistInvestor

 

I'm facing one compiling issue with your example code in

        public static void Command(this DocumentCollection docs, params object[] args)
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            if (doc == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoDocument);
            Invoke(doc, () => doc.Editor.Command(args));
        }

I get "doc.Editor" red underlined with the  following description:
Error CS1929 'Editor' does not contain a definition for 'Command' and the best extension method overload 'DocumentContextHelper.Command(DocumentCollection, params object[])' requires a receiver of type 'DocumentCollection'

 

everything else is like your code, from "using System;" down to the last "}" preceding "/// USAGE EXAMPLE"

 

I'm running VS 2015 Community, targeting NET 4.0

 

Could you guess what's happening?

thanks