菜单选项卡的封装
使用XML来创建选项卡使用案列写在前面,关于文件的说明如下
- 创建一个命令交互类 CommandBase
- 抽象菜单面板基类 IRibbonAbs
- 创建从代码初始化菜单类 RibbonInit
- 创建从配置文件初始化菜单类RibbonInitConfig
- 创建一个借口用于命令绑定 IRibbonUI
- 添加一个XML配置文件 RibbonSetting.xml
- 添加图片文件夹 Images 图片格式为PNG,可以为嵌入资源或者本地文件
示例项目结构
调用方式
using Autodesk.AutoCAD.Runtime; using Lad.Ribbons; using System; using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application; namespace RibbonDemo { /// <summary> /// 分类管理,所有模块 /// </summary> 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(); } } /// <summary> /// 命令 /// </summary> public partial class AppManager { public Action<object> Commond { get; set; } = new Action<object>(e => Console.WriteLine("你好 Commond")); public static Action<object> CommondSt { get; set; } = new Action<object>(e => Console.WriteLine("你好 CommondSt")); public Action<object> CommondLambda = e => Console.WriteLine("你好 CommondLambda"); public static Action<object> 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" ?> <Ribbon> <RibbonTab Title="我的选项卡" Id="1001" IsActive="True"> <RibbonPanel Title="按钮组A" Id="10011"> <RibbonButton Text=" 设置 " Id="20001" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\设置.png" CommandParameter="Line " Orientation="Vertical" Command="Commond"> <RibbonToolTip Title="直线" Content="创建直线" Command="Line" ExpandedImage="\Images\按钮E.png" ExpandedContent="使用LINE命令,可以创建一些连续的直线段,每条直线都是可以单独进行编辑的对象。"/> </RibbonButton> <RibbonButton Text=" 按钮E " Id="20002" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮E.png" CommandParameter="Line " Orientation="Vertical" Command="Commond"/> <RibbonButton Text=" 按钮F " Id="20003" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮F.png" CommandParameter="Line " Orientation="Vertical" Command="CommondSt"/> <RibbonButton Text=" 按钮G " Id="20004" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮G.png" CommandParameter="Line " Orientation="Vertical" Command="CommondLambda"/> <RibbonButton Text=" 按钮H " Id="20005" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮H.png" CommandParameter="Line " Orientation="Vertical" Command="CommondLambdaSt"/> <RibbonButton Text=" 按钮I " Id="20006" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮I.png" CommandParameter="Line " Orientation="Vertical" Command="CommondMethod"/> </RibbonPanel> <RibbonPanel Title="按钮组B" Id="10012"> <RibbonButton Text=" 图形检查 " Id="20007" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\图形检查.png" Orientation="Vertical" Command="CommondMethodSt"/> <RibbonButton Text=" 删除检查标记 " Id="20008" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\删除.png" Orientation="Vertical"/> </RibbonPanel> <RibbonPanel Title="按钮组C" Id="10013" IsVisible="True" IsEnabled="False"> <RibbonButton Text=" 设置 " Id="20009" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\设置.png" Orientation="Vertical"/> <RibbonButton Text=" 按钮A " Id="20010" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮A.png" Orientation="Vertical"/> <RibbonButton Text=" 按钮B " Id="20011" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮B.png" Orientation="Vertical"/> <RibbonButton Text=" 按钮C " Id="20012" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮I.png" Orientation="Vertical"/> <RibbonButton Text=" 按钮D " Id="20013" Size="Large" ShowText="True" ShowImage="True" LargeImage="\Images\按钮E.png" Orientation="Vertical"/> </RibbonPanel> </RibbonTab> </Ribbon>
命令基类
/// <summary> /// 命令交互基础类 /// </summary> internal class CommandBase : System.Windows.Input.ICommand { public event EventHandler CanExecuteChanged; /// <summary> /// 是否可执行,通用方法,传入委托 /// </summary> /// <param name="parameter"></param> /// <returns></returns> public bool CanExecute(object parameter) { //方法为True返回True,其它情况都返回False return DoCanExecute?.Invoke(parameter) == true; } /// <summary> /// 执行 /// </summary> /// <param name="parameter"></param> public void Execute(object parameter) { //如果不为空,执行委托 DoExecute?.Invoke(parameter); } public Action<object> DoExecute { get; set; } public Func<object, bool> 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 { /// <summary> /// 菜单抽象类 /// </summary> 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 面板 /// <summary> /// 将面板添加到RibbonTab /// </summary> /// <param name="panel">面板</param> /// <returns>添加后的RibbonPanel,如果RibbonTab中存在ID相同的RibbonPanel,则返回查找到的RibbonPanel</returns> 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; } /// <summary> /// 根据名称查找面板功能组 /// </summary> /// <param name="name"></param> /// <returns></returns> internal RibbonPanel? FindPanelFrom(string name) { if (string.IsNullOrWhiteSpace(name)) return null; return Tab?.Panels.Where(p => p.Source.Title == name).FirstOrDefault(); } #endregion #region 按钮 /// <summary> /// 添加提示信息 /// </summary> /// <param name="ribbon">按钮条目</param> /// <param name="title">标题,like"直线"</param> /// <param name="content">提示内容,like"创建直线段"</param> /// <param name="command">命令快捷键,like"Line"</param> /// <param name="expandedContent">扩展提示内容,like"使用LINE命令,可以创建一些连续的直线段,每条直线都是可以单独进行编辑的对象。"</param> /// <param name="image ">扩展提示图片</param> 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 /// <summary> /// 从菜单控制器初始化菜单 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="ribbon"></param> /// <param name="action"></param> internal void InitRibbon(Action<RibbonControl> 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<RibbonItemEventArgs>(ComponentManager_ItemInitialized); } if (Autodesk.Windows.ComponentManager.Ribbon == null) Autodesk.Windows.ComponentManager.ItemInitialized += ComponentManager_ItemInitialized; } } }
创建一个空接口
namespace Lad.Ribbons { /// <summary> /// 从配置文件初始化菜单的调用对象需要继承当前接口 /// </summary> 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 { /// <summary> /// 从配置文件初始化菜单 /// </summary> public class RibbonInitConfig : IRibbonAbs { private RibbonControl? control; private IRibbonUI ribbon; private Assembly? asm; private string configPath; private Type? RibbonType; protected Action<object> 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帮助 /// <summary> /// 初始化选项卡 /// </summary> /// <param name="xmlDoc"></param> /// <returns></returns> 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)>(); } /// <summary> /// 初始化面板 /// </summary> /// <param name="xmlNode"></param> /// <returns></returns> 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)>(); } /// <summary> /// 初始化RibbonItem /// </summary> /// <param name="xmlNode"></param> /// <returns></returns> 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<object>? doExecute = null; if (commodMethod == null) { var pinfoMethod = RibbonType.GetProperties().Where(p => p.Name == btnAttribute.Value && typeof(Action<object>) .IsAssignableFrom(p.PropertyType)).FirstOrDefault()?.GetGetMethod();// 判断是否存在委托类型的属性 if (pinfoMethod != null) doExecute = pinfoMethod.Invoke(pinfoMethod.IsStatic ? null : ribbon, null) as Action<object>; } else doExecute = Delegate.CreateDelegate(typeof(Action<object>), commodMethod.IsStatic ? null : ribbon, commodMethod) as Action<object>; 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)>(); } /// <summary> /// 初始化提示 /// </summary> /// <param name="xmlNode"></param> /// <returns></returns> 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; } /// <summary> /// 根据节点属性设置对象的属性 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="info">实例对象</param> /// <param name="attribute"></param> void SetProperty<T>(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 帮助器 /// <summary> /// 获取程序集的资源图片 /// </summary> /// <param name="asm"></param> /// <param name="resourceName">图片路径</param> /// <returns></returns> 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; } /// <summary> /// 获取调用程序集的配置文档 /// </summary> /// <param name="asm"></param> /// <param name="resourceName"></param> /// <returns></returns> 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 { /// <summary> /// 遍历节点 /// </summary> /// <param name="nodeList"></param> /// <returns></returns> internal static IEnumerable<XmlNode> 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; } } /// <summary> /// 面板中添加功能按钮 /// </summary> /// <param name="panel"></param> /// <param name="ribbonItems">按钮组</param> 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 { /// <summary> /// 程序集目录下的子文件夹是否包含配置文件 /// </summary> /// <param name="dirFullName">搜索的文件目录</param> /// <param name="searchName">搜索的文件名</param> /// <param name="path">文件路径</param> /// <returns>是否存在</returns> 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