CAD二次开发--Ribbon相关封装
菜单选项卡的封装
使用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