We need to extend AutoCAD context menu many times to give users a more friendly work environment. AutoCAD .NET makes it very possible through the interface ContextMenuExtension. It is pretty straight to use but has a couple of small temperaments that we have to dodge when playing with it.
Here is a sample implementation about the ContextMenuExtension interface:
#region Namespaces
using System;
using System.Text;
using System.Linq;
using System.Xml;
using System.Reflection;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Windows;
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
using AcadDocument = Autodesk.AutoCAD.ApplicationServices.Document;
using AcadWindows = Autodesk.AutoCAD.Windows;
#endregion
namespace AcadNetAddinCS
{
public class ContextMenuExtensioner4 : ContextMenuExtension
{
public ContextMenuExtensioner4()
{
this.Title = "ContextMenuExtensioner4 Context Menu";
Assembly assem = Assembly.GetExecutingAssembly();
AcadWindows.MenuItem mnItem01 = new AcadWindows.MenuItem("item1");
mnItem01.Checked = false;
mnItem01.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
this.MenuItems.Add(mnItem01);
AcadWindows.MenuItem mnItem01_01 = new AcadWindows.MenuItem("item1_1");
mnItem01_01.Icon = new Icon(assem.GetManifestResourceStream("AcadNetAddinCS.Resources.addin.ico"));
mnItem01_01.Checked = false;
mnItem01_01.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
mnItem01.MenuItems.Add(mnItem01_01);
AcadWindows.MenuItem mnItem01_02 = new AcadWindows.MenuItem("item1_2");
mnItem01_02.Icon = new Icon(assem.GetManifestResourceStream("AcadNetAddinCS.Resources.c.ico"));
mnItem01_02.Checked = true;
mnItem01_02.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
mnItem01.MenuItems.Add(mnItem01_02);
AcadWindows.MenuItem mnItem01_02_01 = new AcadWindows.MenuItem("item1_2_1");
mnItem01_02_01.Checked = false;
mnItem01_02_01.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
mnItem01_02.MenuItems.Add(mnItem01_02_01);
AcadWindows.MenuItem mnItem01_02_01_01 = new AcadWindows.MenuItem("item1_2_1_1");
mnItem01_02_01_01.Icon = new Icon(assem.GetManifestResourceStream("AcadNetAddinCS.Resources.a.ico"));
mnItem01_02_01_01.Checked = true;
mnItem01_02_01_01.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
mnItem01_02_01.MenuItems.Add(mnItem01_02_01_01);
AcadWindows.MenuItem mnItem01_03 = new AcadWindows.MenuItem("item1_3");
mnItem01_03.Icon = new Icon(assem.GetManifestResourceStream("AcadNetAddinCS.Resources.d.ico"));
mnItem01_03.Checked = false;
mnItem01_03.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
mnItem01.MenuItems.Add(mnItem01_03);
AcadWindows.MenuItem mnItem01_03_01 = new AcadWindows.MenuItem("item1_3_1");
mnItem01_03_01.Checked = true;
mnItem01_03_01.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
mnItem01_03.MenuItems.Add(mnItem01_03_01);
AcadWindows.MenuItem mnItem02 = new AcadWindows.MenuItem("item_2");
mnItem02.Icon = new Icon(assem.GetManifestResourceStream("AcadNetAddinCS.Resources.b.ico"));
mnItem02.Checked = false;
mnItem02.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
this.MenuItems.Add(mnItem02);
AcadWindows.MenuItem mnItem03= new AcadWindows.MenuItem("item_3");
mnItem03.Checked = false;
mnItem03.Click += new EventHandler(ContextMenuExtensioner4.MenuItem_OnClick);
this.MenuItems.Add(mnItem03);
}
private static void MenuItem_OnClick(object sender, EventArgs e)
{
AcadWindows.MenuItem mnItem = sender as AcadWindows.MenuItem;
if (mnItem != null)
{
MessageBox.Show(string.Format("'{0}' was just clicked.", mnItem.Text));
System.Diagnostics.Debug.WriteLine(string.Format("'{0}' was just clicked.", mnItem.Text));
if (mnItem.Text == "item1")
{
}
if (mnItem.Text == "item1_1")
{
}
if (mnItem.Text == "item1_2")
{
}
if (mnItem.Text == "item1_2_1")
{
}
if (mnItem.Text == "item1_2_1_1")
{
}
if (mnItem.Text == "item1_3")
{
}
if (mnItem.Text == "item1_3_1")
{
}
if (mnItem.Text == "item_2")
{
}
if (mnItem.Text == "item_3")
{
}
}
}
}
}
The code is too obvious to explain, I believe.
Here is a test local command to exercise the ContextMenuExtension implementation:
[CommandMethod("LocalCommands1", "Cmd1", "LocalCommands1_Cmd1_ID", CommandFlags.Modal, typeof(AcadNetAddinCS.ContextMenuExtensioner4), "LocalCommands1.chm", "LocalCommands1_Cmd1_Index")]
public static void Cmd1_Method()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
try
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
//TODO: add your code below.
Debug.WriteLine("Cmd1 ran.");
ed.WriteMessage("Cmd1 ran.\n");
ed.GetAngle("right click to see context menu.");
tr.Commit();
}
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.ToString());
ed.WriteMessage(ex.ToString());
}
}
Here is what the custom context menu items look like during the command running:
Before we address its small temperaments, let us look at its appearance first which might not be expected by some readers, if not all.
• The context menu item texts are not aligned to the same left edge anymore with icons added. Sometimes to the further left as demonstrated by the item1, item2 and item3; sometimes to the right a bit as demonstrated by the item1_1, item1_2, and item1_3.
• The item group does not display the icon we assigned to it as demonstrated by the item1_3.
• Item groups do not trigger the OnClick even as do items as demonstrated by the item1, Item1_2, item1_3, and item1_2_1. It is not clear though if behaving the same so will do any harm.
• The ‘Checked’ status looks different for a single sub item (squared) or an item group (really checked with a check mark).
• They may look slightly different in different versions. The above demonstration was done in AutoCAD 2011 by the way.
Ok, now it comes to the real concern of the article, the two small temperaments of the ContextMenuExtension.
First, if the context menu item does not have an icon, please do not bother to set null or Nothing to its Icon property. Otherwise, if doing something like so:
mnItem01.Icon = null;
the whole program will be aborted with some long and boring messages for us to read:
“System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation. --->
System.NullReferenceException: Object reference not set to an instance of an
object.
at System.Drawing.Icon.get_Size()
at Autodesk.AutoCAD.Windows.MenuItem.set_Icon(Icon value)
at AcadNetAddinCS.ContextMenuExtensioner4..ctor() in
C:\Temp\AcadNetAddinCS\ContextMenuExtensioner4.cs:line 41
--- End of inner exception stack trace ---
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean
publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandle& ctor,
Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean
fillCache)
at System.RuntimeType.CreateInstanceImpl(Boolean publicOnly, Boolean
skipVisibilityChecks, Boolean fillCache)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at Autodesk.AutoCAD.Runtime.Factory.Create(Type type)
at
Autodesk.AutoCAD.Windows.ContextMenuExtImpl.OnUpdateMenu(ContextMenuExtImpl* )
at Autodesk.AutoCAD.EditorInput.PromptAngleOptions.DoIt()
at Autodesk.AutoCAD.EditorInput.Editor.DoPrompt(PromptOptions opt)
at Autodesk.AutoCAD.EditorInput.Editor.GetAngle(PromptAngleOptions options)
at AcadNetAddinCS.LocalCommands1.Cmd1_Method() in
C:\Temp\AcadNetAddinCS\LocalCommands1.cs:line 49”
Second, AutoCAD 2011 does not accept icons with size modes other than 16 by 16 pixels only. Otherwise, the program will be interrupted again and another long and boring message is waiting for us:
“System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentException: Icons must be 16x16 pixel in size.
at Autodesk.AutoCAD.Windows.MenuItem.set_Icon(Icon value)
at AcadNetAddinCS.ContextMenuExtensioner4..ctor() in C:\Temp\AcadNetAddinCS\ContextMenuExtensioner4.cs:line 40
--- End of inner exception stack trace ---
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandle& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean fillCache)
at System.RuntimeType.CreateInstanceImpl(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean fillCache)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at Autodesk.AutoCAD.Runtime.Factory.Create(Type type)
at Autodesk.AutoCAD.Runtime.PerDocumentCommandClass.Invoke(MethodInfo mi, Boolean bLispFunction)
at Autodesk.AutoCAD.Runtime.CommandClass.CommandThunk.Invoke()”
The limitation is still there in AutoCAD 2012.
We talked about the ContextMenuExtension in various aspects, a sample implementation, its appearance either as expected or not, a couple of small bad temperaments that we have to elude from, some exception messages that we have to read sometimes, and some slight differences in both code and behaviour between AutoCAD 2011 and 2012 as far as ContextMenuExtension is concerned. So, quite some points are covered in this short and simple article.
Last but not least, we do not really have to worry about all these details either good or bad if the ContextMenu Extensioner of the leading edge AutoCAD .NET Addin Wizard (AcadNetAddinWizard) is used to create the ContextMenuExtension implementations automatically for us.
Posted by: |