Custom Menu and Ribbon with code.

phuochung_dang
Contributor
Contributor

Custom Menu and Ribbon with code.

phuochung_dang
Contributor
Contributor

Hello everyone,

 

Currently, I'm trying to create Ribbon and Menu for AutoCAD with code and have 2 problems below:

 

- For the Ribbon, I want AutoCAD to automatically load the custom Ribbon when opening AutoCAD if the working environment shows the Ribbon in AutoCAD, if I switch to the Classic interface, the custom Ribbon will not load (this works properly), but when I switch back to the Ribbon style, it doesn't show the custom Ribbon again. So is there a way to customize it to always load in AutoCAD without shutting down and reopening AutoCAD?

 

- For Menu, I have to load Menu by calling "TestMenu" command, I want AutoCAD to automatically load custom menu like in Ribbon but it can't load. So I want to auto load Menu for this case, what should I do?

 

====================================Code for Ribbon:======================================
 
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using Autodesk.Windows;
using Ribbon.Properties;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace Ribbon
{
    public class Command : IExtensionApplication
    {
        void IExtensionApplication.Initialize()
        {
            TAddRibbon();
        }
 
        public void Terminate()
        {
            throw new NotImplementedException();
        }
 
        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool DeleteObject([In] IntPtr hObject);
 
        public static ImageSource ImageSourceForBitmap(Bitmap bmp)
        {
            var handle = bmp.GetHbitmap();
            try
            {
                return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            }
            finally { DeleteObject(handle); }
        }
 
        private const string myTabId = "My Tool";
 
        [CommandMethod("AddRibbon")]
        public void TAddRibbon()
        {
            RibbonControl ribbonControl = Autodesk.AutoCAD.Ribbon.RibbonServices.RibbonPaletteSet.RibbonControl;
            //RibbonControl ribbonControl = Autodesk.Windows.ComponentManager.Ribbon;
 
            RibbonTab ribbonTab = new RibbonTab();
            ribbonTab.Title = "Custom Ribbon";
            ribbonTab.Id = myTabId;
            ribbonControl.Tabs.Add(ribbonTab);
 
            addPanelGetEntity(ribbonTab);
            addPanelAddEntity(ribbonTab);
 
            ribbonTab.IsActive = true;
        }
 
        private void addPanelGetEntity(RibbonTab ribbonTab)
        {
            RibbonPanelSource ribbonPanelSource = new RibbonPanelSource();
            ribbonPanelSource.Title = "Select Entity";
 
            RibbonPanel ribbonPanel = new RibbonPanel();
            ribbonPanel.Source = ribbonPanelSource;
            ribbonTab.Panels.Add(ribbonPanel);
 
            RibbonButton ribbonButtonGetEntity = new RibbonButton();
            ribbonButtonGetEntity.Text = "My Get Entity";
            ribbonButtonGetEntity.ShowText = true;
            ribbonButtonGetEntity.Image = ImageSourceForBitmap(Resource.smiley_16x16_png);
            ribbonButtonGetEntity.LargeImage = ImageSourceForBitmap(Resource.smiley_32x32_png);
            ribbonButtonGetEntity.ShowImage = true;
            ribbonButtonGetEntity.Size = RibbonItemSize.Large;
            ribbonButtonGetEntity.Orientation = System.Windows.Controls.Orientation.Horizontal;
            ribbonButtonGetEntity.CommandParameter = "GetEntity ";
            ribbonButtonGetEntity.CommandHandler = new AdskCommandHandler();
            ribbonPanelSource.Items.Add(ribbonButtonGetEntity);
        }
 
        private void addPanelAddEntity(RibbonTab ribbonTab)
        {
            RibbonPanelSource ribbonPanelSource = new RibbonPanelSource();
            ribbonPanelSource.Title = "Add Entity";
 
            RibbonPanel ribbonPanel = new RibbonPanel();
            ribbonPanel.Source = ribbonPanelSource;
            ribbonTab.Panels.Add(ribbonPanel);
 
            RibbonButton ribbonButtonAddLine = new RibbonButton();
            ribbonButtonAddLine.Text = "My Add Line";
            ribbonButtonAddLine.ShowText = true;
            ribbonButtonAddLine.CommandParameter = "AddLine ";
            ribbonButtonAddLine.CommandHandler = new AdskCommandHandler();
 
            ribbonPanelSource.Items.Add(ribbonButtonAddLine);
        }
 
        [CommandMethod("AddLine")]
        public void HDDL()
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
 
            Line Lin = new Line();
 
            Lin.StartPoint = new Point3d(10, 20, 0);
            Lin.EndPoint = new Point3d(20, 20, 0);
 
            Transaction acTrans = db.TransactionManager.StartTransaction();
            using (acTrans)
            {
                BlockTable bt = acTrans.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
               
                BlockTableRecord btr = acTrans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
                
                btr.AppendEntity(Lin);
                acTrans.AddNewlyCreatedDBObject(Lin, true);
                
                acTrans.Commit();
            }
        }
 
        [CommandMethod("GetEntity")]
        public static void TGetEntity()
        {
            Document AcDoc = AcAp.DocumentManager.MdiActiveDocument;
 
            Editor AcEd = AcDoc.Editor;
 
            PromptEntityOptions pEntOpts = new PromptEntityOptions("\nSelect entity: ");
 
            pEntOpts.AllowNone = true;
 
            PromptEntityResult pEntRes = AcEd.GetEntity(pEntOpts);
 
            if (pEntRes.Status == PromptStatus.OK)
            {
                AcEd.WriteMessage("\nEnity selected is: " + pEntRes.ObjectId.ObjectClass.DxfName + ".");
            }
 
            else
            {
                AcEd.WriteMessage("\nThere is no entity selected.");
            }
        }
    }
}
 
====================================Code for Menu:======================================
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Runtime;
using Autodesk.Windows;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace Menu
{
    public class Command
    {
        static AcadPopupMenu testMenu = null;
 
        [CommandMethod("TestMenu")]
        public void TestMenu()
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            AcadApplication acadApp = AcAp.AcadApplication as AcadApplication;
 
            if (testMenu == null)
            {
                testMenu = acadApp.MenuGroups.Item(0).Menus.Add("Test Menu");
 
                testMenu.AddMenuItem(testMenu.Count, "Menu1", "line ");
                testMenu.AddMenuItem(testMenu.Count, "Menu2", "polyline ");
                AcadPopupMenu subMenu = testMenu.AddSubMenu(testMenu.Count, "Menu3");
                subMenu.AddMenuItem(testMenu.Count, "Cricle", "circle ");
                testMenu.AddSeparator(testMenu.Count);
                testMenu.AddMenuItem(testMenu.Count, "Menu4", "rectangle ");
            }
 
            bool isShowd = false;
            foreach (AcadPopupMenu menu in acadApp.MenuBar)
            {
                if (menu == testMenu)
                {
                    isShowd = true;
                    break;
                }
            }
 
            if (!isShowd)
            {
                testMenu.InsertInMenuBar(acadApp.MenuBar.Count);
            }
        }
    }
}

 

Thank you.

 

0 Likes
Reply
Accepted solutions (4)
5,340 Views
13 Replies
Replies (13)

norman.yuan
Mentor
Mentor

Firstly, your code that runs in IExtensionApplication.Initialize() has crucial error that could render your add-in dll useless (i.e. after the DLL loaded, your custom command would result in "Unknown command" error). The reason is that you do not have try...catch to wrap the code on Initialize for possible error, and in your code, an error is highly likely: by the time your DLL runs the Initialize() method, there no GUARANTEE that AutoCAD has already had RibbonControl object created, thus the first line of the code in TAddRibbon() method would fail, thus the Initialize() would error out and the DLL load fails.

 

The usual approach of loading custom ribbon created by code is to handle Application.Idle event in Initialize(), and in the Idle event handler to check is RibbonControl is null or not. Only proceed to load custom ribbon items after RibbonControl is reachable.

 

Now back to your question. When you say "switch to classical..." and "switch back..." I assume it means user switch work space. You should handle event to watch workspace change (for example, handling SystemVaraibaleChanged event for system variable "WSCURRENT"): when current workspace changes, you simply check if there is RibbonControl reachable, if yes, if your ribbon items are loaded; if not load them. Of course you need to modify/update your code so that before you create your own ribbon tab/panel, you want to check if they have already exists (or there could be even ribbon tab/panel created by others that has already used the same tab/panel name. In this case you may want to name your tab/panel differently...).

 

As for the menu, it would be the same as ribbon loading: you can loaded in Application.Idle event handler. Just to remember, after your custom ribbon/menu loaded in the Idle event handler, you remove the event handler.

 

Norman Yuan

Drive CAD With Code

EESignature

phuochung_dang
Contributor
Contributor

Hello @norman.yuan,

 

Can you help me fix the above code to work?

Thank you for your reply.

0 Likes

norman.yuan
Mentor
Mentor
Accepted solution

Some pseudo code:

 

public void Initialize()

{

  Application.Idle += LoadRibbonMenuOnIdle;

}

... ...

private void LoadRibbonMenuOnIdle(object sender, EventArgs e)

{

   var ribbonControl = Autodesk.Windows.ComponentManager.Ribbon;

   if (ribbonControl!=null)

   {

     // Load custom ribbon / menu

     Application.SystemVaraibaleChanged += (o, e) =>

    {

       if (e.Name.ToUpper()=="WSCURRENT")

       {

           // Load Ribbon/Menu

       }

    }

     Application.Idle -= LoadRibbonMenuOnIdle;

   }

}

 

As I said in previous reply, since you might need to load your ribbon multiple times due to user changing Workspace, so your ribbon loading code should check if the custom tab/panel has already existed in the ribbon control or not.

 

 

 

Norman Yuan

Drive CAD With Code

EESignature

phuochung_dang
Contributor
Contributor

Hello @norman.yuan ,

 

Thank you for your response, but I'm getting the errot at event variable e in this code:

 

private void LoadRibbonMenuOnIdle(object sender, EventArgs e)

{

   var ribbonControl = Autodesk.Windows.ComponentManager.Ribbon;

   if (ribbonControl!=null)

   {

     // Load custom ribbon / menu

     Application.SystemVaraibaleChanged += (o, e) =>

    {

       if (e.Name.ToUpper()=="WSCURRENT")

       {

           // Load Ribbon/Menu

       }

    };

     Application.Idle -= LoadRibbonMenuOnIdle;

   }

}

 

Thank you.

0 Likes

norman.yuan
Mentor
Mentor
Accepted solution

Ah, it is a typo: the variable e has already been used in the outer code, you can change it to whatever different, such as 

 

   Application.SystemVariableChanged += (o, arg) =>

  {

    if (arg.Name.ToUpper() == "WSCURRENT")

    {

      ... ...

    }

  }

...

 

Norman Yuan

Drive CAD With Code

EESignature

phuochung_dang
Contributor
Contributor

Hello @norman.yuan,

 

Thank you very much, the line of code works great. 

 

Also, can you tell me more, is there a way to add an icon for the Menu with code? Currently my code only shows the content without the icon.

====================================Code for Menu:======================================
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Runtime;
using Autodesk.Windows;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace Menu
{
    public class Command
    {
        static AcadPopupMenu testMenu = null;
 
        [CommandMethod("TestMenu")]
        public void TestMenu()
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            AcadApplication acadApp = AcAp.AcadApplication as AcadApplication;
 
            if (testMenu == null)
            {
                testMenu = acadApp.MenuGroups.Item(0).Menus.Add("Test Menu");
 
                testMenu.AddMenuItem(testMenu.Count, "Menu1", "line ");
                testMenu.AddMenuItem(testMenu.Count, "Menu2", "polyline ");
                AcadPopupMenu subMenu = testMenu.AddSubMenu(testMenu.Count, "Menu3");
                subMenu.AddMenuItem(testMenu.Count, "Cricle", "circle ");
                testMenu.AddSeparator(testMenu.Count);
                testMenu.AddMenuItem(testMenu.Count, "Menu4", "rectangle ");
            }
 
            bool isShowd = false;
            foreach (AcadPopupMenu menu in acadApp.MenuBar)
            {
                if (menu == testMenu)
                {
                    isShowd = true;
                    break;
                }
            }
 
            if (!isShowd)
            {
                testMenu.InsertInMenuBar(acadApp.MenuBar.Count);
            }
        }
    }
}
0 Likes

norman.yuan
Mentor
Mentor
Accepted solution

Sorry, I am not aware that one can add icon to PopupMenu with AutoCAD COM API. 

 

Norman Yuan

Drive CAD With Code

EESignature

Alexander.Rivilis
Mentor
Mentor
Accepted solution

@phuochung_dang wrote:

Also, can you tell me more, is there a way to add an icon for the Menu with code? Currently my code only shows the content without the icon.

 

There are no methods in AutoCAD COM API, which add icon(s) to menu.

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"


Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
Expert Elite Member

phuochung_dang
Contributor
Contributor

Hello @norman.yuan @Alexander.Rivilis 

 

Oh, so that's it, because there's no solution to this. So maybe for Menu, I should go back to creating Menu with CUI command and add custom command for it.

 

Thank you very much.

0 Likes

Anonymous
Not applicable

 

 

            foreach (AcadMenuGroup acadAppMenuGroup in acadApp.MenuGroups)
            {
                doc.Editor.WriteMessage("++++++++++++++\n");
                doc.Editor.WriteMessage(acadAppMenuGroup.Name);
                doc.Editor.WriteMessage("\n");

                foreach (AcadPopupMenu item in acadAppMenuGroup.Menus)
                {
                    doc.Editor.WriteMessage("    " + item.Name + "\n");
                }
            }

 

 

@Alexander.Rivilis 

Why add menu by Item(0) ? I don't understand this api and how to create a tab.

-----------

acadApp.MenuGroups.Item(0).Menus.Add("Test Menu");

 

0 Likes

lchunrui
Observer
Observer

hello,I just have the same question as @phuochung_dang , I have handled Application.Idle event in Initialize() but it seems not work.Here is part of my code ,would you please tell me why the ribbon plugin does not show automatically? thank u.

        void IExtensionApplication.Initialize()
        {
            Application.Idle += LoadRibbonOnIdle;
        }
        private void LoadRibbonOnIdle(object sender, EventArgs e)

        {
            var ribbonControl = Autodesk.Windows.ComponentManager.Ribbon;

            if (ribbonControl != null)

            {
                // Load custom ribbon
                Application.SystemVariableChanged += (o, ev) =>

                {
                    if (ev.Name.ToUpper() == "LWDISPLAY")

                    {
                      // Load Ribbon/Menu
                        RibbonDemo();
                    }

                };        
                Application.Idle -= LoadRibbonOnIdle;
            }

        }
        [CommandMethod("RibbonDemo")]
        public void RibbonDemo()
        {
            // 获取程序集的加载路径
            string currentDLLLocation = Path.GetDirectoryName(this.GetType().Assembly.Location) + "\\";
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Editor editor = doc.Editor;
            // 获取cad的Ribbon界面
            RibbonControl ribbonCtrl = ComponentManager.Ribbon;
            //while(ribbonCtrl==null) ribbonCtrl = ComponentManager.Ribbon;
            if (ribbonCtrl == null) {
               
                editor.WriteMessage("No Ribbon!");
                return;
            }
            
            RibbonTab tab = new RibbonTab();
            tab.Title = "myPlugin";
            tab.Id = "Acad.RibbonId1";
            //tab.IsActive = true;
            
            ribbonCtrl.Tabs.Add(tab);
            //AddPanel(tab);
            
            RibbonPanelSource panelSource = new RibbonPanelSource();
            panelSource.Title = "快捷操作";
            RibbonPanel ribbonPanel = new RibbonPanel();
            ribbonPanel.ResizeItemWidthPriority = 1;
            ribbonPanel.Source = panelSource;
            tab.Panels.Add(ribbonPanel);
           
            addButton(currentDLLLocation, panelSource, "folderOpen", "打开文件", "打开文件", "打开坐标文件", "是打开文件命令,请保证目标文件是格式正确的.csv文件。", "OPENFILE");
           
            addButton(currentDLLLocation, panelSource, "color", "修改颜色", "修改颜色", "修改线条颜色", "这是修改使用插件绘制的所有线条颜色的命令。", "CHANGECOLOR");
            
            addButton(currentDLLLocation, panelSource, "clear", "一键清除", "一键清除", "一键清除所有线条", "这是清除使用插件绘制的所有线条色的命令。", "CLEARALL");
            
            RibbonRowPanel row1 = new RibbonRowPanel();
            row1.Width = 500;
            
            // RibbonCombo
            ribbonCombo = new RibbonCombo {
                Name = "修改线宽",
                ShowText = true,
                Image = LoadImage("lineWidth"),
                LargeImage = LoadImage("lineWidthLarge"),
                Size = RibbonItemSize.Large,
                ShowImage = true,
                Width = 200                
            };
            InitializeComboBox(ribbonCombo);
            ribbonCombo.CurrentChanged += lineWidthCombo_SelectedIndexChanged;
            row1.Items.Add(ribbonCombo);
            row1.Items.Add(new RibbonRowBreak());

            
            ribbonLineTypeCombo = new RibbonCombo {
                Name = "修改线型",
                ShowText = true,
                Size = RibbonItemSize.Large,
                Image = LoadImage("lineType"),
                LargeImage = LoadImage("lineTypeLarge"),
                ShowImage = true,
                Width = 200,
                CommandHandler = new RibbonCommandHandler()
            };

            List<string> stringtext = new List<string> { "实线", "虚线", "点划线" };
            for (int i = 0; i < stringtext.Count; i++)
            {
                RibbonButton ribtext = new RibbonButton();
                ribtext.Text = stringtext[i];
                ribtext.ShowText = true;

                ribbonLineTypeCombo.Items.Add(ribtext);
            }
            ribbonLineTypeCombo.CurrentChanged +=ribbonLineTypeCombo_selectedChanged;
            ribbonLineTypeCombo.Current = ribbonLineTypeCombo.Items[0];
            row1.Items.Add(ribbonLineTypeCombo);
            row1.Items.Add(new RibbonRowBreak());
            panelSource.Items.Add(row1);        
        }

  

0 Likes

norman.yuan
Mentor
Mentor

YOur Application.Idle event handler LoadRibbonOnIdle(object, EventArgs) does not load the custom ribbon. It only add event handler to SystemVariableChanged event to load the custom ribbon ONLY WHEN "LWDISPLAY"(???) is changed. Why when Showing LineWeight is toggled on or off you want to load/reload the custom Ribbon? The system variable to watch SHOULD BE "WSCURRENT". That is, when WorkSpace changes, you want to make sure your dynamically created custom ribbon tab is still there. SO the code should be:

        private void LoadRibbonOnIdle(object sender, EventArgs e)
        {
            var ribbonControl = Autodesk.Windows.ComponentManager.Ribbon;

            if (ribbonControl != null)
            {
                // Load custom ribbon tab
                RibbonDemo();

                // make sure custom ribbon is loaded when Workspace changes
                Application.SystemVariableChanged += (o, ev) =>
                {
                    if (ev.Name.ToUpper() == "WSCURRENT")
                    {
                      // Load Ribbon/Menu
                        RibbonDemo();
                    }
                };        
                Application.Idle -= LoadRibbonOnIdle;
            }
        }

 

Also, in the RibbonDemo() method, you SHOULD TEST whether the custom ribbon tab has already existed or not, and ONLY create the custom ribbon WHET IT DOES NOT EXISTS.

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes

lchunrui
Observer
Observer

Thanks for your reply. I modified my code as you said and it did work.However, there is a new problem: when I select a combo box item, i.e. when the event lineWidthCombo_SelectedIndexChanged is triggered, all my global variables are initialized, and all the new objects that I created in the beginning become null, e.g. the variables clear,loaded, the object modalForm etc.but when I cancel the inheritation to IExtensionApplication and the code that autoloads the custom ribbon, it works normally,can you tell me how to fix that? Appreciate it.

The code I used to add the event for ribbonCombo is:

ribbonCombo.CurrentChanged += lineWidthCombo_SelectedIndexChanged;

The code for lineWidthCombo_SelectedIndexChanged is shown below:

        private void lineWidthCombo_SelectedIndexChanged(object sender, RibbonPropertyChangedEventArgs args)
        {
            //System.Windows.Forms.MessageBox.Show($"{this.loaded}");
            int weight;
            RibbonButton current = (RibbonButton)ribbonCombo.Current;
            string selectedItemName = current.AutomationName;
            if (selectedItemName == "default") weight = -3;
            else if (selectedItemName == "ByBlock") weight = -2;
            else if (selectedItemName == "ByLayer") weight = -1;
            else
            {
                weight = (int)(double.Parse(selectedItemName.Substring(0, 4)) * 100);
            }
            lineWeight = weight;    
            if (loaded == true && clear == false)
            modalForm.DrawCurves();
}

 

0 Likes