在插件开发过程中,随着功能越来越多,用户找寻功能入口将变得越来越困难,在Excel催化剂 ,将采用遍历所有功能的方式,让用户可以轻松使用简单的查找功能找到想要功能所在位置,查找的范围有:功能按钮的显示名称、功能说明等。

按钮功能查找关键词文本来源

这个遍历功能区按钮属性,不确定在xml功能区中能否实现,在设计器功能区,因其已经被强类型为一个类对象,用反射技术可以将这个Ribbon类下所有的控件及属性给遍历出来。详细可参考功能第78波说明。

功能区效果

同样地,自定义函数也可以用遍历的方式,列出所有自定义函数。前提是自定义函数是ExcelDna框架开发的。

自定义函数效果

代码实现

给用户一个按钮,点击后遍历到工作表中。

        private void btnShowFeatures_Click(object sender, RibbonControlEventArgs e) { try { Common.ExcelApp.ScreenUpdating = false; Common.ExcelApp.DisplayAlerts = false; RibbonMenu ribbonMenu = sender as RibbonMenu; GroupVisibleSetting groupVisibleSetting = new GroupVisibleSetting() { CurrentRibbon = this }; groupVisibleSetting.ListTabFunctionInfo(); //遍历自定义函数清单 UdfListInfo.ListUdfInfo(); } catch (Exception ex) { Common.OutMsgError(ex); } finally { Common.ExcelApp.ScreenUpdating = true; Common.ExcelApp.DisplayAlerts = true; } } 

做了个类,用于访问功能区的信息,里面用了反射技术,因笔者水平也是比较菜,是笔者师傅给予帮助下实现的,详细技术要点也讲不清,有兴趣深入的可以自行百度学习。

using Microsoft.Office.Tools.Excel;
using Microsoft.Office.Tools.Ribbon;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using Excel = Microsoft.Office.Interop.Excel;
namespace Excel催化剂
{class GroupVisibleSetting{public Ribbon1 CurrentRibbon { get; set; }public RibbonMenu MenuSwitch { get; set; }public void ListTabFunctionInfo() { DataTable dt = new DataTable(); dt.Columns.Add("一级分组"); dt.Columns.Add("二级分组"); dt.Columns.Add("三级分组"); dt.Columns.Add("功能名称"); dt.Columns.Add("功能说明"); foreach (var grp in CurrentRibbon.Tabs.Cast<RibbonTab>().FirstOrDefault(s => s.Name == "excelCatalyzer").Groups) { foreach (var grpItem in grp.Items) { if (grpItem is RibbonButton) { var btn = grpItem as RibbonButton; DataRow dr = dt.NewRow(); dr["一级分组"] = grp.Label; dr["二级分组"] = grp.Label; dr["三级分组"] = grp.Label; dr["功能名称"] = btn.Label; dr["功能说明"] = btn.SuperTip; dt.Rows.Add(dr); } else if (grpItem is RibbonMenu) { var grpItemMenu = grpItem as RibbonMenu; MenuItemsLoad(grpItemMenu); foreach (var grpItemMenuItem in grpItemMenu.Items) { if (grpItemMenuItem is RibbonButton) { var btn = grpItemMenuItem as RibbonButton; DataRow dr = dt.NewRow(); dr["一级分组"] = grp.Label; dr["二级分组"] = grpItemMenu.Label; dr["三级分组"] = grpItemMenu.Label; dr["功能名称"] = btn.Label; dr["功能说明"] = btn.SuperTip; dt.Rows.Add(dr); } else if (grpItemMenuItem is RibbonSeparator) { var btn = grpItemMenuItem as RibbonSeparator; DataRow dr = dt.NewRow(); dr["一级分组"] = grp.Label; dr["二级分组"] = grpItemMenu.Label; dr["三级分组"] = grpItemMenu.Label; dr["功能名称"] = btn.Title; dr["功能说明"] = "分隔符,组合以下内容"; dt.Rows.Add(dr); } else if (grpItemMenuItem is RibbonGallery) { var itemGal = grpItemMenuItem as RibbonGallery; DataRow dr = dt.NewRow(); dr["一级分组"] = grp.Label; dr["二级分组"] = grpItemMenu.Label; dr["三级分组"] = grpItemMenu.Label; dr["功能名称"] = itemGal.Label; dr["功能说明"] = itemGal.SuperTip; dt.Rows.Add(dr); } else if (grpItemMenuItem is RibbonMenu) { var itemMenu = grpItemMenuItem as RibbonMenu; foreach (var itemMenuItem in itemMenu.Items) { DataRow dr = dt.NewRow(); dr["一级分组"] = grp.Label; dr["二级分组"] = grpItemMenu.Label; dr["三级分组"] = itemMenu.Label; if (itemMenuItem is RibbonButton) { var btn = itemMenuItem as RibbonButton; dr["功能名称"] = btn.Label; dr["功能说明"] = btn.SuperTip; } else if (itemMenuItem is RibbonSeparator) { var btn = itemMenuItem as RibbonSeparator; dr["功能名称"] = btn.Title; dr["功能说明"] = "分隔符,组合以下内容"; } dt.Rows.Add(dr); } } } } else if (grpItem is RibbonToggleButton) { var tglBtn = grpItem as RibbonToggleButton; DataRow dr = dt.NewRow(); dr["一级分组"] = grp.Label; dr["二级分组"] = grp.Label; dr["三级分组"] = grp.Label; dr["功能名称"] = tglBtn.Label; dr["功能说明"] = tglBtn.SuperTip; dt.Rows.Add(dr); } else if (grpItem is RibbonGallery) { var grpGal = grpItem as RibbonGallery; foreach (var grpGalItem in grpGal.Items) { DataRow dr = dt.NewRow(); dr["一级分组"] = grp.Label; dr["二级分组"] = grp.Label; dr["三级分组"] = grp.Label; dr["功能名称"] = grpGalItem.Label; dr["功能说明"] = grpGalItem.SuperTip; dt.Rows.Add(dr); } } } } ListObject listObject = Common.CreateNewVSTOListObject("功能清单",false); Common.OutputDataToListObject(listObject, dt, false); } private void MenuItemsLoad(RibbonMenu grpItemMenu) { if (grpItemMenu.Name == "menuNumberFormatSetting") { CurrentRibbon.menuNumberFormatSetting_ItemsLoading(grpItemMenu, null); var panel = Utilities.TaskPanelUtility.GetCusTomTaskPaneByTitle(Utilities.TaskPanelUtility.NumberFormatPanelTitle); panel.Visible = false; } else if (grpItemMenu.Name == "menuVisualData") { CurrentRibbon.menuVisualData_ItemsLoading(grpItemMenu, null); var panel = Utilities.TaskPanelUtility.GetCusTomTaskPaneByTitle(Utilities.TaskPanelUtility.DataVisualPanelTitle); panel.Visible = false; } else if (grpItemMenu.Name == "menuInsertPicture") { CurrentRibbon.menuInsertPicture_ItemsLoading(grpItemMenu, null); var panel = Utilities.TaskPanelUtility.GetCusTomTaskPaneByTitle(Utilities.TaskPanelUtility.BatchPicInsertPanelTitle); panel.Visible = false; } else if (grpItemMenu.Name == "menuBarCode") { CurrentRibbon.menuBarCode_ItemsLoading(grpItemMenu, null); var panel = Utilities.TaskPanelUtility.GetCusTomTaskPaneByTitle(Utilities.TaskPanelUtility.QrCodePanelTitle); panel.Visible = false; } } public void GrpVisibleSetting() { var grpInfos = GetGrpInfosSetting(); foreach (var item in grpInfos) { var grp = Globals.Ribbons.Ribbon1.GetGrpByGroupName(item.GrpName); if (grp != null) { grp.Visible = item.GrpVisible; } } } public void menuSwitch_ItemsLoading() { this.MenuSwitch.Items.Clear(); //从反射中得到grp的集合 RibbonGroup[] grpfields = GetGrpFields(); //从setting里提取grp信息 var grpInfosSetting = GetGrpInfosSetting(); foreach (var item in grpfields) { var grpInfo = grpInfosSetting.FirstOrDefault(s => s.GrpName == item.Name); RibbonCheckBox control = Globals.Ribbons.Ribbon1.Factory.CreateRibbonCheckBox(); control.Name = "btn" + item.Name; control.Label = item.Label; control.Checked = grpInfo.GrpName != null ? grpInfo.GrpVisible : true; //当setting信息里有保存的话,用setting的信息显示关闭与否,否则用true control.Click += Control_Click; this.MenuSwitch.Items.Add(control); } } private void Control_Click(object sender, RibbonControlEventArgs e) { RibbonCheckBox checkBox = sender as RibbonCheckBox; string grpName = checkBox.Name.Substring(3);//去除btn字符 RibbonGroup grp = Globals.Ribbons.Ribbon1.GetGrpByGroupName(grpName); grp.Visible = checkBox.Checked; SaveSwithSetting(); } private void SaveSwithSetting() { List<string> grpInfos = new List<string>(); foreach (RibbonCheckBox item in this.MenuSwitch.Items) { grpInfos.Add(item.Name.Substring(3) + "," + item.Label + "," + item.Checked.ToString()); } Properties.Settings.Default.SwitchSetting = string.Join(";\n", grpInfos); Properties.Settings.Default.Save(); } /// <summary> /// 反射的方式获得组的对象 /// </summary> /// <returns></returns> private RibbonGroup[] GetGrpFields() { BindingFlags bf = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField; Type t = typeof(Ribbon1); return t.GetFields(bf) .Where(s => s.Name.StartsWith("grp") && s.Name != "grpSwitch" && s.Name != "grpAbout") .Select(s => Globals.Ribbons.Ribbon1.GetGrpByGroupName(s.Name)) .ToArray(); } private (string GrpName, string GrpCaption, bool GrpVisible)[] GetGrpInfosSetting() { string switchSettingString = Properties.Settings.Default.SwitchSetting; return switchSettingString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => ( GrpName: s.Split(',')[0].Trim(new char[] { '\r', '\n', '\0' }), GrpCaption: s.Split(',')[1].Trim(new char[] { '\r', '\n', '\0' }), GrpVisible: bool.Parse(s.Split(',')[2].Trim(new char[] { '\r', '\n', '\0' })) ) ).ToArray(); } } } 

同样地在自定义函数方面,通过在自定义函数里作了一个自定义函数,专门用于遍历自定义函数的属性,已经开源在自定义函数项目中。

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq; using System.Text; using Excel = Microsoft.Office.Interop.Excel; using Microsoft.Office.Tools.Excel; namespace Excel催化剂 { class UdfListInfo { private static string shtName = "功能清单"; internal static void ListUdfInfo() { try { Excel.Worksheet sht = Common.ExcelApp.ActiveWorkbook.Worksheets[shtName]; DataTable dt = new DataTable(); dt.Columns.Add("函数类别"); dt.Columns.Add("函数名称"); dt.Columns.Add("函数注释"); var result = Common.ExcelApp.Run("ListUdfDesc"); if (result is Array) { var arrResult = Utilities.ArrayUtility.ConvertExcelArrayToDotNetArray(result as Array); foreach (var item in arrResult) { var strSplits = item.ToString().Split(new string[] { "#|#" }, StringSplitOptions.RemoveEmptyEntries); if (strSplits.Length == 3) { DataRow dr = dt.NewRow(); dr[0] = strSplits[0]; dr[1] = strSplits[1]; dr[2] = strSplits[2]; dt.Rows.Add(dr); } } Excel.Range listRange = sht.ListObjects[shtName].Range; Excel.Range firstCell = listRange.Offset[0, listRange.Columns.Count + 2].Cells[1, 1]; Worksheet vstoSht = Globals.Factory.GetVstoObject(sht); var listObject = vstoSht.Controls.AddListObject(firstCell, "自定义函数清单"); Common.OutputDataToListObject(listObject, dt, false); } } catch (Exception) { } } } } 

上述代码中,在VSTO项目里,仅需用Application.Run就可以访问到xll里的自定义函数ListUdfDesc,同时有个小要点是自定义函数返回的数组,下标是从1开始的Excel特有的,和.net的0为下标的不一样,需要作下转换。
var arrResult = Utilities.ArrayUtility.ConvertExcelArrayToDotNetArray(result as Array);

 public static object ListUdfDesc() { List<string> list = new List<string>(); var funcs = ExcelRegistration.GetExcelFunctions(); foreach (var funcInfo in funcs) { string name = funcInfo.FunctionAttribute.Name; string desc = funcInfo.FunctionAttribute.Description.Replace("Excel催化剂出品,必属精品!", ""); string catalog = funcInfo.FunctionAttribute.Category; if (!string.IsNullOrEmpty( catalog)) { list.Add($"{catalog}#|#{name}#|#{desc}"); } } if (list.Count>0) { return list.ToArray(); } else { return string.Empty; } } 
        public static object[] ConvertExcelArrayToDotNetArray(Array arr){int lb = arr.GetLowerBound(0); var ret = new object[arr.GetUpperBound(0) - lb + 1]; for (int ix = 0; ix < ret.Length; ++ix) { ret[ix] = arr.GetValue(ix + lb); } return ret; } 

结语

使用遍历的方式,让用户可以更轻松地查找相应功能,将极大地提升查找功能的友好度,本篇用到反射技术,比较高级的技术,若代码不甚明白,可自行进行相关知识点的学习补充。

再一次验证了VSTO给一般开发者带来的便利性,特别是在设计器功能区上,带来了强类型的Ribbon类,可轻松访问此类里的所有对象,而增加此类的内容,仅需类似Winform那般拖拉控件即可完成,敏捷开发首选。

技术交流QQ群

QQ群名:Excel催化剂开源讨论群, QQ群号:788145319

Excel催化剂开源讨论群二维码

关于Excel催化剂

Excel催化剂先是一微信公众号的名称,后来顺其名称,正式推出了Excel插件,插件将持续性地更新,更新的周期视本人的时间而定争取一周能够上线一个大功能模块。Excel催化剂插件承诺个人用户永久性免费使用!

Excel催化剂插件使用最新的布署技术,实现一次安装,日后所有更新自动更新完成,无需重复关注更新动态,手动下载安装包重新安装,只需一次安装即可随时保持最新版本!

Excel催化剂插件下载链接:https://pan.baidu.com/s/1Iz2_NZJ8v7C9eqhNjdnP3Q

联系作者

公众号

取名催化剂,因Excel本身的强大,并非所有人能够立马享受到,大部分人还是在被Excel软件所虐的阶段,就是头脑里很清晰想达到的效果,而且高手们也已经实现出来,就是自己怎么弄都弄不出来,或者更糟的是还不知道Excel能够做什么而停留在不断地重复、机械、手工地在做着数据,耗费着无数的青春年华岁月。所以催生了是否可以作为一种媒介,让广大的Excel用户们可以瞬间点燃Excel的爆点,无需苦苦地挣扎地没日没夜的技巧学习、高级复杂函数的烧脑,最终走向了从入门到放弃的道路。

最后Excel功能强大,其实还需树立一个观点,不是所有事情都要交给Excel去完成,也不是所有事情Excel都是十分胜任的,外面的世界仍然是一个广阔的世界,Excel只是其中一枚耀眼的明星,还有其他更多同样精彩强大的技术、工具等。*Excel催化剂也将借力这些其他技术,让Excel能够发挥更强大的爆发!

关于Excel催化剂作者

姓名:李伟坚,从事数据分析工作多年(BI方向),一名同样在路上的学习者。
服务过行业:零售特别是鞋服类的零售行业,电商(淘宝、天猫、京东、唯品会)

技术路线从一名普通用户,通过Excel软件的学习,从此走向数据世界,非科班IT专业人士。
历经重重难关,终于在数据的道路上达到技术平原期,学习众多的知识不再太吃力,同时也形成了自己的一套数据解决方案(数据采集、数据加工清洗、数据多维建模、数据报表展示等)。

擅长技术领域:Excel等Office家族软件、VBA&VSTO的二次开发、Sqlserver数据库技术、Sqlserver的商业智能BI技术、Powerbi技术、云服务器布署技术等等。

2018年开始职业生涯作了重大调整,从原来的正职工作,转为自由职业者,暂无固定收入,暂对前面道路不太明朗,苦重新回到正职工作,对Excel催化剂的运营和开发必定受到很大的影响(正职工作时间内不可能维护也不可能随便把工作时间内的成果公布于外,工作外的时间也十分有限,因已而立之年,家庭责任重大)。

和广大拥护者一同期盼:Excel催化剂一直能运行下去,我所惠及的群体们能够给予支持(多留言鼓励下、转发下朋友圈推荐、小额打赏下和最重点的可以和所在公司及同行推荐推荐,让我的技术可以在贵司发挥价值,实现双赢(初步设想可以数据顾问的方式或一些小型项目开发的方式合作)。

技术交流QQ群

QQ群名:Excel催化剂开源讨论群, QQ群号:788145319

Excel催化剂开源讨论群二维码

关于Excel催化剂

Excel催化剂先是一微信公众号的名称,后来顺其名称,正式推出了Excel插件,插件将持续性地更新,更新的周期视本人的时间而定争取一周能够上线一个大功能模块。Excel催化剂插件承诺个人用户永久性免费使用!

Excel催化剂插件使用最新的布署技术,实现一次安装,日后所有更新自动更新完成,无需重复关注更新动态,手动下载安装包重新安装,只需一次安装即可随时保持最新版本!

Excel催化剂插件下载链接:https://pan.baidu.com/s/1Iz2_NZJ8v7C9eqhNjdnP3Q

联系作者

公众号

取名催化剂,因Excel本身的强大,并非所有人能够立马享受到,大部分人还是在被Excel软件所虐的阶段,就是头脑里很清晰想达到的效果,而且高手们也已经实现出来,就是自己怎么弄都弄不出来,或者更糟的是还不知道Excel能够做什么而停留在不断地重复、机械、手工地在做着数据,耗费着无数的青春年华岁月。所以催生了是否可以作为一种媒介,让广大的Excel用户们可以瞬间点燃Excel的爆点,无需苦苦地挣扎地没日没夜的技巧学习、高级复杂函数的烧脑,最终走向了从入门到放弃的道路。

最后Excel功能强大,其实还需树立一个观点,不是所有事情都要交给Excel去完成,也不是所有事情Excel都是十分胜任的,外面的世界仍然是一个广阔的世界,Excel只是其中一枚耀眼的明星,还有其他更多同样精彩强大的技术、工具等。*Excel催化剂也将借力这些其他技术,让Excel能够发挥更强大的爆发!

关于Excel催化剂作者

姓名:李伟坚,从事数据分析工作多年(BI方向),一名同样在路上的学习者。
服务过行业:零售特别是鞋服类的零售行业,电商(淘宝、天猫、京东、唯品会)

技术路线从一名普通用户,通过Excel软件的学习,从此走向数据世界,非科班IT专业人士。
历经重重难关,终于在数据的道路上达到技术平原期,学习众多的知识不再太吃力,同时也形成了自己的一套数据解决方案(数据采集、数据加工清洗、数据多维建模、数据报表展示等)。

擅长技术领域:Excel等Office家族软件、VBA&VSTO的二次开发、Sqlserver数据库技术、Sqlserver的商业智能BI技术、Powerbi技术、云服务器布署技术等等。

2018年开始职业生涯作了重大调整,从原来的正职工作,转为自由职业者,暂无固定收入,暂对前面道路不太明朗,苦重新回到正职工作,对Excel催化剂的运营和开发必定受到很大的影响(正职工作时间内不可能维护也不可能随便把工作时间内的成果公布于外,工作外的时间也十分有限,因已而立之年,家庭责任重大)。

和广大拥护者一同期盼:Excel催化剂一直能运行下去,我所惠及的群体们能够给予支持(多留言鼓励下、转发下朋友圈推荐、小额打赏下和最重点的可以和所在公司及同行推荐推荐,让我的技术可以在贵司发挥价值,实现双赢(初步设想可以数据顾问的方式或一些小型项目开发的方式合作)。

转载于:https://www.cnblogs.com/ExcelCuiHuaJi/p/10578999.html

Excel催化剂开源第12波-VSTO开发遍历功能区所有菜单按钮及自定义函数清单相关推荐

  1. Excel催化剂开源第23波-VSTO开发辅助录入功能关键技术

    Excel催化剂开源第23波-VSTO开发辅助录入功能关键技术 Excel催化剂 2019.01.12 14:10* 字数 2948 阅读 41评论 0喜欢 0 编辑文章 在Excel催化剂的几大辅助 ...

  2. Excel催化剂开源第10波-VSTO开发之用户配置数据与工作薄文件一同存储

    在传统的VBA开发中,若是用的是普通加载项方法,是可以存储数据在xlam上的,若用的是Com加载项方法同时是Addins程序级别的项目开发的,配置文件没法保存到工作薄中,一般另外用配置文件来存放供调用 ...

  3. Excel催化剂开源第16波-VSTO开发之脱离传统COM交互以提升性能

    在VSTO开发或其他COM技术开发过程中,甚至VBA也是,在和Excel交互中,难免会遇到性能瓶颈问题,COM技术的交互实在太慢,对大量数据读写等操作,耗时太长,容易卡用户界面以为是程序死机等等. 在 ...

  4. Excel催化剂开源第32波-VSTO开发的插件让WPS顺利调用的方法-注册表增加注册信息...

    VSTO插件开发完成后,鉴于现在WPS用户也不少,很多时候用户没办法用OFFICE软件,只能在WPS环境下办公,VSTO开发的插件,只需增加一句注册表信息,即可让WPS识别到并调用VSTO开发的功能, ...

  5. Excel催化剂开源第13波-VSTO开发之DataGridView控件几个小坑

    Excel催化剂内部大量使用了DataGridView,这其中有一些小坑,花了力气才解决的,在此给广大开发者作简单分享. 为何要使用DataGridView而不是其他控件如ListBox.ListVi ...

  6. Excel催化剂开源第15波-VSTO开发之DataTable数据导出至单元格区域

    上篇提到如何从Excel界面上拿到用户的数据,另外反方向的怎样输出给用户数据,也是关键之处. VSTO最大的优势是,这双向的过程中,全程有用户的交互操作. 而一般IT型的程序,都是脱离用户的操作,只能 ...

  7. excel中如何动态地创建控件以显示查询结果_Excel催化剂开源第23波-VSTO开发辅助录入功能...

    在Excel催化剂的几大辅助录入功能中(数据验证保护.数据多级联动输入.关键词模糊智能匹配输入)中,用了一些customxmlPart技术来存储配置信息,同时在关键词模糊智能匹配输入中,用了一个VST ...

  8. 个人用户永久免费,可自动升级版Excel插件,使用VSTO开发,Excel催化剂功能第12波-快速生成、读取、导出条形码二维码...

    根据指定的内容生成对应的条形码或二维码,在如今移动互联网时代,并不是一件什么新鲜事,随便百度一下,都能找到好多的软件或在线网站可以帮我们做到,但细想一下,如果很偶然地只是生成一个两这样的图形,百度一下 ...

  9. Excel催化剂开源第43波-Excel选择对象Selection在.Net开发中的使用

    Excel的二次开发有一极大的优势所在,可以结合用户的交互进行程序的运行,大量用户的交互,都是从选择对象开始,用户选择了单元格区域.图形.图表等对象,之后再进行程序代码的加工处理,生成用户所需的最终结 ...

最新文章

  1. IT十八掌作业_java基础第十六天_GUI/socket
  2. boost::all_clustering_coefficients用法的测试程序
  3. CCIE-LAB-第十一篇-DMVPN+IPSEC+BGP
  4. css背景图background - 多背景定义
  5. UI设计动效\动画素材模板|分层分步骤学习动效设计
  6. MATLAB中的曲线拟合
  7. Java生成CSV文件的方法
  8. gdb调试daemon程序
  9. PPS网络电视清爽去广告版
  10. 网站建设合同注意事项
  11. python rgb565_RGB565的转换
  12. 运营天猫商城的注意事项
  13. 鸡尾酒会算法 --- 公式
  14. 最适合人工智能开发的5种编程语言
  15. 位运算常用技巧分析汇总(算法进阶)
  16. java面试题2019最新
  17. 用c语言表现一元多项式的除法,c语言编程实例一元多项式的计算
  18. android 手机系统排行榜,手机系统排行榜出炉:第一名意料之中,MIUI排名令人意外!...
  19. 中兴OLT C300 enable密码
  20. zabbix监控Web界面

热门文章

  1. 猿创征文|活在大二,前端的我勇往直前
  2. 【读书】- 二十几岁没有十年
  3. HTML中Table表格的使用与漂亮的表格模板
  4. 51nod 1366 贫富差距 (并查集+最短路)
  5. 《Java并发编程:设计原则与模式 第二版》pdf 附下载链接
  6. 关于Antd的Affix突然不好用了,或者Window的scroll监听不好用了
  7. 上海交通大学安泰经济与管理学院:大疫当前谈供应链思维
  8. 【ObjectARX】--创建和访问图形数据库(DwgDatabase)
  9. 关于.shtml 浏览器打开是源码的问题
  10. 光的干涉衍射计算机模拟仿真技术,实验报告之仿真(光的干涉与衍射)