跳转至

windows程序

使用七牛云api下载图片

先安装七牛云sdk

Bash
pip install qiniu
Bash
1
2
3
4
requests>=2.28.0
beautifulsoup4>=4.11.0
lxml>=4.9.0
pathlib2>=2.3.7; python_version < '3.4'

修改config数据为个人数据

Success
Python
   #!/usr/bin/env python3
   # -*- coding: utf-8 -*-

   """
   使用七牛云API获取文件列表并下载图片文件

   这是推荐的方式,比解析HTML页面更可靠
   需要七牛云的AccessKey和SecretKey
   """

   import os
   import sys
   import time
   import requests
   from pathlib import Path
   from urllib.parse import urljoin

   try:
       from qiniu import Auth, BucketManager
       QINIU_SDK_AVAILABLE = True
   except ImportError:
       QINIU_SDK_AVAILABLE = False
       print("警告:未安装qiniu SDK,请运行: pip install qiniu")

   class QiniuAPIDownloader:
       def __init__(self, access_key, secret_key, bucket_name, download_dir="downloaded_images"):
           """
           初始化七牛云API下载器

           Args:
               access_key: 七牛云AccessKey
               secret_key: 七牛云SecretKey  
               bucket_name: bucket名称
               download_dir: 本地下载目录
           """
           self.access_key = access_key
           self.secret_key = secret_key
           self.bucket_name = bucket_name
           self.download_dir = Path(download_dir)

           # 支持的图片格式
           self.image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico', '.tiff', '.tif'}

           # 创建下载目录
           self.download_dir.mkdir(parents=True, exist_ok=True)

           # 初始化七牛云认证
           if QINIU_SDK_AVAILABLE:
               self.auth = Auth(access_key, secret_key)
               self.bucket_manager = BucketManager(self.auth)
           else:
               self.auth = None
               self.bucket_manager = None

       def get_file_list(self, prefix="", limit=1000):
           """
           获取bucket中的文件列表

           Args:
               prefix: 文件前缀过滤
               limit: 获取文件数量限制

           Returns:
               list: 文件信息列表
           """
           if not QINIU_SDK_AVAILABLE:
               print("错误:需要安装qiniu SDK")
               return []

           try:
               print(f"正在获取bucket '{self.bucket_name}' 中的文件列表...")
               if prefix:
                   print(f"前缀过滤: {prefix}")

               all_files = []
               marker = None

               while True:
                   ret, eof, info = self.bucket_manager.list(
                       self.bucket_name, 
                       prefix=prefix, 
                       marker=marker,
                       limit=limit
                   )

                   if ret is None:
                       print(f"获取文件列表失败: {info}")
                       break

                   files = ret.get('items', [])
                   all_files.extend(files)

                   if eof:
                       break

                   marker = ret.get('marker')

               print(f"找到 {len(all_files)} 个文件")
               return all_files

           except Exception as e:
               print(f"获取文件列表失败: {e}")
               return []

       def filter_image_files(self, file_list):
           """
           筛选出图片文件

           Args:
               file_list: 文件信息列表

           Returns:
               list: 图片文件信息列表
           """
           image_files = []

           for file_info in file_list:
               filename = file_info.get('key', '')
               file_ext = Path(filename).suffix.lower()

               if file_ext in self.image_extensions:
                   image_files.append(file_info)

           print(f"筛选出 {len(image_files)} 个图片文件")
           return image_files

       def get_download_url(self, filename, domain=None, expires=3600):
           """
           获取文件下载URL

           Args:
               filename: 文件名
               domain: 绑定的域名,如果为None则使用默认域名
               expires: URL过期时间(秒)

           Returns:
               str: 下载URL
           """
           if not QINIU_SDK_AVAILABLE:
               return None

           try:
               if domain:
                   # 使用自定义域名
                   if not domain.startswith('http'):
                       domain = f"http://{domain}"
                   base_url = domain.rstrip('/')
                   download_url = self.auth.private_download_url(f"{base_url}/{filename}", expires=expires)
               else:
                   # 使用默认域名(需要配置)
                   # 这里需要替换为实际的域名
                   print("警告:未指定下载域名,请提供bucket绑定的域名")
                   return None

               return download_url

           except Exception as e:
               print(f"生成下载URL失败 {filename}: {e}")
               return None

       def download_file(self, file_info, domain=None):
           """
           下载单个文件

           Args:
               file_info: 文件信息字典
               domain: 下载域名

           Returns:
               bool: 下载是否成功
           """
           filename = file_info.get('key', '')
           filesize = file_info.get('fsize', 0)

           try:
               # 获取下载URL
               download_url = self.get_download_url(filename, domain)
               if not download_url:
                   print(f"无法获取下载URL: {filename}")
                   return False

               print(f"正在下载: {filename} ({filesize} bytes)")

               # 下载文件
               response = requests.get(download_url, stream=True, timeout=60)
               response.raise_for_status()

               # 创建本地文件路径
               local_path = self.download_dir / filename
               local_path.parent.mkdir(parents=True, exist_ok=True)

               # 保存文件
               with open(local_path, 'wb') as f:
                   for chunk in response.iter_content(chunk_size=8192):
                       f.write(chunk)

               print(f"下载完成: {local_path}")
               return True

           except Exception as e:
               print(f"下载失败 {filename}: {e}")
               return False

       def download_images(self, prefix="images/", domain=None):
           """
           下载所有图片文件

           Args:
               prefix: 文件前缀过滤
               domain: 下载域名
           """
           if not QINIU_SDK_AVAILABLE:
               print("错误:请安装qiniu SDK: pip install qiniu")
               return

           print("=== 七牛云API图片下载器 ===\n")

           # 1. 获取文件列表
           file_list = self.get_file_list(prefix)
           if not file_list:
               print("未找到任何文件")
               return

           # 2. 筛选图片文件
           image_files = self.filter_image_files(file_list)
           if not image_files:
               print("未找到图片文件")
               return

           print(f"\n找到的图片文件:")
           for i, file_info in enumerate(image_files, 1):
               filename = file_info.get('key', '')
               filesize = file_info.get('fsize', 0)
               print(f"{i:3d}. {filename} ({filesize} bytes)")

           if not domain:
               print("\n错误:请提供bucket绑定的域名")
               print("例如: your-bucket.domain.com")
               return

           # 3. 下载文件
           print(f"\n开始下载到: {self.download_dir.absolute()}")

           downloaded_count = 0
           failed_count = 0

           for file_info in image_files:
               if self.download_file(file_info, domain):
                   downloaded_count += 1
               else:
                   failed_count += 1

               # 添加延迟避免请求过快
               time.sleep(0.2)

           print(f"\n下载总结:")
           print(f"成功下载: {downloaded_count} 个文件")
           print(f"下载失败: {failed_count} 个文件")
           print(f"文件保存在: {self.download_dir.absolute()}")

   def main():
       """主函数"""
       print("七牛云API下载器")
       print("=" * 30)

       # 配置信息 - 请填入您的七牛云密钥信息
       config = {
           'access_key': '',  # 请填入您的AccessKey
           'secret_key': '',  # 请填入您的SecretKey  
           'bucket_name': '',  # bucket名称
           'prefix': 'images/',  # 文件前缀
           'domain': '',  # 请填入bucket绑定的域名,如: example.com
       }

       # 检查配置
       if not config['access_key'] or not config['secret_key']:
           print("请配置七牛云密钥信息:")
           print("1. 登录七牛云控制台")
           print("2. 在个人中心 -> 密钥管理中获取AccessKey和SecretKey")
           print("3. 将密钥信息填入此脚本的config字典中")
           return

       if not config['domain']:
           print("请配置bucket的访问域名:")
           print("1. 在七牛云控制台的bucket设置中找到绑定的域名")
           print("2. 将域名填入此脚本的config字典中")
           print("   格式如: example.com 或 cdn.example.com")
           return

       # 创建下载器并执行下载
       downloader = QiniuAPIDownloader(
           access_key=config['access_key'],
           secret_key=config['secret_key'], 
           bucket_name=config['bucket_name']
       )

       downloader.download_images(
           prefix=config['prefix'],
           domain=config['domain']
       )

   if __name__ == "__main__":
       main()

def main():函数中config数据根据实际情况填写。

yaml-cpp读写yml文件

代码处理

下载地址

Github

主干版rebase到Tag0.8.0

编译版本

创建一个build文件夹,使用默认配置

image-20241227155336653

Debug编译debug版本

Release编译Release版本

使用的时候预处理器中需要填入YAML_CPP_STATIC_DEFINE

读写yaml文件

UTF-8 和 CString 之间的转换函数

你可以定义一些辅助函数,用于在 UTF-8 和 CString(UTF-16)之间进行转换:

C++
#include <atlstr.h> // CString
#include <string>

// UTF-8 -> CString (UTF-16)
CString Utf8ToCString(const std::string& utf8Str) {
    int wideLength = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, nullptr, 0);
    CString wideStr;
    MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, wideStr.GetBuffer(wideLength), wideLength);
    wideStr.ReleaseBuffer();
    return wideStr;
}

// CString (UTF-16) -> UTF-8
std::string CStringToUtf8(const CString& cstr) {
    int utf8Length = WideCharToMultiByte(CP_UTF8, 0, cstr, -1, nullptr, 0, nullptr, nullptr);
    std::string utf8Str(utf8Length, 0);
    WideCharToMultiByte(CP_UTF8, 0, cstr, -1, &utf8Str[0], utf8Length, nullptr, nullptr);
    return utf8Str;
}

读取 YAML 文件支持 CString

使用 yaml-cpp 读取 YAML 文件时,将读取的 UTF-8 字符串转换为 CString

C++
#include <iostream>
#include <yaml-cpp/yaml.h>
#include <atlstr.h> // CString

// 上述 Utf8ToCString 和 CStringToUtf8 函数应定义在此

int main() {
    try {
        // 加载 YAML 文件
        YAML::Node config = YAML::LoadFile("config.yaml");

        // 读取 UTF-8 编码的字符串,并转换为 CString
        CString name = Utf8ToCString(config["name"].as<std::string>());
        CString description = Utf8ToCString(config["description"].as<std::string>());

        std::wcout << L"Name: " << name.GetString() << std::endl;
        std::wcout << L"Description: " << description.GetString() << std::endl;
    } catch (const YAML::Exception& e) {
        std::cerr << "Error reading YAML file: " << e.what() << std::endl;
    }

    return 0;
}

写入 YAML 文件支持 CString

在写入 YAML 文件时,将 CString 转换为 UTF-8:

C++
#include <iostream>
#include <yaml-cpp/yaml.h>
#include <atlstr.h> // CString

// 上述 Utf8ToCString 和 CStringToUtf8 函数应定义在此

int main() {
    try {
        YAML::Node config;

        // 设置 CString 内容并转换为 UTF-8
        CString name = _T("小明");
        CString description = _T("这是一个包含 Unicode 字符的 YAML 文件。");

        config["name"] = CStringToUtf8(name);
        config["description"] = CStringToUtf8(description);

        // 写入 YAML 文件
        std::ofstream fout("output.yaml");
        fout << config;
        fout.close();

        std::wcout << L"YAML file written successfully!" << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Error writing YAML file: " << e.what() << std::endl;
    }

    return 0;
}

AutoCAD.net Mleader jig

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ZwSoft.ZwCAD.DatabaseServices;
using ZwSoft.ZwCAD.EditorInput;
using ZwSoft.ZwCAD.Geometry;
using ZwSoft.ZwCAD.Runtime;

namespace LeaderPlacement
{
    public class LeaderCmds
    {
        class DirectionalLeaderJig : EntityJig
        {
            private Point3d _start, _end;
            private string _contents;
            private int _index;
            private int _lineIndex;
            private bool _started;
            public DirectionalLeaderJig(string txt, Point3d start, MLeader ld) : base(ld)
            {
                // Store info that's passed in, but don't init the MLeader
                _contents = txt;
                _start = start;
                _end = start;
                _started = false;
            }
            // A fairly standard Sampler function
            protected override SamplerStatus Sampler(JigPrompts prompts)
            {
                var po = new JigPromptPointOptions();
                po.UserInputControls =
                  (UserInputControls.Accept3dCoordinates |
                   UserInputControls.NoNegativeResponseAccepted);
                po.Message = "\nEnd point";
                var res = prompts.AcquirePoint(po);
                if (_end == res.Value)
                {
                    return SamplerStatus.NoChange;
                }
                else if (res.Status == PromptStatus.OK)
                {
                    _end = res.Value;
                    return SamplerStatus.OK;
                }
                return SamplerStatus.Cancel;
            }
            protected override bool Update()
            {
                var ml = (MLeader)Entity;
                if (!_started)
                {
                    if (_start.DistanceTo(_end) > Tolerance.Global.EqualPoint)
                    {
                        // When the jig actually starts - and we have mouse movement -
                        // we create the MText and init the MLeader
                        ml.ContentType = ContentType.MTextContent;
                        var mt = new MText();
                        mt.Contents = _contents;
                        ml.MText = mt;
                        // Create the MLeader cluster and add a line to it
                        _index = ml.AddLeader();
                        _lineIndex = ml.AddLeaderLine(_index);
                        // Set the vertices on the line
                        ml.AddFirstVertex(_lineIndex, _start);
                        ml.AddLastVertex(_lineIndex, _end);
                        // Make sure we don't do this again
                        _started = true;
                    }
                }
                else
                {
                    // We only make the MLeader visible on the second time through
                    // (this also helps avoid some strange geometry flicker)
                    ml.Visible = true;
                    // We already have a line, so just set its last vertex
                    ml.SetLastVertex(_lineIndex, _end);
                }
                if (_started)
                {
                    // Set the direction of the text to depend on the X of the end-point
                    // (i.e. is if to the left or right of the start-point?)
                    var dl = new Vector3d((_end.X >= _start.X ? 1 : -1), 0, 0);
                    ml.SetDogleg(_index, dl);
                }
                return true;
            }
        }

        [CommandMethod("DL")]
        public void DirectionalLeader()
        {
            var doc = ZwSoft.ZwCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            var ed = doc.Editor;
            var db = doc.Database;
            // Ask the user for the string and the start point of the leader
            var pso = new PromptStringOptions("\nEnter text");
            pso.AllowSpaces = true;
            var pr = ed.GetString(pso);
            if (pr.Status != PromptStatus.OK)
                return;
            var ppr = ed.GetPoint("\nStart point of leader");
            if (ppr.Status != PromptStatus.OK)
                return;
            // Start a transaction, as we'll be jigging a db-resident object
            using (var tr = db.TransactionManager.StartTransaction())
            {
                var bt =
                  (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead, false);
                var btr =
                  (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite, false);
                // Create and pass in an invisible MLeader
                // This helps avoid flickering when we start the jig
                var ml = new MLeader();
                ml.Visible = false;
                // Create jig
                var jig = new DirectionalLeaderJig(pr.StringResult, ppr.Value, ml);
                // Add the MLeader to the drawing: this allows it to be displayed
                btr.AppendEntity(ml);
                tr.AddNewlyCreatedDBObject(ml, true);
                // Set end point in the jig
                var res = ed.Drag(jig);
                // If all is well, commit
                if (res.Status == PromptStatus.OK)
                {
                    tr.Commit();
                }
            }
        }
    }
}

Csharp使用Newtonsoft.Json生成JSON字符串

下载newtonsoftjson

在“解决方案资源管理器”中,右键单击项目,然后选择“管理NuGet程序包”。在NuGet包管理器中,搜索“Newtonsoft.Json”。找到Newtonsoft.Json包,点击安装按钮

C#
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

对于复杂json串

  • 对于简单的json的可以直接解析, 复杂的json, 建议用先创建json对应的类,然后再用JsonConvert.DeserializeObject转为类来解析, 当json比较复杂时, 创建类也比较浪费时间, VS2022为C#提供了json转C#类的工具,先复制需要转为类的json字符串,然后将光标定位到cs文件的空白处,最后点击编辑–选择性粘贴–将Json粘贴为类,如下图:

  • 除了VS自带的工具,也有一些网站提供了类似的功能,例如Json2CSharp

### demo

JSON
1
2
3
4
5
6
7
8
9
{
    "items": [{
        "id": "csrf",
        "attributes": {
            "nonce key": "CSRF NONCE",
            "nonce": "i8Ah1n1DHs71704s2oZnSxmiz4/R3T5mbFrkxErz4m8RUDf3kyX+ror25kZ09Env0tGeVBe+iES8/Y04XRfAKvghp1/+ZIx09oVE7GiE"
        }
    }]
}

class

C#
//如果好用,请收藏地址,帮忙分享。
public class Attributes
{
    /// <summary>
    /// 
    /// </summary>
    public string nonce_key { get; set; }
/// <summary>
/// 
/// </summary>
    public string nonce { get; set; }
}

public class ItemsItem
{
    /// <summary>
    /// 
    /// </summary>
    public string id { get; set; }
    /// <summary>
    /// 
    /// </summary>
    public Attributes attributes { get; set; }
}
//Root可以改成自己喜欢的类名
public class CScrfRoot
{
    /// <summary>
    /// 
    /// </summary>
    public List<ItemsItem> items { get; set; }
}

读取json文件

C#
public static string GetDllDirectory()
{
    string codeBase = Assembly.GetExecutingAssembly().CodeBase;
    UriBuilder uri = new UriBuilder(codeBase);
    string path = Uri.UnescapeDataString(uri.Path);
    return System.IO.Path.GetDirectoryName(path);
}
public MyDropMenu()
{
    System.Uri resourceLocater = new System.Uri("/MyDropMenu;component/ComUseicons.xaml", System.UriKind.Relative);
    ResourceDictionary rd = (ResourceDictionary)Application.LoadComponent(resourceLocater);
    Application.Current.Resources.MergedDictionaries.Add(rd);

    InitializeComponent();
    try
    {
        string jsonFile = GetDllDirectory() + "\\Menu.json";
        // 确保文件存在
        if (!File.Exists(jsonFile))
            throw new FileNotFoundException("The JSON file was not found." + jsonFile);

        // 读取文件内容并反序列化为指定的类型 T
        var reader = new StreamReader(jsonFile);
        var json = reader.ReadToEnd();
        var person = JsonConvert.DeserializeObject<Root>(json);
        Items = person.ItemMenu;
        //遍历items,将icon添加数据
        // 遍历并修改Icon
        foreach (var itemMenu in Items)
        {
            itemMenu.Icon = GetDllDirectory() + "\\config\\Images\\PNG\\" + itemMenu.Icon;
            foreach (var subItem in itemMenu.SubItems)
            {
                subItem.Icon = GetDllDirectory() + "\\config\\Images\\PNG\\" + subItem.Icon;
            }
        }
        LeftMenu.ItemsSource = Items;
    }
    catch (Exception)
    {
        throw;
    }
}

获取token(nonce)值

C#
 public static string getTokenFromJson(string strJson)
        {
            string strRet = "";
            //strJson = "{\"items\":[{\"id\":\"csrf\",\"attributes\":{\"nonce key\":\"CSRF NONCE\",\"nonce\":\"i8Ah1n1DHs71704s2oZnSxmiz4/R3T5mbFrkxErz4m8RUDf3kyX+ror25kZ09Env0tGeVBe+iES8/Y04XRfAKvghp1/+ZIx09oVE7GiE\"}}]}";
            var person = JsonConvert.DeserializeObject<CScrfRoot>(strJson);
            List<ItemsItem> listItems = person.items;
            if(listItems.Count >= 1)
            {
                ItemsItem itemsItem = listItems[0];
                Attributes attr = itemsItem.attributes;
                strRet = attr.nonce;
            }

            return strRet;
        }

LINQ to JSON主要使用到JObject, JArray, JProperty和JValue这四个对象

  • JObject用来生成一个JSON对象,简单来说就是生成”{}”,
  • JArray用来生成一个JSON数组,也就是”[]”,
  • JProperty用来生成一个JSON数据,格式为key/value的值,
  • 而JValue则直接生成一个JSON值
C#
//将json转换为JObject
JObject jObj = new JObject();
jObj.Add("process0id", AdditionClass.GetDeFaultProjectNo());


PdfRow pdfRow1 = new PdfRow();
pdfRow1.status = "success";
pdfRow1.pdfname = "D:\\ZWPDF\\PDF\\JG-72-BL-LB1.pdf";


PdfRow pdfRow2 = new PdfRow();
pdfRow2.status = "error";
pdfRow2.pdfname = "D:\\ZWPDF\\PDF\\JG-72-BL-LB2.pdf";


List<PdfRow> videogames = new List<PdfRow>();
videogames.Add(pdfRow1);
videogames.Add(pdfRow2);

JArray jArray = (JArray)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(videogames));
jObj.Add("message", "转换完成");
jObj.Add("rowPdf", jArray);
Console.WriteLine(jObj.ToString());
JSON
{
  "process0id": "05369",
  "message": "转换完成",
  "rowPdf": [
    {
      "status": "error",
      "pdfname": "D:\\TEST\\ZP-35-DYT-35N3--竖向图框.dwg"
    },
    {
      "status": "error",
      "pdfname": "D:\\TEST\\图框外有多余线条.dwg"
    },
    {
      "status": "error",
      "pdfname": "D:\\TEST\\弧线标注的圆心在图框外1.dwg"
    }
  ]
}

Python应用程序打包指南

使用PyInstaller将Python脚本打包为可执行文件:

Bash
pyinstaller -F -w -i 21.ico gitlog.py

参数说明: - -F: 生成单个可执行文件 - -w: 不显示控制台窗口(适用于GUI应用) - -i 21.ico: 指定应用程序图标 - gitlog.py: 要打包的Python脚本文件

安装docsify

  • 先安装nodejs

  • 安装docsify工具

Bash
npm i docsify-cli -g
  • 初始化目录
Text Only
1
2
3
docsify init <path> [--local false] [--theme vue] [--plugins false]

# docsify i <path> [-l false] [-t vue] [--plugins false]

<path>默认为当前目录。使用./docs(或docs)之类的相对路径。

  • local选项:

    • 速记:-l
    • 类型:布尔值
    • 默认:false
    • 说明:将文件复制docsify到文档路径,默认值是false用来使用cdn.jsdelivr.net<内容分发网络(CDN)>。要显式设置此选项以使用--no-local
  • theme选项:

    • 速记:-t
    • 类型:字符串
    • 默认:vue
    • 说明:选择一个主题,默认为vue,其他选项为bubledarkpure
  • plugins选项:

    • 速记:-p
    • 类型:布尔值
    • 默认:false
    • 描述:提供插件列表作为<script>标签插入到index.html.
Bash
docsify init ./docs
  • serve命令

localhost使用 livereload运行服务器。

Bash
1
2
3
docsify serve <path> [--open false] [--port 3000]

# docsify s <path> [-o false] [-p 3000]
  • open选项:

    • 速记:-o
    • 类型:布尔值
    • 默认:false
    • 说明:在默认浏览器中打开文档,默认为false. 要显式设置此选项以false使用--no-open.
  • port选项:

    • 速记:-p
    • 类型:数字
    • 默认:3000
    • 说明:选择监听端口,默认为3000.
  • Docsify 的生成器。

Bash
1
2
3
docsify generate <path> [--sidebar _sidebar.md]

# docsify g <path> [-s _sidebar.md]
  • sidebar选项:
    • 速记:-s
    • 类型:字符串
    • 默认:_sidebar.md
    • 说明:生成侧边栏文件,默认为_sidebar.md.

插件设置

设置配色

HTML
<script>
    .markdown-section strong {
          color: rgb(239, 112, 96);
        }

    .markdown-section code {
      border-radius: 2px;
      font-family: "Helvetica Neue",Helvetica,"Hiragino Sans GB","Microsoft YaHei",Arial,sans-serif;
      font-size: 16px !important;
      margin: 0 2px;
      padding: 3px 5px;
      white-space: nowrap;
      /*border: 1px solid #282c34;*/
      /*color: rgb(184, 101, 208);*/
    }
    .markdown-section > div > img, .markdown-section pre {
      box-shadow: 2px 2px 20px 6px rgb(255, 255, 255) !important;
    }

    .markdown-section a:not(:hover) {
      text-decoration: none;
    }
    #main h2 span{ color:#18b566 !important; }
    #main h3 span{ color:#089acc !important; }
    #main h4 span{ color:#FF9700 !important; }
    p code{
      background-color: rgb(255, 255, 255) !important;
    }

    /*添加代码块复制按钮样式*/
    .docsify-copy-code-button {
      background: #00a1d6 !important;
      color: #FFFFFF !important;
      font-size: 13px !important;
    }

    ::after{
      color: #9da2fd !important;
      font-size: 13px !important;
    }
    .markdown-section>p {
      font-size: 16px !important;
    }


    /*代码块头部图标 start*/
    .markdown-section pre:before {
      content: '';
      display: block;
      background: url(_media/codeHeader.png);
      height: 30px;
      background-size: 40px;
      background-repeat: no-repeat;
      background-color: #1C1C1C;
      background-position: 10px 10px;
    }
    /*代码块头部图标 end*/

    .markdown-section pre>code {
      color: #c0c3c1;
      font-family: 'Inconsolata', consolas,"PingFang SC", "Microsoft YaHei", monospace;
      background-color: #212121;
      font-size: 15px;
      white-space: pre;
      line-height: 1.5;
      -moz-tab-size: 4;
      -o-tab-size: 4;
      tab-size: 4;
    }

    @media (max-width:600px) {
      pre {
        padding-left: 3px !important;
        padding-right: 3px !important;
        margin-left: -20px !important;
        margin-right: -20px !important;
        box-shadow: 0px 0px 20px 0px #f7f7f7 !important;
      }

      /*代码块复制按钮默认隐藏*/
      .docsify-copy-code-button {
        display: none;
      }

      .advertisement{
        display: none;
      }

    }

    .token.keyword{
      color: #f92672 !important;
    }

    .token.comment{
      color: #75715e !important;
    }

    .token.tag{
      color: #a589ad !important;
    }

    .token.attr-name{
      color: #de916c !important;
    }

    .token.attr-value{
      color: #4faee2 !important;
    }

    .token.macro.property{
      color: #4faee2 !important;
    }

    .token.function{
      color: #66D9EF !important;
    }
    .token.string{
      color: #e6db74 !important;
    }
    .token.punctuation{
      color: #c0c3c1 !important;
    }

    .token.number{
      color: #ae81ff  !important;
    }
    .token.operator{
      color: #f92672 !important;
    }
    .token.builtin{
      color: #66D9EF !important;
    }
    .token.decorator.annotation.punctuation
    {
      color: #a6e22e !important;
    }

    .token.class-name{
      color: #a6e22e !important;
    }

    .token.namespace{
      color: #f92672 !important;
    }

    .token.property{
      color: #f92672 !important;
    }

    .token.parameter{
      color: #f92672 !important;
    }

    .token.variable{
      color: #f92672 !important;
    }

    .token.namespace{
      color: #ededed !important;
    }
</script>
HTML
<script src="assets/js/docsify-copy-code.min.js"></script>
<script src="assets/js/docsify-tabs.min.js"></script>
<script src="assets/js/docsify-themeable.min.js"></script>
<script src="assets/js/prism-line-numbers.min.js"></script>
<script src="assets/js/docsify-sidebar-collapse.min.js"></script>
<script src="assets/js/search.js"></script>
<script src="assets/js/docsify.min.js"></script>
<script src="assets/js/emoji.min.js"></script>
<script src="assets/js/zoom-image.min.js"></script>
<script src="assets/js/prism-autoloader.min.js"></script>
<script src="assets/js/prism-autoloader.js"></script>
<script src="assets/js/prism-javascript.js"></script>
<script src="assets/js/prism-php.js"></script>
<script src="assets/js/prism-bash.js"></script>
<script src="assets/js/prism-c.js"></script>
<script src="assets/js/prism-cpp.js"></script>
<script src="assets/js/prism-python.js"></script>
<script src="assets/js/prism-go.js"></script>
<script src="assets/js/prism-java.js"></script>
<script src="assets/js/prism-sql.js"></script>
<script src="assets/js/prism-markup.js"></script>
<script src="assets/js/prism-yaml.js"></script>
<script src="assets/js/prism-json.js"></script>
<script src="assets/js/prism-docker.js"></script>
<script src="assets/js/prism-git.js"></script>
<script src="assets/js/prism-dart.js"></script>
<script src="assets/js/prism-ini.js"></script>
<script src="assets/js/prism-nginx.js"></script>
<script src="assets/js/prism-css.js"></script>
<script src="assets/js/prism-http.js"></script>
<script src="assets/js/prism-latex.js"></script>
<script src="assets/js/prism-markdown.js"></script>
<script src="assets/js/prism-matlab.js"></script>
<script src="assets/js/prism-powershell.js"></script>
<script src="assets/js/prism-c++.js"></script>
<script src="assets/js/prism-csharp.js"></script>

出现两个搜索框

  • 隐藏第一个input框就好
XML
1
2
3
4
5
<style>
.sidebar .search:nth-child(1){
  display: none;
}
</style>

插件的问题直接去

https://docsify.js.org/#/awesome?id=plugins 查询

美化提示样式

Docsify-alerts

[!NOTE] An alert of type 'note' using global style 'callout'.

[!NOTE|style:flat] An alert of type 'note' using alert specific style 'flat' which overrides global style 'callout'.

As you can see in the second snippet, output can be configured on alert level also. Supported options are listed in following table:

Key Allowed value
style One of follwowing values: callout, flat
label Any text
icon A valid Font Awesome icon, e.g. 'fas fa-comment'
className A name of a CSS class which specifies the look and feel
labelVisibility One of follwowing values: visible (default), hidden
iconVisibility One of follwowing values: visible (default), hidden

[!TIP|style:flat|label:My own heading|iconVisibility:hidden] An alert of type 'tip' using alert specific style 'flat' which overrides global style 'callout'. In addition, this alert uses an own heading and hides specific icon.

As mentioned above you can provide your own alert types. Therefore, you have to provide the type configuration via index.html. Following example shows an additional type COMMENT.

HTML
<script>
  window.$docsify = {
    'flexible-alerts': {
      comment: {
        label: 'Comment',

        // localization
        label: {
          '/en-GB/': 'Comment',
          '/': 'Kommentar'
        },

        // Assuming that we use Font Awesome
        icon: 'fas fa-comment',
        className: 'note'
      }
    }
  };
</script>

[!COMMENT] An alert of type 'comment' using style 'callout' with default settings.

avatar

avatar

docsify-plantuml

docsify-plantuml

HTML
1
2
3
4
5
6
7
8
9
<script>
window.$docsify = {
  plantuml: {
    skin: 'default',
  },
}
</script>

<script src="//unpkg.com/docsify-plantuml/dist/docsify-plantuml.min.js"></script>
  • 怎么用
Text Only
1
2
3
4
5
6
7
8
9
@startuml
autonumber

Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response

Alice -> Bob: Another authentication Request
Alice <-- Bob: another authentication Response
@enduml
Text Only
1
2
3
4
@startuml
Alice -> Bob: Authentication Request [[$./other-file docs]]
Bob --> Alice: Authentication Response [[$../other-file docs]]
@enduml

徽章查询服务

Bash
1
2
3
[徽章](badgen.net)

[自定义徽章](img.shields.io)

WPF自定义抽屉菜单

处理wpf自定义控件

image-20250901162509475

DropMenuControls\Converters\BooleanToVisibilityConverter

C#
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace DropMenuControls.Converters
{
    public class BooleanToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool boolValue)
            {
                return boolValue ? Visibility.Visible : Visibility.Collapsed;
            }
            return Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Visibility visibility)
            {
                return visibility == Visibility.Visible;
            }
            return false;
        }
    }
}

DropMenuControls\Models\MenuItem

C#
using System.Collections.Generic;

namespace DropMenuControls.Models
{
    public class MenuData
    {
        public List<MenuItem> ItemMenu { get; set; }
    }

    public class MenuItem
    {
        public string Name { get; set; }
        public string Icon { get; set; }
        public int FontSize { get; set; }
        public string Command { get; set; }
        public List<MenuItem> SubItems { get; set; }

        public bool HasSubItems => SubItems != null && SubItems.Count > 0;
    }
}

Generic.xaml

C#
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DropMenuControls">

    <!--  DropMenu 控件的默认样式  -->
    <Style TargetType="{x:Type local:DropMenu}">
        <Setter Property="Background" Value="White" />
        <Setter Property="BorderBrush" Value="#DDDDDD" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="FontFamily" Value="Segoe UI" />
        <Setter Property="FontSize" Value="14" />
        <Setter Property="Foreground" Value="#333333" />
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="UseLayoutRounding" Value="True" />
    </Style>

</ResourceDictionary>

DropMenuControls\ViewModels\DropMenuViewModel

C#
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Windows;
using Newtonsoft.Json;
using DropMenuControls.Models;
using System.Reflection;

namespace DropMenuControls.ViewModels
{
    public class DropMenuViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<MenuItemViewModel> MenuItems { get; } = new ObservableCollection<MenuItemViewModel>();

        private MenuItemViewModel _selectedItem;
        public MenuItemViewModel SelectedItem
        {
            get => _selectedItem;
            set
            {
                if (_selectedItem != value)
                {
                    _selectedItem = value;
                    OnPropertyChanged(nameof(SelectedItem));
                    // 移除这里的事件触发,避免重复调用
                    // 事件触发应该只在OnMenuItemClicked中处理
                }
            }
        }

        private string _menuConfigPath = "Menu.json";
        /// <summary>
        /// 菜单配置文件路径
        /// </summary>
        public string MenuConfigPath
        {
            get => _menuConfigPath;
            set
            {
                if (_menuConfigPath != value)
                {
                    _menuConfigPath = value;
                    OnPropertyChanged(nameof(MenuConfigPath));
                }
            }
        }

        private string _iconDirectory = "Icons";
        /// <summary>
        /// 图标目录路径
        /// </summary>
        public string IconDirectory
        {
            get => _iconDirectory;
            set
            {
                if (_iconDirectory != value)
                {
                    _iconDirectory = value;
                    OnPropertyChanged(nameof(IconDirectory));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public DropMenuViewModel()
        {
            LoadMenuFromJson();
        }

        public DropMenuViewModel(string menuConfigPath, string iconDirectory)
        {
            MenuConfigPath = menuConfigPath;
            IconDirectory = iconDirectory;
            LoadMenuFromJson();
        }

        public void LoadMenuFromJson()
        {
            try
            {
                // 使用配置的路径,支持相对路径和绝对路径
                string jsonPath;
                if (Path.IsPathRooted(MenuConfigPath))
                {
                    // 绝对路径
                    jsonPath = MenuConfigPath;
                }
                else
                {
                    // 相对路径,相对于执行程序目录
                    jsonPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), MenuConfigPath);
                }
                if (!File.Exists(jsonPath))
                {
                    System.Diagnostics.Debug.WriteLine($"菜单配置文件未找到: {jsonPath}");
                    return;
                }

                string jsonContent = File.ReadAllText(jsonPath);

                var settings = new JsonSerializerSettings
                {
                    MissingMemberHandling = MissingMemberHandling.Ignore,
                    NullValueHandling = NullValueHandling.Ignore
                };

                MenuData menuData = JsonConvert.DeserializeObject<MenuData>(jsonContent, settings);

                if (menuData?.ItemMenu != null)
                {
                    BuildMenuItems(menuData.ItemMenu);
                }

            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"加载菜单配置失败: {ex.Message}");
            }
        }

        private void BuildMenuItems(System.Collections.Generic.List<MenuItem> menuItems)
        {
            MenuItems.Clear();

            foreach (var item in menuItems)
            {
                var viewModel = new MenuItemViewModel(item, IconDirectory);
                BindMenuItemEvents(viewModel); // 递归绑定所有菜单项的事件
                MenuItems.Add(viewModel);
            }
        }

        // 递归绑定菜单项和所有子项的事件
        private void BindMenuItemEvents(MenuItemViewModel menuItem)
        {
            menuItem.MenuItemClicked += OnMenuItemClicked;

            // 为所有子项也绑定事件
            foreach (var subItem in menuItem.SubItems)
            {
                BindMenuItemEvents(subItem);
            }
        }

        private void OnMenuItemClicked(MenuItemViewModel clickedItem)
        {
            System.Diagnostics.Debug.WriteLine($"[DropMenuViewModel] OnMenuItemClicked被调用: {clickedItem.Name}");

            // 只有当菜单项有命令时才处理选中状态和触发事件
            if (!string.IsNullOrWhiteSpace(clickedItem.Command))
            {
                System.Diagnostics.Debug.WriteLine($"[DropMenuViewModel] 处理有命令的菜单项: {clickedItem.Command}");

                // 清除所有选中状态
                ClearAllSelections();

                // 设置当前选中项
                SelectedItem = clickedItem;
                clickedItem.IsSelected = true;

                // 记录命令并触发外部事件(只触发一次)
                ShowCommandMessage(clickedItem.Command);
                RaiseMenuItemClickedEvent(clickedItem);
            }
            else
            {
                System.Diagnostics.Debug.WriteLine($"[DropMenuViewModel] 跳过无命令的菜单项: {clickedItem.Name}");
            }
        }

        private void ClearAllSelections()
        {
            foreach (var item in MenuItems)
            {
                ClearSelectionRecursive(item);
            }
        }

        // 递归清除所有层级的选中状态
        private void ClearSelectionRecursive(MenuItemViewModel menuItem)
        {
            menuItem.IsSelected = false;

            foreach (var subItem in menuItem.SubItems)
            {
                ClearSelectionRecursive(subItem);
            }
        }

        private void ShowCommandMessage(string command)
        {
            // 不在ViewModel中显示MessageBox,将这个责任交给UI层处理
            // 这样避免了重复弹出对话框的问题
            System.Diagnostics.Debug.WriteLine($"菜单命令被触发: {command}");
        }

        // 公共方法,供外部调用以触发菜单项点击事件
        public event Action<MenuItemViewModel> MenuItemClickedEvent;

        private void RaiseMenuItemClickedEvent(MenuItemViewModel item)
        {
            System.Diagnostics.Debug.WriteLine($"[DropMenuViewModel] 向外部触发MenuItemClickedEvent: {item.Name}");
            MenuItemClickedEvent?.Invoke(item);
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

DropMenuControls\ViewModels\MenuItemViewModel

C#
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using DropMenuControls.Models;

namespace DropMenuControls.ViewModels
{
    public class MenuItemViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// 图标目录路径
        /// </summary>
        public string IconDirectory { get; set; } = "Icons";

        private string _name;
        private string _icon;
        private int _fontSize;
        private string _command;
        private bool _isExpanded;
        private bool _isSelected;
        private BitmapImage _iconSource;

        public string Name
        {
            get => _name;
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged(nameof(Name));
                }
            }
        }

        public string Icon
        {
            get => _icon;
            set
            {
                if (_icon != value)
                {
                    _icon = value;
                    OnPropertyChanged(nameof(Icon));
                    LoadIcon();
                }
            }
        }

        public int FontSize
        {
            get => _fontSize;
            set
            {
                if (_fontSize != value)
                {
                    _fontSize = value;
                    OnPropertyChanged(nameof(FontSize));
                }
            }
        }

        public string Command
        {
            get => _command;
            set
            {
                if (_command != value)
                {
                    _command = value;
                    OnPropertyChanged(nameof(Command));
                }
            }
        }

        public bool IsExpanded
        {
            get => _isExpanded;
            set
            {
                if (_isExpanded != value)
                {
                    _isExpanded = value;
                    OnPropertyChanged(nameof(IsExpanded));
                }
            }
        }

        public bool IsSelected
        {
            get => _isSelected;
            set
            {
                if (_isSelected != value)
                {
                    _isSelected = value;
                    OnPropertyChanged(nameof(IsSelected));
                }
            }
        }

        public BitmapImage IconSource
        {
            get => _iconSource;
            set
            {
                if (_iconSource != value)
                {
                    _iconSource = value;
                    OnPropertyChanged(nameof(IconSource));
                }
            }
        }

        public ObservableCollection<MenuItemViewModel> SubItems { get; } = new ObservableCollection<MenuItemViewModel>();

        public bool HasSubItems => SubItems.Count > 0;

        private void LoadIcon()
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(Icon))
                {
                    string iconPath;
                    if (Path.IsPathRooted(IconDirectory))
                    {
                        // IconDirectory是绝对路径
                        iconPath = Path.Combine(IconDirectory, Icon);
                    }
                    else
                    {
                        // IconDirectory是相对路径,相对于执行程序目录
                        iconPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), IconDirectory, Icon);
                    }
                    if (File.Exists(iconPath))
                    {
                        var bitmap = new BitmapImage();
                        bitmap.BeginInit();
                        bitmap.UriSource = new Uri(iconPath, UriKind.Absolute);
                        bitmap.DecodePixelWidth = 24;
                        bitmap.DecodePixelHeight = 24;
                        bitmap.EndInit();
                        IconSource = bitmap;
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"加载图标失败: {ex.Message}");
            }
        }

        public ICommand ExecuteMenuItemCommand { get; }
        public event Action<MenuItemViewModel> MenuItemClicked;
        public event PropertyChangedEventHandler PropertyChanged;

        public MenuItemViewModel()
        {
            ExecuteMenuItemCommand = new RelayCommand(ExecuteMenuItem);
        }

        public MenuItemViewModel(MenuItem menuItem, string iconDirectory = "Icons")
        {
            ExecuteMenuItemCommand = new RelayCommand(ExecuteMenuItem);
            Name = menuItem.Name;
            Icon = menuItem.Icon;
            FontSize = menuItem.FontSize;
            Command = menuItem.Command;
            IconDirectory = iconDirectory;
            LoadIcon();

            if (menuItem.SubItems != null)
            {
                foreach (var subItem in menuItem.SubItems)
                {
                    var subViewModel = new MenuItemViewModel(subItem, iconDirectory);
                    SubItems.Add(subViewModel);
                }
            }

            OnPropertyChanged(nameof(HasSubItems));
        }

        private DateTime _lastExecutionTime = DateTime.MinValue;
        private static readonly TimeSpan MinimumInterval = TimeSpan.FromMilliseconds(500); // 500ms防重复间隔

        private void ExecuteMenuItem()
        {
            var currentTime = DateTime.Now;

            // 防重复触发机制 - 使用时间间隔控制
            if (currentTime - _lastExecutionTime < MinimumInterval)
            {
                System.Diagnostics.Debug.WriteLine($"[MenuItemViewModel] 防止重复执行 (间隔太短): {Name}, 间隔: {(currentTime - _lastExecutionTime).TotalMilliseconds}ms");
                return;
            }

            _lastExecutionTime = currentTime;
            System.Diagnostics.Debug.WriteLine($"[MenuItemViewModel] 执行菜单项: {Name}");

            // 如果有子项,切换展开状态
            if (HasSubItems)
            {
                IsExpanded = !IsExpanded;
            }

            // 总是触发事件,让DropMenuViewModel来处理逻辑(包括选中状态和命令执行)
            MenuItemClicked?.Invoke(this);
        }

        // 清除选中状态(用于实现单选逻辑)
        public void ClearSelection()
        {
            IsSelected = false;
            foreach (var subItem in SubItems)
            {
                subItem.ClearSelection();
            }
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    // Csharp 7.3 兼容的RelayCommand实现
    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;

        public RelayCommand(Action execute, Func<bool> canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute?.Invoke() ?? true;
        }

        public void Execute(object parameter)
        {
            _execute();
        }
    }
}

DropMenu.xaml

C#
<UserControl
    x:Class="DropMenuControls.DropMenu"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:converters="clr-namespace:DropMenuControls.Converters"
    xmlns:vm="clr-namespace:DropMenuControls.ViewModels">

    <UserControl.Resources>
        <converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />

        <!--  主菜单按钮样式 - 现代化动态效果  -->
        <Style x:Key="MenuButtonStyle" TargetType="Button">
            <Setter Property="Background" Value="#F8F9FA" />
            <Setter Property="BorderBrush" Value="#E0E0E0" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="Padding" Value="15,12" />
            <Setter Property="Margin" Value="5,2" />
            <Setter Property="HorizontalContentAlignment" Value="Left" />
            <Setter Property="Cursor" Value="Hand" />
            <Setter Property="FontSize" Value="14" />
            <Setter Property="FontWeight" Value="Medium" />
            <Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect
                        BlurRadius="0"
                        Opacity="0"
                        ShadowDepth="0"
                        Color="Black" />
                </Setter.Value>
            </Setter>
            <Setter Property="RenderTransform">
                <Setter.Value>
                    <TransformGroup>
                        <ScaleTransform ScaleX="1" ScaleY="1" />
                        <TranslateTransform X="0" Y="0" />
                    </TransformGroup>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsSelected}" Value="True">
                    <Setter Property="Background" Value="#2196F3" />
                    <Setter Property="Foreground" Value="White" />
                    <Setter Property="BorderBrush" Value="#1976D2" />
                    <Setter Property="FontWeight" Value="Bold" />
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                    To="1.03"
                                    Duration="0:0:0.25" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    To="1.03"
                                    Duration="0:0:0.25" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Effect.BlurRadius"
                                    To="8"
                                    Duration="0:0:0.25" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Effect.Opacity"
                                    To="0.4"
                                    Duration="0:0:0.25" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Effect.ShadowDepth"
                                    To="2"
                                    Duration="0:0:0.25" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                    To="1"
                                    Duration="0:0:0.25" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    To="1"
                                    Duration="0:0:0.25" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Effect.BlurRadius"
                                    To="0"
                                    Duration="0:0:0.25" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Effect.Opacity"
                                    To="0"
                                    Duration="0:0:0.25" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Effect.ShadowDepth"
                                    To="0"
                                    Duration="0:0:0.25" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.ExitActions>
                </DataTrigger>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#E8F4FD" />
                    <Setter Property="BorderBrush" Value="#2196F3" />
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[1].Y"
                                    To="-1"
                                    Duration="0:0:0.2" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                    To="1.01"
                                    Duration="0:0:0.2" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    To="1.01"
                                    Duration="0:0:0.2" />
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[1].Y"
                                    To="0"
                                    Duration="0:0:0.2" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                    To="1"
                                    Duration="0:0:0.2" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    To="1"
                                    Duration="0:0:0.2" />
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.ExitActions>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                    To="0.97"
                                    Duration="0:0:0.1" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    To="0.97"
                                    Duration="0:0:0.1" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[1].Y"
                                    To="1"
                                    Duration="0:0:0.1" />
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                    To="1"
                                    Duration="0:0:0.15" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    To="1"
                                    Duration="0:0:0.15" />
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[1].Y"
                                    To="0"
                                    Duration="0:0:0.15" />
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.ExitActions>
                </Trigger>
            </Style.Triggers>
        </Style>

        <!--  子菜单项样式  -->
        <Style x:Key="SubMenuItemStyle" TargetType="Border">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="BorderBrush" Value="#E0E0E0" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="Padding" Value="25,8" />
            <Setter Property="Margin" Value="8,1" />
            <Setter Property="Cursor" Value="Hand" />
            <Setter Property="CornerRadius" Value="4" />
            <Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
            <Setter Property="RenderTransform">
                <Setter.Value>
                    <TransformGroup>
                        <TranslateTransform X="0" />
                        <ScaleTransform ScaleX="1" ScaleY="1" />
                    </TransformGroup>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsSelected}" Value="True">
                    <Setter Property="Background" Value="#43A047" />
                    <Setter Property="BorderBrush" Value="#4CAF50" />
                    <Setter Property="BorderThickness" Value="2" />
                </DataTrigger>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#E8F5E8" />
                    <Setter Property="BorderBrush" Value="#4CAF50" />
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].X"
                                    To="5"
                                    Duration="0:0:0.15" />
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="RenderTransform.Children[0].X"
                                    To="0"
                                    Duration="0:0:0.15" />
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.ExitActions>
                </Trigger>
            </Style.Triggers>
        </Style>

        <!--  子菜单按钮样式 - 最简版本,完全稳定  -->
        <Style x:Key="SubMenuButtonStyle" TargetType="Button">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="BorderBrush" Value="#E0E0E0" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="Padding" Value="25,8" />
            <Setter Property="Margin" Value="8,1" />
            <Setter Property="Cursor" Value="Hand" />
            <Setter Property="HorizontalContentAlignment" Value="Left" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border
                            Padding="{TemplateBinding Padding}"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="4">
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsSelected}" Value="True">
                    <Setter Property="Background" Value="#43A047" />
                    <Setter Property="BorderBrush" Value="#4CAF50" />
                    <Setter Property="Foreground" Value="White" />
                    <Setter Property="BorderThickness" Value="2" />
                </DataTrigger>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#E8F5E8" />
                    <Setter Property="BorderBrush" Value="#4CAF50" />
                </Trigger>
            </Style.Triggers>
        </Style>

        <!--  菜单项数据模板  -->
        <DataTemplate x:Key="MenuItemTemplate" DataType="{x:Type vm:MenuItemViewModel}">
            <StackPanel>
                <!--  主菜单项按钮  -->
                <Button Command="{Binding ExecuteMenuItemCommand}" Style="{StaticResource MenuButtonStyle}">
                    <StackPanel Orientation="Horizontal">
                        <!--  图标  -->
                        <Image
                            Width="24"
                            Height="24"
                            Margin="0,0,10,0"
                            Source="{Binding IconSource}" />

                        <!--  文本  -->
                        <TextBlock
                            VerticalAlignment="Center"
                            FontSize="{Binding FontSize}"
                            Text="{Binding Name}" />

                        <!--  展开指示器 - 简化版本  -->
                        <TextBlock
                            Margin="5,0,0,0"
                            VerticalAlignment="Center"
                            RenderTransformOrigin="0.5,0.5"
                            Text="▼"
                            Visibility="{Binding HasSubItems, Converter={StaticResource BooleanToVisibilityConverter}}">
                            <TextBlock.RenderTransform>
                                <RotateTransform Angle="0" />
                            </TextBlock.RenderTransform>
                            <TextBlock.Style>
                                <Style TargetType="TextBlock">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsExpanded}" Value="True">
                                            <DataTrigger.EnterActions>
                                                <BeginStoryboard>
                                                    <Storyboard>
                                                        <DoubleAnimation
                                                            Storyboard.TargetProperty="RenderTransform.Angle"
                                                            To="180"
                                                            Duration="0:0:0.2" />
                                                    </Storyboard>
                                                </BeginStoryboard>
                                            </DataTrigger.EnterActions>
                                            <DataTrigger.ExitActions>
                                                <BeginStoryboard>
                                                    <Storyboard>
                                                        <DoubleAnimation
                                                            Storyboard.TargetProperty="RenderTransform.Angle"
                                                            To="0"
                                                            Duration="0:0:0.2" />
                                                    </Storyboard>
                                                </BeginStoryboard>
                                            </DataTrigger.ExitActions>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </TextBlock.Style>
                        </TextBlock>
                    </StackPanel>
                </Button>
                <!--  子菜单项容器 - 简化版本  -->
                <StackPanel
                    Margin="15,0,0,0"
                    RenderTransformOrigin="0.5,0"
                    Visibility="{Binding IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
                    <StackPanel.RenderTransform>
                        <ScaleTransform ScaleY="1" />
                    </StackPanel.RenderTransform>
                    <StackPanel.Style>
                        <Style TargetType="StackPanel">
                            <Setter Property="Opacity" Value="0" />
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding IsExpanded}" Value="True">
                                    <DataTrigger.EnterActions>
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <DoubleAnimation
                                                    Storyboard.TargetProperty="Opacity"
                                                    To="1"
                                                    Duration="0:0:0.25" />
                                                <DoubleAnimation
                                                    Storyboard.TargetProperty="RenderTransform.ScaleY"
                                                    From="0"
                                                    To="1"
                                                    Duration="0:0:0.25" />
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </DataTrigger.EnterActions>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </StackPanel.Style>

                    <ItemsControl ItemsSource="{Binding SubItems}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <!--  改用Button统一处理点击事件,避免重复绑定  -->
                                <Button Command="{Binding ExecuteMenuItemCommand}" Style="{StaticResource SubMenuButtonStyle}">
                                    <StackPanel Orientation="Horizontal">
                                        <Image
                                            Width="20"
                                            Height="20"
                                            Margin="0,0,8,0"
                                            Source="{Binding IconSource}" />

                                        <TextBlock FontSize="{Binding FontSize}" Text="{Binding Name}" />
                                    </StackPanel>
                                </Button>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>

    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl
            Background="White"
            ItemTemplate="{StaticResource MenuItemTemplate}"
            ItemsSource="{Binding MenuItems}" />
    </ScrollViewer>
</UserControl>

DropMenu.cs

C#
using System;
using System.Windows;
using System.Windows.Controls;
using DropMenuControls.ViewModels;

namespace DropMenuControls
{
    /// <summary>
    /// 高级自定义下拉菜单控件
    /// 支持丰富的视觉效果、动画和交互体验
    /// 使用MVVM模式架构
    /// </summary>
    public partial class DropMenu : UserControl
    {
        public DropMenuViewModel ViewModel { get; private set; }

        // 依赖属性 - 菜单配置文件路径
        public static readonly DependencyProperty MenuConfigPathProperty =
            DependencyProperty.Register("MenuConfigPath", typeof(string), typeof(DropMenu),
                new PropertyMetadata("Menu.json", OnMenuConfigPathChanged));

        // 依赖属性 - 图标目录路径
        public static readonly DependencyProperty IconDirectoryProperty =
            DependencyProperty.Register("IconDirectory", typeof(string), typeof(DropMenu),
                new PropertyMetadata("Icons", OnIconDirectoryChanged));

        /// <summary>
        /// 菜单配置文件路径
        /// </summary>
        public string MenuConfigPath
        {
            get { return (string)GetValue(MenuConfigPathProperty); }
            set { SetValue(MenuConfigPathProperty, value); }
        }

        /// <summary>
        /// 图标目录路径
        /// </summary>
        public string IconDirectory
        {
            get { return (string)GetValue(IconDirectoryProperty); }
            set { SetValue(IconDirectoryProperty, value); }
        }

        /// <summary>
        /// 菜单项被点击事件
        /// </summary>
        public event EventHandler<MenuItemClickedEventArgs> MenuItemClicked;

        public DropMenu()
        {
            InitializeComponent();

            // 创建ViewModel并设置DataContext,传入配置路径
            ViewModel = new DropMenuViewModel(MenuConfigPath, IconDirectory);
            DataContext = ViewModel;

            // 订阅ViewModel的事件
            ViewModel.MenuItemClickedEvent += OnMenuItemClicked;
        }

        private void OnMenuItemClicked(MenuItemViewModel clickedItem)
        {
            // 触发控件的外部事件
            MenuItemClicked?.Invoke(this, new MenuItemClickedEventArgs(clickedItem));
        }

        // 依赖属性变化处理
        private static void OnMenuConfigPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is DropMenu control && e.NewValue is string newPath)
            {
                // 更新ViewModel的配置路径
                if (control.ViewModel != null)
                {
                    control.ViewModel.MenuConfigPath = newPath;
                    control.ViewModel.LoadMenuFromJson();
                }
            }
        }

        private static void OnIconDirectoryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is DropMenu control && e.NewValue is string newIconDirectory)
            {
                // 更新ViewModel的图标目录
                if (control.ViewModel != null)
                {
                    control.ViewModel.IconDirectory = newIconDirectory;
                    control.ViewModel.LoadMenuFromJson(); // 重新加载以应用新的图标路径
                }
            }
        }

        /// <summary>
        /// 公共方法:重新加载菜单
        /// </summary>
        public void ReloadMenu()
        {
            ViewModel?.LoadMenuFromJson();
        }

        /// <summary>
        /// 公共方法:清除所有选中状态
        /// </summary>
        public void ClearSelection()
        {
            if (ViewModel != null)
            {
                foreach (var item in ViewModel.MenuItems)
                {
                    item.ClearSelection();
                }
            }
        }
    }

    /// <summary>
    /// 菜单项点击事件参数
    /// </summary>
    public class MenuItemClickedEventArgs : EventArgs
    {
        public MenuItemViewModel ClickedItem { get; }
        public string Command { get; }
        public string ItemName { get; }

        public MenuItemClickedEventArgs(MenuItemViewModel clickedItem)
        {
            ClickedItem = clickedItem;
            Command = clickedItem?.Command;
            ItemName = clickedItem?.Name;
        }
    }
}

pachages.config

C#
1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net47" />
</packages>

在ZWCAD中使用

将菜单设定到PalletSet中

Commands.cs

C#
 public class Commands
 {
     internal static PaletteSet ps = null;
     [CommandMethod("TEST1")]
     public void TEST1()
     {
         if (ps == null)
         {
             ps = new PaletteSet("抽屉菜单");
             ps.Add("Menu", new PaletteBridge());
             ps.Visible = true;
             ps.Dock = ZwSoft.ZwCAD.Windows.DockSides.Left;
             if (ps.Size.Width < 200)
             {
                 ps.Size = new System.Drawing.Size(500, ps.Size.Height);
             }
         }
         else
             ps.Visible = !ps.Visible;
     }
     private static PaletteSet _palette;
     [CommandMethod("ShowDockWpf")]
     public void ShowDockWpf()
     {
         if (_palette == null)
         {
             _palette = new PaletteSet("我的 WPF Dock 面板")
             {
                 Style = PaletteSetStyles.ShowPropertiesMenu |
                     PaletteSetStyles.ShowAutoHideButton |
                     PaletteSetStyles.ShowCloseButton
             };

             _palette.Size = new System.Drawing.Size(300, 400);

             // WPF 控件
             var wpfCtrl = new WpfDropMenu();

             // 通过 ElementHost 封装
             var host = new ElementHost
             {
                 Dock = System.Windows.Forms.DockStyle.Fill,
                 Child = wpfCtrl
             };

             _palette.Add("工具面板", host);
         }

         _palette.Visible = true; // 显示
     }
 }

WpfDropMenu.xaml

C#
<UserControl
    x:Class="ZwObjectZrxNet3.Views.WpfDropMenu"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:DropMenuControls;assembly=DropMenuControls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:ZwObjectZrxNet3.Views"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <Grid>
        <controls:DropMenu x:Name="dropMenu" MenuItemClicked="CustomDropMenu_MenuItemClicked" />
    </Grid>
</UserControl>

WpfDropMenu.cs

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ZwSoft.ZwCAD.Windows.Data;
using ZwSoft.ZwCAD.ApplicationServices;
using System.Reflection;
using System.IO;
using Path = System.IO.Path;

namespace ZwObjectZrxNet3.Views
{
    /// <summary>
    /// WpfDropMenu.xaml 的交互逻辑
    /// </summary>
    public partial class WpfDropMenu : UserControl
    {
        public WpfDropMenu()
        {
            InitializeComponent();
            string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            dropMenu.IconDirectory = Path.Combine(path, "Icons");
            dropMenu.MenuConfigPath = Path.Combine(path, "Menu.json");
        }

        private void CustomDropMenu_MenuItemClicked(object sender, DropMenuControls.MenuItemClickedEventArgs args)
        {
            string itemName = args.ItemName ?? "未知项目";
            string command = args.Command ?? "无命令";
            using (DocumentLock loc = ZwSoft.ZwCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.LockDocument())
            {
                Document doc = ZwSoft.ZwCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
                if (!string.IsNullOrWhiteSpace(command) && command != "无命令")
                {
                    string testCommand = command;
                    //如果command没有/n 则加上
                    if (!command.StartsWith(" "))
                    {
                        testCommand = command + " ";
                    }

                    doc.SendStringToExecute(testCommand, false, false, false);
                    //MessageBox.Show($"执行命令: {command}",
                    //    "菜单项点击", MessageBoxButton.OK, MessageBoxImage.Information);
                }
            }
        }
    }
}

创建一个Winform用户控件做中间层转接WPF控件

PaletteBridge.cs

C#
namespace ZwObjectZrxNet3
{
    partial class PaletteBridge
    {
        /// <summary> 
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary> 
        /// 清理所有正在使用的资源。
        /// </summary>
        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region 组件设计器生成的代码

        /// <summary> 
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.elementHost1 = new System.Windows.Forms.Integration.ElementHost();
            this.wpfDropMenu1 = new Views.WpfDropMenu();
            this.SuspendLayout();
            // 
            // elementHost1
            // 
            this.elementHost1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.elementHost1.Location = new System.Drawing.Point(0, 0);
            this.elementHost1.Name = "elementHost1";
            this.elementHost1.Size = new System.Drawing.Size(150, 150);
            this.elementHost1.TabIndex = 0;
            this.elementHost1.Text = "elementHost1";
            this.elementHost1.Child = wpfDropMenu1;
            // 
            // PallateBridge
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.elementHost1);
            this.Name = "PallateBridge";
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Integration.ElementHost elementHost1;
        private Views.WpfDropMenu wpfDropMenu1;
    }
}

image-20250901163639624

Menu.json

JSON
{
    "ItemMenu": [
        {
            "Name":"Home",
            "Icon":"Home.png",
            "FontSize":16,
            "Command": "Home",
            "SubItems":[]
        },
        {
            "Name": "Customer",
            "Icon": "BootstrapIcons-_1Circle.png",
            "FontSize":16,
            "SubItems": [
                {
                    "Name": "Customer1",
                    "Icon": "notification 22.png",
                    "FontSize":16,
                    "Command": "Test1"
                },
                {
                    "Name": "Customer2",
                    "Icon": "ET 28.png",
                    "FontSize":16,
                    "Command": "Test2"
                },
                {
                    "Name": "Customer3",
                    "Icon": "email 33.png",
                    "FontSize":16,
                    "Command": "Test3"
                },
                {
                    "Name": "Customer4",
                    "Icon": "edit 13.png",
                    "FontSize":16,
                    "Command": "Test4"
                },
                {
                    "Name": "Customer5",
                    "Icon": "credits 6.png",
                    "FontSize":16,
                    "Command": "Test5"
                }
            ]
        },
        {
            "Name": "Providers",
            "Icon": "BootstrapIcons-_2Circle.png",
            "FontSize":16,
            "SubItems":[
                {
                    "Name": "Provider1",
                    "Icon": "phonecall 24.png",
                    "FontSize":16,
                    "Command": "Test1"
                },
                {
                    "Name": "Provider2",
                    "Icon": "cart 11.png",
                    "FontSize":16,
                    "Command": "Test2"
                },
                {
                    "Name": "Provider3",
                    "Icon": "calculator 31.png",
                    "FontSize":16,
                    "Command": "Test3"
                }
            ]
        },
        {
            "Name": "Others",
            "Icon": "BootstrapIcons-_3Circle.png",
            "FontSize":16,
            "SubItems":[
                {
                    "Name": "Other1",
                    "Icon": "social_server_fault 81.png",
                    "FontSize":16,
                    "Command": "Test1"
                },
                {
                    "Name": "Other2",
                    "Icon": "check 43.png",
                    "FontSize":16,
                    "Command": "Test2"
                }
            ]
        }
    ]
}

Vs自定义项目模板

image-20250415192756364

先配置好项目相关文件

导出项目相关文件

image-20250415192839962

导出为项目

image-20250415193017914

image-20250415193036748

image-20250415193112362

取消,需要对压缩文件进行处理

处理压缩包

image-20250415193410753

修改数据

img

修改完成后将压缩包放置到Visual Studio 2022\Templates\ProjectTemplates

Text Only
Visual Studio 2022\Templates\ProjectTemplates

image-20250415193604984

Text Only
MyTemplate.vstemplate
<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
  <TemplateData>
    <Name>ZwObjectZrxNet</Name>
    <Description>ObjectZRX+WPF模板</Description>
    <ProjectType>CSharp</ProjectType>
    <ProjectSubType>
    </ProjectSubType>
    <SortOrder>1000</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>ZwObjectZrxNet</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <LocationField>Enabled</LocationField>
    <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
    <Icon>__TemplateIcon.ico</Icon>
    <PreviewImage>__PreviewImage.ico</PreviewImage>
    <LanguageTag>CSharp</LanguageTag>
    <PlatformTag>Windows</PlatformTag>
    <ProjectTypeTag>Library</ProjectTypeTag>
  </TemplateData>
  <TemplateContent>
    <Project TargetFileName="$safeprojectname$.csproj" File="$safeprojectname$.csproj" ReplaceParameters="true">
      <ProjectItem ReplaceParameters="true" TargetFileName="app.config">app.config</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="Commands.cs">Commands.cs</ProjectItem>
      <Folder Name="Controls" TargetFolderName="Controls" />
      <Folder Name="Converters" TargetFolderName="Converters">
        <ProjectItem ReplaceParameters="true" TargetFileName="OptionToBooleanConverter.cs">OptionToBooleanConverter.cs</ProjectItem>
      </Folder>
      <Folder Name="Models" TargetFolderName="Models" />
      <ProjectItem ReplaceParameters="true" TargetFileName="packages.config">packages.config</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="PlugInApplication.cs">PlugInApplication.cs</ProjectItem>
      <Folder Name="Properties" TargetFolderName="Properties">
        <ProjectItem ReplaceParameters="true" TargetFileName="AssemblyInfo.cs">AssemblyInfo.cs</ProjectItem>
      </Folder>
      <Folder Name="Styles" TargetFolderName="Styles">
        <ProjectItem ReplaceParameters="true" TargetFileName="Styles.xaml">Styles.xaml</ProjectItem>
      </Folder>
      <Folder Name="ViewModel" TargetFolderName="ViewModel">
        <ProjectItem ReplaceParameters="true" TargetFileName="WpfDemoViewModel.cs">WpfDemoViewModel.cs</ProjectItem>
      </Folder>
      <Folder Name="Views" TargetFolderName="Views">
        <ProjectItem ReplaceParameters="true" TargetFileName="WpfDemo.xaml">WpfDemo.xaml</ProjectItem>
        <ProjectItem ReplaceParameters="true" TargetFileName="WpfDemo.xaml.cs">WpfDemo.xaml.cs</ProjectItem>
      </Folder>
    </Project>
  </TemplateContent>
</VSTemplate>

Csharp

Text Only
$safeprojectname$.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{$guid1$}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>$safeprojectname$</RootNamespace>
    <AssemblyName>$safeprojectname$</AssemblyName>
    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
    <NuGetPackageImportStamp>
    </NuGetPackageImportStamp>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>..\..\Out\Debug\bin\x64\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>..\..\Out\Release\bin\x64\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="CommunityToolkit.Mvvm, Version=8.4.0.0, Culture=neutral, PublicKeyToken=4aff67a105548ee2, processorArchitecture=MSIL">
      <HintPath>..\packages\CommunityToolkit.Mvvm.8.4.0\lib\netstandard2.0\CommunityToolkit.Mvvm.dll</HintPath>
    </Reference>
    <Reference Include="MaterialDesignColors, Version=5.2.1.0, Culture=neutral, PublicKeyToken=df2a72020bd7962a, processorArchitecture=MSIL">
      <HintPath>..\packages\MaterialDesignColors.5.2.1\lib\net462\MaterialDesignColors.dll</HintPath>
    </Reference>
    <Reference Include="MaterialDesignThemes.Wpf, Version=5.2.1.0, Culture=neutral, PublicKeyToken=df2a72020bd7962a, processorArchitecture=MSIL">
      <HintPath>..\packages\MaterialDesignThemes.5.2.1\lib\net462\MaterialDesignThemes.Wpf.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.Xaml.Behaviors, Version=1.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.Xaml.Behaviors.Wpf.1.1.39\lib\net45\Microsoft.Xaml.Behaviors.dll</HintPath>
    </Reference>
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
    <Reference Include="System.Buffers, Version=4.0.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Buffers.4.6.0\lib\net462\System.Buffers.dll</HintPath>
    </Reference>
    <Reference Include="System.ComponentModel.Annotations, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\packages\System.ComponentModel.Annotations.5.0.0\lib\net461\System.ComponentModel.Annotations.dll</HintPath>
    </Reference>
    <Reference Include="System.ComponentModel.DataAnnotations" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Memory, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Memory.4.6.0\lib\net462\System.Memory.dll</HintPath>
    </Reference>
    <Reference Include="System.Numerics" />
    <Reference Include="System.Numerics.Vectors, Version=4.1.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Numerics.Vectors.4.6.0\lib\net462\System.Numerics.Vectors.dll</HintPath>
    </Reference>
    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.0\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
    </Reference>
    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
    </Reference>
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xaml" />
    <Reference Include="WindowsBase" />
    <Reference Include="WindowsFormsIntegration" />
    <Reference Include="ZcCui">
      <HintPath>$(ZrxSdk2025)\inc\ZcCui.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZcWindows">
      <HintPath>$(ZrxSdk2025)\inc\ZcWindows.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZdWindows">
      <HintPath>$(ZrxSdk2025)\inc\ZdWindows.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZwDatabaseMgd">
      <HintPath>$(ZrxSdk2025)\inc\ZwDatabaseMgd.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZwDatabaseMgdBrep">
      <HintPath>$(ZrxSdk2025)\inc\ZwDatabaseMgdBrep.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZwManaged">
      <HintPath>$(ZrxSdk2025)\inc\ZwManaged.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Commands.cs" />
    <Compile Include="Converters\OptionToBooleanConverter.cs" />
    <Compile Include="PlugInApplication.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="ViewModel\WpfDemoViewModel.cs" />
    <Compile Include="Views\WpfDemo.xaml.cs">
      <DependentUpon>WpfDemo.xaml</DependentUpon>
    </Compile>
  </ItemGroup>
  <ItemGroup>
    <Folder Include="Controls\" />
    <Folder Include="Models\" />
  </ItemGroup>
  <ItemGroup>
    <Page Include="Styles\Styles.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="Views\WpfDemo.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </ItemGroup>
  <ItemGroup>
    <None Include="app.config" />
    <None Include="packages.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="..\packages\MaterialDesignThemes.5.2.1\build\MaterialDesignThemes.targets" Condition="Exists('..\packages\MaterialDesignThemes.5.2.1\build\MaterialDesignThemes.targets')" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\packages\MaterialDesignThemes.5.2.1\build\MaterialDesignThemes.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MaterialDesignThemes.5.2.1\build\MaterialDesignThemes.targets'))" />
    <Error Condition="!Exists('..\packages\CommunityToolkit.Mvvm.8.4.0\build\CommunityToolkit.Mvvm.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CommunityToolkit.Mvvm.8.4.0\build\CommunityToolkit.Mvvm.targets'))" />
  </Target>
  <Import Project="..\packages\CommunityToolkit.Mvvm.8.4.0\build\CommunityToolkit.Mvvm.targets" Condition="Exists('..\packages\CommunityToolkit.Mvvm.8.4.0\build\CommunityToolkit.Mvvm.targets')" />
</Project>

Csharp

image-20250416092345724

模版参数

.Net封装ObjectARX自定义实体类型

参考Github ManagedCircle

使用ARX自带的向导

选择.NET mixed managed code support工程

将props中关于arx的删除

配置信息按照如下所示:

image-20250407193241272

image-20250407193257438

image-20250407193310752

image-20250407193323409

image-20250407193335187

关于主代码

image-20250407193517842

acrxEntryPoint.cpp

C++
//-----------------------------------------------------------------------------
//----- acrxEntryPoint.cpp
//-----------------------------------------------------------------------------
#include "StdAfx.h"
#include "resource.h"
#include "ZwSerialNoMgd.h"

//-----------------------------------------------------------------------------
#define szRDS _RXST("")
AC_DECLARE_EXTENSION_MODULE(MgdWrapperDLL) ;
static AcMgObjectFactoryBase** g_PEs = NULL;

//-----------------------------------------------------------------------------
//----- ObjectARX EntryPoint
class CMgdWrapperApp : public AcRxArxApp {

public:
    CMgdWrapperApp() : AcRxArxApp() {}

    virtual AcRx::AppRetCode On_kInitAppMsg(void *pkt) {
        // TODO: Load dependencies here
        // Save critical data pointers before running the constructors (see afxdllx.h for details)
        AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
        pModuleState->m_pClassInit = pModuleState->m_classList;
        pModuleState->m_pFactoryInit = pModuleState->m_factoryList;
        pModuleState->m_classList.m_pHead = NULL;
        pModuleState->m_factoryList.m_pHead = NULL;

        MgdWrapperDLL.AttachInstance (_hdllInstance) ;
        InitAcUiDLL () ;

        // You *must* call On_kInitAppMsg here
        AcRx::AppRetCode retCode = AcRxArxApp::On_kInitAppMsg(pkt);

        // TODO: Add your initialization code here
        AcMgObjectFactoryBase* PEs[] =
        {
            new AcMgObjectFactory<ZDN::CustomWrapper::ZwSerialNoMgd, CZwSerialNo>(),
                    NULL
        };

        g_PEs = PEs;
        return (retCode);
    }

    virtual AcRx::AppRetCode On_kUnloadAppMsg(void *pkt) {
        // TODO: Add your code here
        int i = 0;
        while (g_PEs[i] != NULL)
            delete g_PEs[i++];
        // You *must* call On_kUnloadAppMsg here
        AcRx::AppRetCode retCode = AcRxArxApp::On_kUnloadAppMsg(pkt);

        // TODO: Unload dependencies here
        MgdWrapperDLL.DetachInstance () ;

        return (retCode);
    }

    virtual void RegisterServerComponents() {
    }
};

//-----------------------------------------------------------------------------
IMPLEMENT_ARX_ENTRYPOINT(CMgdWrapperApp)

image-20250407193555427

stdafx.h

C++
#pragma once
#define MGDWRAPPER_MODULE

#pragma pack (push, 8)
#pragma warning(disable: 4786 4996)
//#pragma warning(disable: 4098)

//-----------------------------------------------------------------------------
#define STRICT

#include <sdkddkver.h>

//- ObjectARX and OMF headers needs this
#include <map>

//-----------------------------------------------------------------------------
#include <afxwin.h>             //- MFC core and standard components
#include <afxext.h>             //- MFC extensions
#include <afxcmn.h>             //- MFC support for Windows Common Controls

//-----------------------------------------------------------------------------
#using <mscorlib.dll>
#using <System.dll>

//#using <acdbmgd.dll>
//#using <acmgd.dll>
//#using <AcCui.dll>

#include <vcclr.h>
//-----------------------------------------------------------------------------
#include <afxwin.h>             //- MFC core and standard components
#include <afxext.h>             //- MFC extensions

#ifndef _AFX_NO_OLE_SUPPORT
#include <afxole.h>             //- MFC OLE classes
#include <afxodlgs.h>           //- MFC OLE dialog classes
#include <afxdisp.h>            //- MFC Automation classes
#endif // _AFX_NO_OLE_SUPPORT

#ifndef _AFX_NO_DB_SUPPORT
#include <afxdb.h>              //- MFC ODBC database classes
#endif // _AFX_NO_DB_SUPPORT

#ifndef _AFX_NO_DAO_SUPPORT
#include <afxdao.h>             //- MFC DAO database classes
#endif // _AFX_NO_DAO_SUPPORT

#include <afxdtctl.h>           //- MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>             //- MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
//-----------------------------------------------------------------------------
//- Include ObjectDBX/ObjectARX headers
//- Uncomment one of the following lines to bring a given library in your project.
//#define _BREP_SUPPORT_                    //- Support for the BRep API
//#define _HLR_SUPPORT_                     //- Support for the Hidden Line Removal API
//#define _AMODELER_SUPPORT_                //- Support for the AModeler API
//#define _ASE_SUPPORT_                         //- Support for the ASI/ASE API
//#define _RENDER_SUPPORT_                  //- Support for the AutoCAD Render API
//#define _ARX_CUSTOM_DRAG_N_DROP_  //- Support for the ObjectARX Drag'n Drop API
//#define _INC_LEAGACY_HEADERS_         //- Include legacy headers in this project
#include "arxHeaders.h"

#include <afxcview.h>
//-----------------------------------------------------------------------------
#include "DocData.h" //- Your document specific data class holder

//- Declare it as an extern here so that it becomes available in all modules
extern AcApDataManager<CDocData> DocVars ;

#pragma pack (pop)

image-20250407193816704

ZwSerialNoMgd.h

C++
#pragma once
#include <CZwSerialNo.h>
using namespace System ;
using namespace ZwSoft::ZwCAD::Geometry ;
using namespace  ZwSoft::ZwCAD::DatabaseServices ;
namespace ZDN
{
    namespace CustomWrapper
    {
        [ZwSoft::ZwCAD::Runtime::Wrapper("CZwSerialNo")] 
        public ref class ZwSerialNoMgd
            : public ZwSoft::ZwCAD::DatabaseServices::Entity 
        {
         public:
          //- Constructor
          ZwSerialNoMgd();

        internal:

          ZwSerialNoMgd(System::IntPtr unmanagedPointer, bool bAutoDelete);

          //- Returns the unmanaged ARX Object
          inline CZwSerialNo *GetImpObj() {
            return (static_cast<CZwSerialNo *>(UnmanagedObject.ToPointer()));
          }

         public:
          void SetInsertPt(ZwSoft::ZwCAD::Geometry::Point3d center);
          void setScale(ZwSoft::ZwCAD::Geometry::Scale3d newScale);
          void setDescription(String ^ strDescription);
          void setTag(String ^ strDescription);
          void setValue(String ^ strDescription);
          void setGaugename(String ^ strDescription);
          void setGaugecode(String ^ strDescription);

         /* void setRadius(double radius);
          void gc2AcString(System::String ^ s);*/
          void convertFromMgdObjectId(ZwSoft::ZwCAD::DatabaseServices::ObjectId mgdId);
        };
    }

} // namespace ZDN

image-20250407193859988

ZwSerialNoMgd.cpp

C++
#include "StdAfx.h"
#include "ZwSerialNoMgd.h"
#include <gcroot.h>
#include "zmgdinterop.h"

ZDN::CustomWrapper::ZwSerialNoMgd::ZwSerialNoMgd() :
    ZwSoft::ZwCAD::DatabaseServices::Entity((System::IntPtr) new CZwSerialNo(), true)
{}
ZDN::CustomWrapper::ZwSerialNoMgd::ZwSerialNoMgd(System::IntPtr unmanagedPointer, bool bAutoDelete) :
    ZwSoft::ZwCAD::DatabaseServices::Entity(unmanagedPointer, bAutoDelete)
{}
void ZDN::CustomWrapper::ZwSerialNoMgd::SetInsertPt(ZwSoft::ZwCAD::Geometry::Point3d center)
{
    AcGePoint3d _center = GETPOINT3D(center);

    GetImpObj()->SetInsertPt(_center);
}

void ZDN::CustomWrapper::ZwSerialNoMgd::setScale(ZwSoft::ZwCAD::Geometry::Scale3d newScale)
{
    AcGeScale3d _center = GETSCALE3D(newScale);

    GetImpObj()->setScale(_center);
}

void ZDN::CustomWrapper::ZwSerialNoMgd::setDescription(String ^ strDescription)
{
    GetImpObj()->setDescription((LPCTSTR)StringToCIF(strDescription));
}

void ZDN::CustomWrapper::ZwSerialNoMgd::setTag(String ^ strDescription)
{
    GetImpObj()->setTag((LPCTSTR)StringToCIF(strDescription));
}

void ZDN::CustomWrapper::ZwSerialNoMgd::setValue(String ^ strDescription)
{
    GetImpObj()->setValue((LPCTSTR)StringToCIF(strDescription));
}

void ZDN::CustomWrapper::ZwSerialNoMgd::setGaugename(String ^ strDescription)
{
    GetImpObj()->setGaugename((LPCTSTR)StringToCIF(strDescription));
}

void ZDN::CustomWrapper::ZwSerialNoMgd::setGaugecode(String ^ strDescription)
{
    GetImpObj()->setGaugecode((LPCTSTR)StringToCIF(strDescription));
}

void ZDN::CustomWrapper::ZwSerialNoMgd::convertFromMgdObjectId(
    ZwSoft::ZwCAD::DatabaseServices::ObjectId mgdId)
{
    System::String ^ hStr = mgdId.Handle.ToString();
    UInt64     hInt = System::Convert::ToInt64(hStr, 16);
    AcDbHandle handle(hInt);

    AcDbObjectId      objId;
    Acad::ErrorStatus es =
        acdbHostApplicationServices()->workingDatabase()->getAcDbObjectId(objId, false, handle);
    if (es == Acad::eOk)
    {
        acutPrintf(ACRX_T("\nObjectClass from Id : %s"), objId.objectClass()->name());
    }
}

image-20250407193924510

.net中调用

C#
 [CommandMethod("TestNo")]
 public void TestNo()
 {
     Document doc = Application.DocumentManager.MdiActiveDocument;
     Database db = doc.Database;
     Editor ed = doc.Editor;

     PromptEntityOptions peo = new PromptEntityOptions("\nSelect custom object: ");
     peo.SetRejectMessage("\nInvalid selection...");
     peo.AddAllowedClass(typeof(ZwSerialNoMgd), true);

     PromptEntityResult per = ed.GetEntity(peo);

     if (per.Status != PromptStatus.OK)
         return;

     using (Transaction Tx = db.TransactionManager.StartTransaction())
     {
         ZwSerialNoMgd entity = Tx.GetObject(per.ObjectId, OpenMode.ForWrite)
             as ZwSerialNoMgd;

         entity.ColorIndex = 1;

         Tx.Commit();
     }
 }

启动ZW机械CAD

C#
private ZWCAD.ZcadApplication m_cadApp;
private ZwmToolKitLib.ZwmApp m_objZwmApp;
private ZwmToolKitLib.ZwmDb m_objZwmDb;
private ZwmToolKitLib.Title m_objZwmTitle;
private ZwmToolKitLib.Bom m_objZwmBom;
private ZwmToolKitLib.Frame m_objZwmFrame;

try
{
    //取得一个正在运行的ZWCAD实例 
    m_cadApp = (ZWCAD.ZcadApplication)Marshal.GetActiveObject("ZWCAD.Application.2025");

}//end of try 
catch
{
    try
    {
        //建立一个新的AUTOCAD实例,并标识已经建立成功。 
        //        m_cadApp = new AutoCAD.AcadApplication();
        //m_cadApp = new ZWCAD.ZcadApplication();
        Type comType = Type.GetTypeFromProgID("ZWCAD.Application.2025");
        object comObj = Activator.CreateInstance(comType);
        m_cadApp = (ZWCAD.ZcadApplication)comObj;
        m_cadApp.Visible = true;
    }
    catch
    {
        throw new Exception("无法起动CAD应用程序,确认已经安装");
    }
}//end of catch 
 if (m_cadApp != null && m_objZwmApp == null)
 {
     m_objZwmApp = m_cadApp.GetInterfaceObject("ZwmToolKit.ZwmApp");
     m_objZwmApp.GetDb(out m_objZwmDb);
     if (m_objZwmApp != null)
     {
         String strCadPath = "";
         String strZwmPath = "";
         String strVersion = "";

         m_objZwmApp.GetCadPath(out strCadPath);
         m_objZwmApp.GetZwmPath(out strZwmPath);
         m_objZwmApp.GetVersion(out strVersion);

         textBox_cad_path.Text = strCadPath;
         textBox_zwm_path.Text = strZwmPath;
         textBox_version.Text = strVersion;
     }
     else
         MessageBox.Show("创建 ZwmToolKit.ZwmApp 失败!请检查机械软件启动是否正常");
 }

image-20250604093310804