CAD二次开发--Ribbon相关封装


 菜单选项卡的封装

使用XML来创建选项卡使用案列写在前面,关于文件的说明如下

  1. 创建一个命令交互类 CommandBase
  2. 抽象菜单面板基类 IRibbonAbs
  3. 创建从代码初始化菜单类 RibbonInit
  4. 创建从配置文件初始化菜单类RibbonInitConfig
  5. 创建一个借口用于命令绑定 IRibbonUI
  6. 添加一个XML配置文件   RibbonSetting.xml
  7. 添加图片文件夹  Images  图片格式为PNG,可以为嵌入资源或者本地文件

示例项目结构

调用方式

using Autodesk.AutoCAD.Runtime;
using Lad.Ribbons;
using System;
using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;

namespace RibbonDemo
{
    /// 
    /// 分类管理,所有模块
    /// 
    public partial class AppManager : IRibbonUI, IExtensionApplication
    {
        [CommandMethod("Load_RibbonInit")]
        public void Initialize()
        {
            RibbonInitConfig ribbon = new(this, "RibbonDemo.RibbonSetting.xml");
            try
            {
                ribbon.Init();
            }
            catch (System.Exception e)
            {
                AcadApp.DocumentManager.MdiActiveDocument.Editor.WriteMessage(e.Message);
            }

        }

        public void Terminate()
        {
            throw new NotImplementedException();
        }
    }

    /// 
    /// 命令
    /// 
    public partial class AppManager
    {
        public Action Commond { get; set; } = new Action(e => Console.WriteLine("你好 Commond"));

        public static Action CommondSt { get; set; } = new Action(e => Console.WriteLine("你好 CommondSt"));

        public Action CommondLambda = e => Console.WriteLine("你好 CommondLambda");

        public static Action CommondlambdaSt = e => Console.WriteLine("你好 CommondlambdaSt");

        public void CommondMethod(object e)
        {
            Console.WriteLine("你好CommondMethod");
        }
        public static void CommondMethodSt(object e)
        {
            Console.WriteLine("你好CommondMethodSt");
        }
    }

}

注意:命令只能为一个参数,参数类型为object,返回值为void的方法或委托属性

显示结果如下,这里按钮图片随便找了几个,可以找一些好看的图片放进去

XML文件内容

<?xml version="1.0" encoding="utf-8" ?>

    
        
            
                
            
            
            
            
            
            
        
        
            
            
        
        
            
            
            
            
            
        
    

命令基类

    /// 
    /// 命令交互基础类
    /// 
    internal class CommandBase : System.Windows.Input.ICommand
    {
        public event EventHandler CanExecuteChanged;
        /// 
        /// 是否可执行,通用方法,传入委托
        /// 
        /// 
        /// 
        public bool CanExecute(object parameter)
        {
            //方法为True返回True,其它情况都返回False
            return DoCanExecute?.Invoke(parameter) == true;
        }
        /// 
        /// 执行
        /// 
        /// 
        public void Execute(object parameter)
        {
            //如果不为空,执行委托
            DoExecute?.Invoke(parameter);
        }

        public Action DoExecute { get; set; }
        public Func DoCanExecute { get; set; }
    }

菜单抽象类

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.Windows;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Lad.Ribbons
{
    /// 
    /// 菜单抽象类
    /// 
    public abstract class IRibbonAbs
    {
        internal RibbonTab? Tab;
        internal string Title;
        internal string Id;

        protected IRibbonAbs(string title, string id) { Title = title; Id = id; }
        internal IRibbonAbs() { Tab = null; Title = string.Empty; Id = string.Empty; }

        #region 面板
        /// 
        /// 将面板添加到RibbonTab
        /// 
        /// 面板
        /// 添加后的RibbonPanel,如果RibbonTab中存在ID相同的RibbonPanel,则返回查找到的RibbonPanel
        internal RibbonPanel AddPanel(RibbonPanel panel)
        {
            if (Tab == null)
                throw new ArgumentNullException(nameof(Tab));

            if (string.IsNullOrWhiteSpace(panel.Source.Id))
            {
                Tab.Panels.Add(panel);
                return panel;
            }
            var temp = Tab.FindPanel(panel.Source.Id);
            if (temp == null)
                Tab.Panels.Add(panel);
            else
                panel = temp;

            return panel;
        }
        /// 
        /// 根据名称查找面板功能组
        /// 
        /// 
        /// 
        internal RibbonPanel? FindPanelFrom(string name)
        {
            if (string.IsNullOrWhiteSpace(name)) return null;
            return Tab?.Panels.Where(p => p.Source.Title == name).FirstOrDefault();
        }
        #endregion

        #region 按钮

        /// 
        /// 添加提示信息
        /// 
        /// 按钮条目
        /// 标题,like"直线"
        /// 提示内容,like"创建直线段"
        /// 命令快捷键,like"Line"
        /// 扩展提示内容,like"使用LINE命令,可以创建一些连续的直线段,每条直线都是可以单独进行编辑的对象。"
        /// 扩展提示图片
        public void SetRibbonItemToolTip(RibbonItem ribbon, string title, object content, string command, object? expandedContent = default,
            System.Windows.Media.ImageSource? image = default)
        {
            if (ribbon == null) return;
            //添加提示对象
            RibbonToolTip toolTip = new RibbonToolTip()
            {
                Title = title,
                Content = content,
                Command = command,
                ExpandedContent = expandedContent
            };

            if (image != null)
                toolTip.ExpandedImage = image;

            ribbon.ToolTip = toolTip;
        }
        #endregion

        /// 
        /// 从菜单控制器初始化菜单
        /// 
        /// 
        /// 
        /// 
        internal void InitRibbon(Action action)
        {
            //程序空闲时触发的事件
            void LoadRibbonMenuOnIdle(object sender, EventArgs e)
            {
                var ribbonControl = Autodesk.Windows.ComponentManager.Ribbon;
                if (ribbonControl != null)
                {
                    Application.Idle -= LoadRibbonMenuOnIdle;
                    // Load Ribbon/Menu
                    action?.Invoke(ribbonControl);
                    if (Tab != null)
                        ribbonControl.ActiveTab = Tab;
                }
            }
            Application.Idle += LoadRibbonMenuOnIdle;

            //启动程序激活菜单的事件
            void ComponentManager_ItemInitialized(object sender, RibbonItemEventArgs e)
            {
                if (Autodesk.Windows.ComponentManager.Ribbon != null)
                    Autodesk.Windows.ComponentManager.ItemInitialized -= new EventHandler(ComponentManager_ItemInitialized);
            }
            if (Autodesk.Windows.ComponentManager.Ribbon == null)
                Autodesk.Windows.ComponentManager.ItemInitialized += ComponentManager_ItemInitialized;
        }
    }
}

创建一个空接口

namespace Lad.Ribbons
{
    /// 
    /// 从配置文件初始化菜单的调用对象需要继承当前接口
    /// 
    public interface IRibbonUI
    {
    }
}

创建初始化菜单的类

using Autodesk.Windows;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Media.Imaging;
using System.Xml;

namespace Lad.Ribbons
{
    /// 
    /// 从配置文件初始化菜单
    /// 
    public class RibbonInitConfig : IRibbonAbs
    {
        private RibbonControl? control;
        private IRibbonUI ribbon;
        private Assembly? asm;
        private string configPath;
        private Type? RibbonType;
        protected Action CommandBase = e => System.Windows.MessageBox.Show("未绑定命令");

        public RibbonInitConfig(IRibbonUI ribbonUI, string configPath) : base()
        {
            this.ribbon = ribbonUI;
            this.RibbonType = ribbonUI.GetType();
            this.asm = Assembly.GetAssembly(RibbonType);
            this.configPath = configPath;
        }
        #region 功能区回调
        const string RibbonRootNode = "Ribbon";
        const string RibbonTabNode = "RibbonTab";
        const string RibbonPanelNode = "RibbonPanel";
        const string RibbonItemNodeName = "RibbonButton";
        const string CommondAttribute = "Command";
        const string RibbonToolTipNode = "RibbonToolTip";

        public void Init()
        {
            this.InitRibbon(control =>
            {
                this.control = control;
                var xdoc = GetResourceXmlDoc(asm, configPath);
                foreach (var tab in InitRibbonTabs(xdoc))
                {
                    this.Tab = tab.Tab;
                    foreach (var panel in InitRibbonPanels(tab.Node))
                    {
                        var tempPanel = panel.Panel;
                        foreach (var btnItem in InitRibbonItems(panel.Node))
                        {
                            if (btnItem.Item == null) continue;
                            RibbonItem? tempBtn = null;
                            if ((tempBtn = tempPanel.Source.Items.Where(b => b.Text == btnItem.Item.Text).FirstOrDefault()) != null)
                                tempPanel.Source.Items.Remove(tempBtn);

                            btnItem.Item.ToolTip = InitRibbonToolTip(btnItem.Node);
                            tempPanel.Source.Items.Add(btnItem.Item);
                        }
                    }
                }
            });
        }
        #endregion
        #region Ribbon帮助
        /// 
        /// 初始化选项卡
        /// 
        /// 
        /// 
        IEnumerable<(RibbonTab Tab, XmlNode Node)> InitRibbonTabs(XmlDocument? xmlDoc)
        {
            return xmlDoc?.SelectNodes($"/{RibbonRootNode}/{RibbonTabNode}")?.ForEach().Select(xmlNode =>
            {
                RibbonTab? temp = (!string.IsNullOrWhiteSpace(xmlNode.Attributes["Id"]?.Value) ? control?.FindTab(xmlNode.Attributes["Id"]?.Value) : null);
                bool isNew = temp == null;
                RibbonTab ribbonTab = temp ?? new RibbonTab();
                foreach (XmlAttribute attribute in xmlNode.Attributes)
                    SetProperty(ribbonTab, attribute);

                if (isNew) control?.Tabs.Add(ribbonTab);

                return (ribbonTab, xmlNode);
            }) ?? Enumerable.Empty<(RibbonTab Tab, XmlNode Node)>();
        }
        /// 
        /// 初始化面板
        /// 
        /// 
        /// 
        IEnumerable<(RibbonPanel Panel, XmlNode Node)> InitRibbonPanels(XmlNode xmlNode)
        {
            return xmlNode?.SelectNodes(RibbonPanelNode)?.ForEach().Select(node =>
            {
                RibbonPanel? temp = (!string.IsNullOrWhiteSpace(node.Attributes["Id"]?.Value) ? this.Tab?.FindPanel(node.Attributes["Id"]?.Value) : null);
                bool isNew = temp == null;

                RibbonPanel panel = temp ?? new();
                RibbonPanelSource panelSource = panel.Source ?? new();
                foreach (XmlAttribute panelAttribute in node.Attributes)
                    SetProperty(panelSource, panelAttribute);
                if (isNew)
                {
                    panel.Source = panelSource;
                    this.Tab?.Panels.Add(panel);
                }
                return (panel, node);
            }) ?? Enumerable.Empty<(RibbonPanel? Panel, XmlNode Node)>();
        }
        /// 
        /// 初始化RibbonItem
        /// 
        /// 
        /// 
        IEnumerable<(RibbonItem? Item, XmlNode Node)> InitRibbonItems(XmlNode xmlNode)
        {
            if (RibbonType == null) return Enumerable.Empty<(RibbonItem? Item, XmlNode Node)>();

            return xmlNode?.SelectNodes(RibbonItemNodeName)?.ForEach().Select(node =>
            {
                var type = Assembly.GetAssembly(typeof(RibbonItem)).GetType($"Autodesk.Windows.{node.Name}");
                if (type != null)
                {
                    var ribbonButton = System.Activator.CreateInstance(type);
                    //设置RibbonItem的属性
                    foreach (XmlAttribute btnAttribute in node.Attributes)
                    {
                        if (string.IsNullOrWhiteSpace(btnAttribute.Value)) continue;

                        //根据特性名判断是否为命令
                        if (CommondAttribute == btnAttribute.Name && typeof(RibbonCommandItem).IsAssignableFrom(type))
                        {
                            var proinfo = type.GetProperty(RibbonCommandItem.CommandHandlerPropertyName);

                            var commodMethod = (RibbonType.GetMethods().Where(m => m.Name == btnAttribute.Value && m.GetParameters().Length == 1
                            && m.GetParameters()[0].ParameterType == typeof(object)).FirstOrDefault());
                            Action? doExecute = null;
                            if (commodMethod == null)
                            {
                                var pinfoMethod = RibbonType.GetProperties().Where(p => p.Name == btnAttribute.Value && typeof(Action)
                                   .IsAssignableFrom(p.PropertyType)).FirstOrDefault()?.GetGetMethod();// 判断是否存在委托类型的属性
                                if (pinfoMethod != null)
                                    doExecute = pinfoMethod.Invoke(pinfoMethod.IsStatic ? null : ribbon, null) as Action;
                            }
                            else
                                doExecute = Delegate.CreateDelegate(typeof(Action), commodMethod.IsStatic ? null : ribbon, commodMethod) as Action;

                            proinfo.SetValue(ribbonButton, new CommandBase() { DoCanExecute = e => true, DoExecute = doExecute ?? CommandBase }, null);
                        }
                        //根据特性名称获取对应的属性
                        if (CommondAttribute != btnAttribute.Name)
                        {
                            var propertyInfo = type.GetProperty(btnAttribute.Name);
                            if (propertyInfo == null) continue;

                            if (propertyInfo.PropertyType.Equals(typeof(System.Windows.Media.ImageSource)))
                            {
                                BitmapImage? image;
                                if ((image = GetImageResource(asm, btnAttribute.Value)) != null)
                                    propertyInfo.SetValue(ribbonButton, image, null);
                            }
                            else
                                propertyInfo.SetValue(ribbonButton, propertyInfo.PropertyType.IsEnum ? Enum.Parse(propertyInfo.PropertyType, btnAttribute.Value)
                                    : Convert.ChangeType(btnAttribute.Value, propertyInfo.PropertyType), null);
                        }

                    }
                    return (ribbonButton as RibbonItem, node);
                }
                return (default, node);
            }) ?? Enumerable.Empty<(RibbonItem? Item, XmlNode Node)>();

        }
        /// 
        /// 初始化提示
        /// 
        /// 
        /// 
        RibbonToolTip? InitRibbonToolTip(XmlNode xmlNode)
        {
            var node = xmlNode?.SelectSingleNode(RibbonToolTipNode);
            if (node == null) return null;

            RibbonToolTip toolTip = new();
            foreach (XmlAttribute attribute in node.Attributes)
            {
                var propertyInfo = typeof(RibbonToolTip).GetProperty(attribute.Name);
                if (propertyInfo == null) continue;

                if (propertyInfo.PropertyType.Equals(typeof(System.Windows.Media.ImageSource)))
                {
                    BitmapImage? image;
                    if ((image = GetImageResource(asm, attribute.Value)) != null)
                        propertyInfo.SetValue(toolTip, image, null);
                }
                else
                    propertyInfo.SetValue(toolTip, propertyInfo.PropertyType.IsEnum ? Enum.Parse(propertyInfo.PropertyType, attribute.Value)
                        : Convert.ChangeType(attribute.Value, propertyInfo.PropertyType), null);
            }

            return toolTip;

        }
        /// 
        /// 根据节点属性设置对象的属性
        /// 
        /// 
        /// 实例对象
        /// 
        void SetProperty(T info, XmlAttribute attribute) where T : class
        {
            var propertyInfo = typeof(T).GetProperty(attribute.Name);
            propertyInfo?.SetValue(info, propertyInfo.PropertyType.IsEnum ? Enum.Parse(propertyInfo.PropertyType, attribute.Value)
                : Convert.ChangeType(attribute.Value, propertyInfo.PropertyType), null);
        }
        #endregion
        #region 帮助器
        /// 
        /// 获取程序集的资源图片
        /// 
        /// 
        /// 图片路径
        /// 
        private BitmapImage? GetImageResource(Assembly? asm, string resourceName)
        {
            if (string.IsNullOrWhiteSpace(resourceName) || asm == null) return null;

            var resourcefileName = asm.GetManifestResourceNames()
                          .Where(s => s.Contains(resourceName.Replace(@"\", ".")))
                          .FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(resourcefileName))
                return RibbonExtensions.SteamToBitmapImage(asm.GetManifestResourceStream(resourcefileName));

            if (!string.IsNullOrWhiteSpace(asm.Location) && !string.IsNullOrEmpty(resourceName))
            {
                var dirPath = Directory.GetParent(asm.Location).FullName;
                if (File.Exists(dirPath + resourceName))
                    return new(new(dirPath + resourceName));

                if (dirPath.DirExists(resourceName.Substring(resourceName.LastIndexOf(@"\") + 1), out var path))
                    return new(new(path));
            }
            return null;
        }
        /// 
        /// 获取调用程序集的配置文档
        /// 
        /// 
        /// 
        /// 
        private XmlDocument? GetResourceXmlDoc(Assembly? asm, string resourceName)
        {
            if (asm != null)
            {
                var resourcefileName = asm.GetManifestResourceNames()
                      .Where(s => string.Compare(resourceName, s, StringComparison.OrdinalIgnoreCase) == 0)
                      .FirstOrDefault();

                if (!string.IsNullOrWhiteSpace(resourcefileName))
                {
                    using (StreamReader resourceReader = new StreamReader(asm.GetManifestResourceStream(resourcefileName)))
                    {
                        if (resourceReader != null)
                        {
                            XmlDocument xmlDoc = new XmlDocument();
                            xmlDoc.Load(resourceReader);
                            return xmlDoc;
                        }
                    }
                }

            }
            return null;
        }
        #endregion
    }

}

扩展函数

using Autodesk.Windows;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Media.Imaging;
using System.Xml;

namespace Lad.Ribbons
{
    public static class RibbonExtensions
    {
        /// 
        /// 遍历节点
        /// 
        /// 
        /// 
        internal static IEnumerable ForEach(this XmlNodeList nodeList)
        {
            foreach (XmlNode node in nodeList)
                yield return node;
        }

        internal static BitmapImage? SteamToBitmapImage(Stream stream)
        {
            return stream == null ? null : BitmapToBitmapImage(new(stream));
        }
        // Bitmap --> BitmapImage
        internal static BitmapImage? BitmapToBitmapImage(System.Drawing.Bitmap bitmap)
        {
            if (bitmap == null)
                return null;
            using (MemoryStream stream = new MemoryStream())
            {
                bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png); // 坑点:格式选Bmp时,不带透明度

                stream.Position = 0;
                BitmapImage result = new BitmapImage();
                result.BeginInit();
                // According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed."
                // Force the bitmap to load right now so we can dispose the stream.
                result.CacheOption = BitmapCacheOption.OnLoad;
                result.StreamSource = stream;
                result.EndInit();
                result.Freeze();
                return result;
            }
        }

        /// 
        /// 面板中添加功能按钮
        /// 
        /// 
        /// 按钮组
        public static void AddRibbonItems(this RibbonPanel panel, params RibbonItem[] ribbonItems)
        {
            if (panel == null) throw new ArgumentNullException("RibbonPanel");
            foreach (var item in ribbonItems)
                if (!panel.Source.Items.Any(b => b.Text == item.Text))
                    panel.Source.Items.Add(item);
        }


    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Lad.Ribbons
{
    internal static class FilePathExtension
    {
        /// 
        /// 程序集目录下的子文件夹是否包含配置文件
        /// 
        /// 搜索的文件目录
        /// 搜索的文件名
        /// 文件路径
        /// 是否存在
        public static bool DirExists(this string dirFullName, string searchName, out string path)
        {
            foreach (var dir in System.IO.Directory.GetDirectories(dirFullName))
            {
                path = System.IO.Path.Combine(dir, searchName);
                if (System.IO.File.Exists(path))
                    return true;
                else
                {
                    if (DirExists(dir, searchName, out path))
                        return true;
                }
            }
            path = string.Empty;
            return false;
        }
    }
}

使用示例2

            RibbonInit ribbonInit = new RibbonInit("我的选项卡", "1001");
            ribbonInit.Init(tab =>
            {
                ribbonInit.CreatePanel("信息管理", "10011")
                .AddRibbonItems(new RibbonItem[] {
                    ribbonInit.CreatLargeButton("查询", e =>
                    {
                        var button = e as RibbonButton;
                        if (button.CommandParameter != null)//命令行发送命令
                        {
                            Document doc = AcadApp.DocumentManager.MdiActiveDocument;
                            doc.SendStringToExecute(button.CommandParameter.ToString(), true, false, false);
                        }
                    }, default, "Line ")
                });
            });
                  tab.SetRibbonItemToolTip(btn, "直线", "创建直线段", "Line", "使用Line命令,可以创建一些连续的直线段,每条直线都是可以单独进行编辑的对象",
                      new(Path.Combine(imgPath, "Images", "LineToolTip.png")));

显示结果

//查找面板、选项卡的方法
control.FindTab("1001")   //查找选项卡

control.FindPanel("10011",false) //查找面板---ID对应的是RibbonPanelSource的ID

control.ActiveTab.FindPanel("10011")//查找面板---ID对应的是RibbonPanelSource的ID