cad.net 操作cui和cuix含工具条
版本说明
本文测试为Acad2008(Cui)和Acad2021(Cuix)的案例,应该能够涉及所有高低版本的操作.
Cui是个xml,Cuix是个压缩包...知道好像没什么好处,还是应该调用管理器操作而不是序列化操作,除非你发现了其中的一些bug无法避免...
关于宏的说明,看这个桌子的链接
特别的bug
public static class CuiStatic
{
#if NET35
//为了让Acad2008配合高版本的API,所以写了个拓展方法给它
public static bool RemovePartialMenu(this CustomizationSection cs, string fileName, string menuGroupName)
{
return cs.RemovePartialMenu(fileName);//卸载局部cui
}
#endif
}
然后 cs.RemovePartialMenu 这个万恶之源,令我在测试Acad2021移除未融入的cuix的时候是失效的,为什么失效呢?
在我百思不得其解的时候,执行了一次"_.CuiUnLoad"自带的卸载命令,它会将cuix某些错误修复了,
然后再执行 cs.RemovePartialMenu 是成功的了.
修改主要Cui和Cuix
using Autodesk.AutoCAD.Customization;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
//需要引用 AcCui.dll
namespace JoinBox.Menu
{
// 修改mainCui文件,可以考虑更换为修改局部cui替代
// 参考来自 https://adndevblog.typepad.com/autocad/2012/12/customizing-double-click-on-block-reference.html
public partial class Cmd
{
[CommandMethod("ChangeMacroCUI")]
public void ChangeMacroCUI()
{
var cs = Cui.CuiMain(out string expand);
//遍历所有双击动作
DoubleClickAction ad = null;
foreach (DoubleClickAction dca in cs.MenuGroup.DoubleClickActions)
{
if (dca.Name == "块" || dca.Name == "Block")
{
ad = dca;
var ma = ad.DoubleClickCmd.MenuMacroReference.macro;
#if true
ma.Command = "$M =$(if,$(and,$(>,$(getvar,blockeditlock),0)),^C^C_properties,^C^C_bedit)"; //原始的
#else
ma.Command = $"^C^C_{Cmd_MyBEdit}";//新创建的命令
#endif
}
if (dca.Name == "属性块")
{
ad = dca;
var ma = ad.DoubleClickCmd.MenuMacroReference.macro;
#if true
ma.Command = "^C^C_eattedit"; //原始的
#else
ma.Command = $"^C^C_{Cmd_MyBEdit}";//新创建的命令
#endif
}
}
if (cs.IsModified)
{
cs.SaveAs(cs.CUIFileName);//保存覆盖掉主cui文件
CuiStatic.CuiAcedReloadMenus();
}
}
const string Cmd_MyBEdit = "MyBEdit";
[CommandMethod(Cmd_MyBEdit, CommandFlags.UsePickSet)]
public void MyBeditCommand()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var ed = doc.Editor;
var db = doc.Database;
string regAppName = "MyApp";
ed.WriteMessage("\n aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
return;
var result = ed.GetSelection();
if (result.Status != PromptStatus.OK)
{
return;
}
var ss = result.Value;
foreach (SelectedObject so in ss)
{
bool isMyBlockRef = false;
db.Action(tr =>
{
var ent = so.ObjectId.ToEntity(tr);
// 让我们检查XDATA以识别我们是否需要显示我们的对话框
if (ent.GetXDataForApplication(regAppName) != null)
{
isMyBlockRef = true;
}
});
if (isMyBlockRef)
{
Application.ShowAlertDialog("定制行动,如显示我们的表格。");
}
else
{
// 让Auto CAD进行块编辑。
ObjectId[] ids = ss.GetObjectIds();
ed.SetImpliedSelection(ids);
doc.SendStringToExecute("_BEDIT ", false, false, false);
}
}
}
}
}
修改局部Cui和Cuix
大多数时候不需要修改主Cui而是用局部Cui,因为局部Cui会替代主Cui的动作.
using System.IO;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Customization;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
namespace JoinBox.Menu
{
// 修改局部cui文件
// 参考来自: https://through-the-interface.typepad.com/through_the_interface/2007/05/creating_a_part.html
public partial class Cmd
{
/// <summary>
/// 建立CUI菜单
/// </summary>
[CommandMethod("BuildMenuCUI")]
public void BuildMenuCUI()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
var pcfs = Cui.CuiMain(out string expand).PartialCuiFiles;//局部cui
string myCuiName = "Kean";
string myCuiFile = AutoGo.ConfigPath + myCuiName + expand;
string msg = $"\n自定义CUI文件: \"{myCuiFile}\" ";
if (pcfs.Contains(myCuiFile))
{
ed.WriteMessage(msg + "已经加载;");
ed.WriteMessage("\n执行卸载");
Cui.UnLoad(myCuiName);
ed.WriteMessage("\n卸载成功");
return;
}
if (File.Exists(myCuiFile))
{
ed.WriteMessage(msg + "存在,");
}
else
{
ed.WriteMessage(msg + "不存在建立它,");
var pcs = CuiCreate(myCuiName, "Attblockref");//属性块
pcs.SaveAs(myCuiFile); //保存文件可能因为c盘权限而失败,更换到其他盘
}
ed.WriteMessage("执行加载\r");
Cui.Load(myCuiFile);
CuiStatic.CuiAcedReloadMenus();
}
/// <summary>
/// 创建CUI菜单双击
/// </summary>
/// <param name="cuiName">cui菜单的名称</param>
/// <param name="dxfName">限定双击对象的类型,例如"Circle" "Attblockref"</param>
/// <returns></returns>
private static CustomizationSection CuiCreate(string cuiName, string dxfName)
{
// 为我们的部分菜单创建自定义部分
var pcs = new CustomizationSection
{
MenuGroupName = cuiName
};
string doubleClick = "DoubleClick";
//cui双击事件名称...这里也是命令定义的名称,需要用这个名称去动态编译一个命令出来...
string actionName = $"{doubleClick}ActionName_{cuiName}";
//创建双击动作...局部的定义会覆盖掉主cui的定义,覆盖顺序未知
var dca = new DoubleClickAction(pcs.MenuGroup, actionName, -1)
{
Description = "双击自定义",
ElementID = "EID_" + actionName,
DxfName = dxfName
};
var macGroup = new MacroGroup($"MacroGroup_{cuiName}", pcs.MenuGroup);
//actionName是执行的命令,你把他当成命令的唯一身份证号码
var createMenuMacro = macGroup.CreateMenuMacro(actionName, $"^C^C_{actionName}", $"ID_{dxfName}_{doubleClick}_{cuiName}");
var cmd = new DoubleClickCmd(dca)
{
MacroID = createMenuMacro.ElementID
};
dca.DoubleClickCmd = cmd;
return pcs;
}
//这里还没有完成动态编译.......就将就一下
const string Cmd_double = "DoubleClickActionName_Kean";//CuiCreate提供的...最好修改成动态编译
[CommandMethod(Cmd_double)]
public void DoubleClickActionName_Kean()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("\n双击属性块执行了我耶");
}
}
}
Cui类
using System;
using System.Text;
using System.IO;
using Autodesk.AutoCAD.Customization;
using Autodesk.AutoCAD.ApplicationServices;
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
namespace JoinBox.Menu
{
public class Cui
{
/// <summary>
/// 打开主CUI文件
/// </summary>
/// <param name="expand">拓展名</param>
/// <returns></returns>
public static CustomizationSection CuiMain(out string expand)
{
expand = "";
//打开主CUI文件,可以考虑局部cui文件
string old = (string)Acap.GetSystemVariable("MENUNAME");
var mainCuiFile = old;
if (mainCuiFile == null)
{
throw new ArgumentNullException(nameof(CuiMain));
}
if (File.Exists(mainCuiFile + ".cuix"))
{
mainCuiFile += ".cuix";//高版本
expand = ".cuix";
}
else if (File.Exists(mainCuiFile + ".cui"))
{
mainCuiFile += ".cui";//低版本08
expand = ".cui";
}
if (mainCuiFile == old)
{
throw new ArgumentNullException("没有找到对应的cui文件:" + nameof(CuiMain));
}
//反序列化这个文件打开
return new CustomizationSection(mainCuiFile);
}
/// <summary>
/// 加载CUI文件
/// </summary>
/// <param name="cuiFile">cui文件路径</param>
public static void Load(string cuiFile)
{
SendCmd("_.CuiLoad", new string[] { cuiFile });
//var cs = CuiMain(out string expand);
//cs.AddPartialMenu()//加载局部cui,加载类的
}
/// <summary>
/// 卸载CUI文件
/// </summary>
/// <param name="cuiMenuGroupName">组名</param>
public static void UnLoad(string cuiMenuGroupName)
{
// AccordingCadLispSendCmd("_.CuiUnLoad", new string[] { cuiMenuGroupName });
var cs = CuiMain(out string expand);
UnLoad(cs, cuiMenuGroupName);
}
/// <summary>
/// 卸载局部cui
/// </summary>
/// <param name="cs">MainCui</param>
/// <param name="removeCuiFile">指定移除的文件</param>
/// <param name="removeBadFile">移除坏掉的cui</param>
static void UnLoad(CustomizationSection cs, string removeCuiFile, bool removeBadFile = true)
{
var pcfs = cs.PartialCuiFiles;//局部cui
//移除坏掉的cui(删除路径的文件之后显示未融入的)
if (removeBadFile)
{
for (int i = 0; i < pcfs.Count; i++)
{
var fileName = pcfs[i];
if (!File.Exists(fileName))
{
RemovePartialMenu(cs, pcfs, fileName);
i--;
}
}
}
//移除指定名称的局部cui文件
for (int i = 0; i < pcfs.Count; i++)
{
var fileName = pcfs[i];
if (removeCuiFile.ToUpper() == GetFileName(fileName).ToUpper())
{
RemovePartialMenu(cs, pcfs, fileName);
i--;
}
}
if (cs.IsModified)
{
cs.Save();
}
}
/// <summary>
/// 移除局部cui
/// </summary>
/// <param name="cs"></param>
/// <param name="pcfs"></param>
/// <param name="fileName"></param>
static void RemovePartialMenu(CustomizationSection cs, PartialCuiFileCollection pcfs, string fileName)
{
//Acad2008执行RemovePartialMenu会移除了pcfs元素和MainCui的元素
//Acad2021执行RemovePartialMenu是true,但是没有和Acad08一样
var aa2 = cs.RemovePartialMenu(fileName, null);//移除局部cui
if (aa2 && pcfs.Contains(fileName))
{
pcfs.Remove(fileName);
// 如果Acad2021移除失败了,证明了是第一次移除,可能存在cuix的错误导致.
// 利用lisp语句移除是正确的,此命令会导致成功修复了某些东西.
// 毕竟修复了一次之后执行上面就是正确的了,不知道怎么复现.
SendCmd("_.CuiUnLoad", new string[] { fileName });
// https://adndevblog.typepad.com/autocad/2012/07/unload-partial-cuix-when-autocad-quits.html
// 这句无法工作
// var aa3 = Acap.UnloadPartialMenu(fileName);//卸载局部cui
}
}
/// <summary>
/// 路径或(文件名.后缀)获取文件名,因为不存在cui路径时候会未融入
/// </summary>
/// <param name="fileOrPath"></param>
/// <returns></returns>
static string GetFileName(string fileOrPath)
{
var a = fileOrPath.LastIndexOf('\\') + 1;
var b = fileOrPath.LastIndexOf('.');
var groupName = fileOrPath.Substring(a, b - a);
return groupName;
}
/// <summary>
/// 以lisp的方式发送命令
/// </summary>
/// <param name="cuiFile"></param>
static void SendCmd(string cmd, string[] args)
{
Document doc = Application.DocumentManager.MdiActiveDocument;
object oldCmdEcho = Application.GetSystemVariable("CMDECHO");
object oldFileDia = Application.GetSystemVariable("FILEDIA");
Application.SetSystemVariable("CMDECHO", 0);
Application.SetSystemVariable("FILEDIA", 0);
//界面还没准备好导致这里会出错
var arg = new StringBuilder();
foreach (var item in args)
{
arg.Append(item);
}
doc.SendStringToExecute(cmd + " " + arg + " ", false, false, false);
doc.SendStringToExecute("(setvar \"FILEDIA\" " + oldFileDia.ToString() + ")(princ) ", false, false, false);
doc.SendStringToExecute("(setvar \"CMDECHO\" " + oldCmdEcho.ToString() + ")(princ) ", false, false, false);
}
}
}
CuiStatic类
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.Customization;
namespace JoinBox.Menu
{
public static class CuiStatic
{
#if NET35
//为了让Acad2008配合高版本的API,所以写了个拓展方法给它
public static bool RemovePartialMenu(this CustomizationSection cs, string fileName, string menuGroupName)
{
return cs.RemovePartialMenu(fileName);//卸载局部cui
}
#endif
#if NET35
/// <summary>
/// 重新载入CUI文件,刷新当前工作区2008
/// </summary>
/// <returns></returns>
[DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedReloadMenus")]
public static extern int CuiAcedReloadMenus();
#else
/* 这里未能详尽所有的版本 .*/
/// <summary>
/// 重新载入CUI文件,刷新当前工作区2021
/// </summary>
/// <returns></returns>
[DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedReloadMenus@@YAX_N@Z")]
public static extern int CuiAcedReloadMenus();
#endif
}
}
20210528更新,详尽所有的版本
上面的 /* 这里未能详尽所有的版本 .*/ 标记的入口点不一样,但是有规律,所以可以参考此处代码:
JoinBoxCurrency.PeInfo 参考 测试篇 c#读PE32PE32+
namespace JoinBox.Menu
{
public static class CuiStatic
{
/// <summary>
/// 重新载入CUI文件,刷新当前工作区
/// </summary>
/// <returns></returns>
public static int CuiAcedReloadMenus()
{
string funcName = "acedReloadMenus";
// 路径通过程序集就能拿到
var acadexeName = Process.GetCurrentProcess().MainModule.FileName;
bool getYes = false;
//输出所有的函数名
var pe = new JoinBoxCurrency.PeInfo(acadexeName);
var sb = new StringBuilder();
foreach (var name in pe.ExportDirectory.NameList)
{
sb.Append(Environment.NewLine);
var str = Encoding.Default.GetString(name as byte[]);
if (str.Contains(funcName))
{
funcName = str;
getYes = true;
break;
}
sb.Append(str);
}
// Debug.WriteLine(sb.ToString());
if (!getYes)
{
throw new Exception("没有找到对应的函数");
}
IntPtr hModule = Win32API.WinApi.GetModuleHandle(acadexeName); // 执行前必须加载了先...
if (hModule == IntPtr.Zero)
throw new Exception("找不到模块:" + acadexeName + "当前程序没有加载这个东西?");
//函数指针
IntPtr funcAdress = Win32API.WinApi.GetProcAddress(hModule, funcName);
if (funcAdress == IntPtr.Zero)
throw new Exception("找不到函数入口点:" + funcAdress);
//利用委托调用函数指针,从而实现对方法的调用.
var deFunc = Marshal.GetDelegateForFunctionPointer(funcAdress, typeof(DelegateAcedReloadMenus)) as DelegateAcedReloadMenus;
return deFunc.Invoke();//调用方法(刷新cad的cui)
}
//委托,调用函数指针
delegate int DelegateAcedReloadMenus();
}}
ToolBar工具条
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.Customization;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
namespace JoinBox.Menu
{
public class ToolBar
{
[CommandMethod("TestToolBarClose")]
public void TestToolBarClose()
{
var cuiMain = Cui.CuiMain(out string expand);
ToolBarGetExistsPrint(cuiMain);
//关闭工具条
ToolBarClose(cuiMain, "YQARCH");
//ToolBarClose(cuiMain, "MINI");
//ToolBarClose(cuiMain, "ACAD");
}
/// <summary>
/// 打印当前工作空间显示的工具条id
/// </summary>
public static void ToolBarGetExistsPrint(CustomizationSection cs)
{
List<string> lst = new();
//当前使用的工作空间
string wscurrent = (string)Application.GetSystemVariable("wscurrent");
foreach (Workspace pace in cs.Workspaces)//工作空间 "二维草图与注释"
{
var cs2 = pace.CustomizationSection;//空间下的cui
if (wscurrent != pace.Name)//当前空间
{
continue;
}
Debug.WriteLine("**当前使用的空间名字:" + pace.Name);
Debug.WriteLine("**此空间的Cui文件:" + cs2.CUIFileName);
foreach (Toolbar tb in cs2.MenuGroup.Toolbars)
{
if (tb.ToolbarVisible == ToolbarVisible.hide)
{
lst.Add(tb.ElementID);
}
}
break;
//foreach (WorkspaceToolbar tb in pace.WorkspaceToolbars)//工具条
//{
// if (tb.Display == 0)//会遍历到其他空间可展示的东西
// {
// lst.Add(tb.ElementID);
// }
//}
}
//按照字母排序,打印目前已经存在的工具条
lst = lst.OrderBy(elementID => elementID).ToList();
foreach (var item in lst)
{
Debug.WriteLine(item);
}
}
/// <summary>
/// 关闭工具条
/// </summary>
public static void ToolBarClose(CustomizationSection cs, string cuiMenuGroupName)
{
//当前使用的工作空间
string wscurrent = (string)Application.GetSystemVariable("wscurrent");
foreach (Workspace pace in cs.Workspaces)//工作空间 "二维草图与注释"
{
if (wscurrent != pace.Name)//当前空间
{
continue;
}
foreach (WorkspaceToolbar tb in pace.WorkspaceToolbars)//工具条
{
if (tb.MenuGroup == cuiMenuGroupName)
{
pace.WorkspaceToolbars.Remove(tb);//卸载工具条
}
}
break;
}
}
}
}
相关阅读
[尼克劳斯] https://www.cnblogs.com/bomb12138/p/3607990.html
(完)