From 5f24922f549e3e5897d05e642e5a00b07b45cda1 Mon Sep 17 00:00:00 2001 From: ShrlAlgo <903524121@qq.com> Date: Thu, 4 Sep 2025 09:53:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.idea.AddInManager/.idea/.gitignore | 13 + .idea/.idea.AddInManager/.idea/encodings.xml | 4 + .../.idea.AddInManager/.idea/indexLayout.xml | 8 + AddInManager.addin | 14 + AddInManager.sln | 22 + AddInManager/AIM.cs | 120 ++ AddInManager/AddInManager.csproj | 58 + AddInManager/Addin.cs | 112 ++ AddInManager/AddinItem.cs | 146 ++ AddInManager/AddinItemComparer.cs | 10 + AddInManager/AddinManager.cs | 320 +++++ AddInManager/AddinType.cs | 13 + AddInManager/Addins.cs | 172 +++ AddInManager/AddinsApplication.cs | 64 + AddInManager/AddinsCommand.cs | 69 + AddInManager/App.cs | 121 ++ AddInManager/AssemLoader.cs | 275 ++++ AddInManager/CAddInManagerFaceless.cs | 19 + AddInManager/CAddInManagerManual.cs | 40 + AddInManager/CAddInManagerReadOnly.cs | 33 + AddInManager/FailedToRunECDialog.cs | 16 + AddInManager/FileUtils.cs | 239 ++++ AddInManager/FolderTooBigDialog.cs | 24 + AddInManager/IAddinNode.cs | 11 + AddInManager/IniFile.cs | 56 + AddInManager/ManifestFile.cs | 392 +++++ AddInManager/Properties/Resources.Designer.cs | 137 ++ AddInManager/Properties/Resources.resx | 145 ++ AddInManager/Properties/Settings.Designer.cs | 38 + AddInManager/Properties/Settings.settings | 9 + AddInManager/RelayCommand.cs | 32 + AddInManager/Resources/Develop_16.png | Bin 0 -> 331 bytes AddInManager/Resources/Develop_32.png | Bin 0 -> 558 bytes AddInManager/StaticUtil.cs | 29 + AddInManager/VisibilityMode.cs | 19 + AddInManager/Wpf/AssemblySelectorWindow.xaml | 107 ++ .../Wpf/AssemblySelectorWindow.xaml.cs | 81 ++ AddInManager/Wpf/LeftMarginMultiplier.cs | 37 + AddInManager/Wpf/MainWindow.xaml | 414 ++++++ AddInManager/Wpf/MainWindow.xaml.cs | 1274 +++++++++++++++++ AddInManager/Wpf/Styles/ModernStyles.xaml | 862 +++++++++++ AddInManager/app.config | 15 + 42 files changed, 5570 insertions(+) create mode 100644 .idea/.idea.AddInManager/.idea/.gitignore create mode 100644 .idea/.idea.AddInManager/.idea/encodings.xml create mode 100644 .idea/.idea.AddInManager/.idea/indexLayout.xml create mode 100644 AddInManager.addin create mode 100644 AddInManager.sln create mode 100644 AddInManager/AIM.cs create mode 100644 AddInManager/AddInManager.csproj create mode 100644 AddInManager/Addin.cs create mode 100644 AddInManager/AddinItem.cs create mode 100644 AddInManager/AddinItemComparer.cs create mode 100644 AddInManager/AddinManager.cs create mode 100644 AddInManager/AddinType.cs create mode 100644 AddInManager/Addins.cs create mode 100644 AddInManager/AddinsApplication.cs create mode 100644 AddInManager/AddinsCommand.cs create mode 100644 AddInManager/App.cs create mode 100644 AddInManager/AssemLoader.cs create mode 100644 AddInManager/CAddInManagerFaceless.cs create mode 100644 AddInManager/CAddInManagerManual.cs create mode 100644 AddInManager/CAddInManagerReadOnly.cs create mode 100644 AddInManager/FailedToRunECDialog.cs create mode 100644 AddInManager/FileUtils.cs create mode 100644 AddInManager/FolderTooBigDialog.cs create mode 100644 AddInManager/IAddinNode.cs create mode 100644 AddInManager/IniFile.cs create mode 100644 AddInManager/ManifestFile.cs create mode 100644 AddInManager/Properties/Resources.Designer.cs create mode 100644 AddInManager/Properties/Resources.resx create mode 100644 AddInManager/Properties/Settings.Designer.cs create mode 100644 AddInManager/Properties/Settings.settings create mode 100644 AddInManager/RelayCommand.cs create mode 100644 AddInManager/Resources/Develop_16.png create mode 100644 AddInManager/Resources/Develop_32.png create mode 100644 AddInManager/StaticUtil.cs create mode 100644 AddInManager/VisibilityMode.cs create mode 100644 AddInManager/Wpf/AssemblySelectorWindow.xaml create mode 100644 AddInManager/Wpf/AssemblySelectorWindow.xaml.cs create mode 100644 AddInManager/Wpf/LeftMarginMultiplier.cs create mode 100644 AddInManager/Wpf/MainWindow.xaml create mode 100644 AddInManager/Wpf/MainWindow.xaml.cs create mode 100644 AddInManager/Wpf/Styles/ModernStyles.xaml create mode 100644 AddInManager/app.config diff --git a/.idea/.idea.AddInManager/.idea/.gitignore b/.idea/.idea.AddInManager/.idea/.gitignore new file mode 100644 index 0000000..9bb7aef --- /dev/null +++ b/.idea/.idea.AddInManager/.idea/.gitignore @@ -0,0 +1,13 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# Rider 忽略的文件 +/.idea.AddInManager.iml +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.AddInManager/.idea/encodings.xml b/.idea/.idea.AddInManager/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.AddInManager/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.AddInManager/.idea/indexLayout.xml b/.idea/.idea.AddInManager/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.AddInManager/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/AddInManager.addin b/AddInManager.addin new file mode 100644 index 0000000..d0664fe --- /dev/null +++ b/AddInManager.addin @@ -0,0 +1,14 @@ + + + + AddInManager.dll + 插件开发工具 + E8196B4E-AC4F-4B22-BF75-48F9A6039BDA + AddInManager.App + Add-In Manager + AlwaysVisible + Unknown + ADSK + Autodesk, www.autodesk.com + + diff --git a/AddInManager.sln b/AddInManager.sln new file mode 100644 index 0000000..e55e977 --- /dev/null +++ b/AddInManager.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AddInManager", "AddInManager\AddInManager.csproj", "{1A000607-4238-4AB2-9E73-A6937E061ABE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A000607-4238-4AB2-9E73-A6937E061ABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A000607-4238-4AB2-9E73-A6937E061ABE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A000607-4238-4AB2-9E73-A6937E061ABE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A000607-4238-4AB2-9E73-A6937E061ABE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AddInManager/AIM.cs b/AddInManager/AIM.cs new file mode 100644 index 0000000..9a200d1 --- /dev/null +++ b/AddInManager/AIM.cs @@ -0,0 +1,120 @@ +using System; +using System.Reflection; +using System.Windows; + +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; + +namespace AddInManager +{ + public sealed class AIM + { + public Result ExecuteCommand(ExternalCommandData data, ref string message, ElementSet elements, bool faceless) + { + if (ActiveCmd != null && faceless) + { + return RunActiveCommand(data, ref message, elements); + } + + var mainWindow = new Wpf.MainWindow(this); + var dialogResult = mainWindow.ShowDialog(); + + if (dialogResult != true) + { + return Result.Cancelled; + } + + // 窗口关闭后,检查是否有选中的命令需要执行 + if (ActiveCmd != null && ActiveCmdItem != null) + { + return RunActiveCommand(data, ref message, elements); + } + + return Result.Succeeded; // 如果没有命令要执行,返回成功 + } + + public string ActiveTempFolder { get; set; } = string.Empty; + + private Result RunActiveCommand(ExternalCommandData data, ref string message, ElementSet elements) + { + var filePath = ActiveCmd.FilePath; + var assemLoader = new AssemLoader(); + Result result; + try + { + assemLoader.HookAssemblyResolve(); + var assembly = assemLoader.LoadAddinsToTempFolder(filePath, false); + if (null == assembly) + { + result = Result.Failed; + } + else + { + ActiveTempFolder = assemLoader.TempFolder; + var externalCommand = assembly.CreateInstance(ActiveCmdItem.FullClassName) as IExternalCommand; + if (externalCommand == null) + { + result = Result.Failed; + } + else + { + ActiveEC = externalCommand; + result = ActiveEC.Execute(data, ref message, elements); + } + } + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString()); + result = Result.Failed; + } + finally + { + assemLoader.UnhookAssemblyResolve(); + assemLoader.CopyGeneratedFilesBack(); + } + return result; + } + + public static AIM Instance + { + get + { + if (m_inst == null) + { + lock (typeof(AIM)) + { + if (m_inst == null) + { + m_inst = new AIM(); + } + } + } + return m_inst; + } + } + + private AIM() + { + AddinManager = new AddinManager(); + ActiveCmd = null; + ActiveCmdItem = null; + ActiveApp = null; + ActiveAppItem = null; + } + + public IExternalCommand ActiveEC { get; set; } + + public Addin ActiveCmd { get; set; } + + public AddinItem ActiveCmdItem { get; set; } + + public Addin ActiveApp { get; set; } + + public AddinItem ActiveAppItem { get; set; } + + public AddinManager AddinManager { get; set; } + + private static volatile AIM m_inst; + } +} diff --git a/AddInManager/AddInManager.csproj b/AddInManager/AddInManager.csproj new file mode 100644 index 0000000..65eca3a --- /dev/null +++ b/AddInManager/AddInManager.csproj @@ -0,0 +1,58 @@ + + + Library + AddInManager + AddInManager + False + preview + True + False + False + net48 + + + + + + + + + + True + True + Settings.settings + + + + + True + True + Resources.resx + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + \ No newline at end of file diff --git a/AddInManager/Addin.cs b/AddInManager/Addin.cs new file mode 100644 index 0000000..b50b3df --- /dev/null +++ b/AddInManager/Addin.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace AddInManager +{ + public class Addin : IAddinNode + { + public List ItemList { get; set; } + + public string FilePath { get; set; } + + public bool Save { get; set; } + + public bool Hidden { get; set; } + + public Addin(string filePath) + { + ItemList = new List(); + FilePath = filePath; + Save = true; + } + + public Addin(string filePath, List itemList) + { + ItemList = itemList; + FilePath = filePath; + SortAddinItem(); + Save = true; + } + + public void SortAddinItem() + { + ItemList.Sort(new AddinItemComparer()); + } + + public void RemoveItem(AddinItem item) + { + ItemList.Remove(item); + if (ItemList.Count == 0) + { + AIM.Instance.AddinManager.RemoveAddin(this); + } + } + + public void SaveToLocalIni(IniFile file) + { + if (ItemList == null || ItemList.Count == 0) + { + return; + } + var addinType = ItemList[0].AddinType; + if (addinType == AddinType.Command) + { + file.WriteSection("ExternalCommands"); + file.Write("ExternalCommands", "ECCount", 0); + var num = 0; + foreach (var addinItem in ItemList) + { + if (addinItem.Save) + { + WriteExternalCommand(file, addinItem, ++num); + } + } + file.Write("ExternalCommands", "ECCount", num); + return; + } + file.WriteSection("ExternalApplications"); + file.Write("ExternalApplications", "EACount", 0); + var num2 = 0; + foreach (var addinItem2 in ItemList) + { + WriteExternalApplication(file, addinItem2, ++num2); + } + file.Write("ExternalApplications", "EACount", num2); + } + + private void WriteExternalCommand(IniFile file, AddinItem item, int number) + { + file.Write("ExternalCommands", $"ECName{number}", item.Name); + file.Write("ExternalCommands", $"ECClassName{number}", item.FullClassName); + file.Write("ExternalCommands", $"ECAssembly{number}", item.AssemblyName); + file.Write("ExternalCommands", $"ECDescription{number}", item.Description); + } + + private void WriteExternalApplication(IniFile file, AddinItem item, int number) + { + file.Write("ExternalApplications", $"EAClassName{number}", item.FullClassName); + file.Write("ExternalApplications", $"EAAssembly{number}", item.AssemblyName); + } + + public void SaveToLocalManifest() + { + if (ItemList == null || ItemList.Count == 0) + { + return; + } + var addinType = ItemList[0].AddinType; + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(FilePath); + var manifestFile = new ManifestFile($"{fileNameWithoutExtension}.addin"); + if (addinType == AddinType.Application) + { + manifestFile.Applications = ItemList; + } + else if (addinType == AddinType.Command) + { + manifestFile.Commands = ItemList; + } + manifestFile.Save(); + } + } +} diff --git a/AddInManager/AddinItem.cs b/AddInManager/AddinItem.cs new file mode 100644 index 0000000..f4d324b --- /dev/null +++ b/AddInManager/AddinItem.cs @@ -0,0 +1,146 @@ +using System; +using System.IO; + +using Autodesk.Revit.Attributes; + +namespace AddInManager +{ + public class AddinItem : IAddinNode + { + public AddinItem(AddinType type) + { + AddinType = type; + m_clientId = Guid.NewGuid(); + ClientIdString = m_clientId.ToString(); + m_assemblyPath = string.Empty; + AssemblyName = string.Empty; + FullClassName = string.Empty; + m_name = string.Empty; + Save = true; + VisibilityMode = VisibilityMode.AlwaysVisible; + } + + public AddinItem(string assemblyPath, Guid clientId, string fullClassName, AddinType type, TransactionMode? transactionMode, RegenerationOption? regenerationOption, JournalingMode? journalingMode) + { + TransactionMode = transactionMode; + RegenerationMode = regenerationOption; + JournalingMode = journalingMode; + AddinType = type; + m_assemblyPath = assemblyPath; + AssemblyName = Path.GetFileName(m_assemblyPath); + m_clientId = clientId; + ClientIdString = clientId.ToString(); + FullClassName = fullClassName; + var num = fullClassName.LastIndexOf("."); + m_name = fullClassName.Substring(num + 1); + Save = true; + VisibilityMode = VisibilityMode.AlwaysVisible; + } + + public void SaveToManifest() + { + var manifestFile = new ManifestFile($"{m_name}.addin"); + if (AddinType == AddinType.Application) + { + manifestFile.Applications.Add(this); + } + else if (AddinType == AddinType.Command) + { + manifestFile.Commands.Add(this); + } + manifestFile.Save(); + } + + public AddinType AddinType { get; set; } + + public string AssemblyPath + { + get => m_assemblyPath; + set + { + m_assemblyPath = value; + AssemblyName = Path.GetFileName(m_assemblyPath); + } + } + + public string AssemblyName { get; set; } + + public Guid ClientId + { + get => m_clientId; + set + { + m_clientId = value; + ClientIdString = m_clientId.ToString(); + } + } + + protected internal string ClientIdString { get; set; } + + public string FullClassName { get; set; } + + public string Name + { + get + { + if (string.IsNullOrEmpty(m_name)) + { + return "External Tool"; + } + return m_name; + } + set + { + if (!string.IsNullOrEmpty(value)) + { + m_name = value; + return; + } + m_name = "External Tool"; + } + } + + public string Description + { + get + { + if (string.IsNullOrEmpty(m_description)) + { + return "\"\""; + } + return m_description; + } + set + { + if (string.IsNullOrEmpty(value)) + { + m_description = "\"\""; + return; + } + m_description = value; + } + } + + public VisibilityMode VisibilityMode { get; set; } + + public bool Save { get; set; } + + public bool Hidden { get; set; } + + public TransactionMode? TransactionMode { get; set; } + + public RegenerationOption? RegenerationMode { get; set; } + + public JournalingMode? JournalingMode { get; set; } + + public override string ToString() + { + return m_name; + } + + protected string m_assemblyPath; + protected Guid m_clientId; + private string m_name; + private string m_description; + } +} diff --git a/AddInManager/AddinItemComparer.cs b/AddInManager/AddinItemComparer.cs new file mode 100644 index 0000000..db5b6f3 --- /dev/null +++ b/AddInManager/AddinItemComparer.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace AddInManager +{ + public class AddinItemComparer : IComparer + { + public int Compare(AddinItem x, AddinItem y) => string.Compare(x?.Name, y?.Name, StringComparison.Ordinal); + } +} diff --git a/AddInManager/AddinManager.cs b/AddInManager/AddinManager.cs new file mode 100644 index 0000000..c528f29 --- /dev/null +++ b/AddInManager/AddinManager.cs @@ -0,0 +1,320 @@ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +using AddInManager.Properties; + +namespace AddInManager +{ + public class AddinManager + { + public AddinsApplication Applications { get; } + + public int AppCount => Applications.Count; + + public AddinsCommand Commands { get; } + + public int CmdCount => Commands.Count; + + public AddinManager() + { + Commands = new AddinsCommand(); + Applications = new AddinsApplication(); + GetIniFilePaths(); + ReadAddinsFromAimIni(); + } + + public IniFile AimIniFile { get; set; } + + public IniFile RevitIniFile { get; set; } + + private void GetIniFilePaths() + { + var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var text = Path.Combine(folderPath, Settings.Default.AppFolder); + var text2 = Path.Combine(text, "AimInternal.ini"); + AimIniFile = new IniFile(text2); + var currentProcess = Process.GetCurrentProcess(); + var fileName = currentProcess.MainModule.FileName; + var text3 = fileName.Replace(".exe", ".ini"); + RevitIniFile = new IniFile(text3); + } + + public void ReadAddinsFromAimIni() + { + Commands.ReadItems(AimIniFile); + Applications.ReadItems(AimIniFile); + } + + public void RemoveAddin(Addin addin) + { + if (!Commands.RemoveAddIn(addin)) + { + Applications.RemoveAddIn(addin); + } + } + + public AddinType LoadAddin(string filePath) + { + var addinType = AddinType.Invalid; + if (!File.Exists(filePath)) + { + return addinType; + } + Path.GetFileName(filePath); + var assemLoader = new AssemLoader(); + List list = null; + List list2 = null; + try + { + assemLoader.HookAssemblyResolve(); + var assembly = assemLoader.LoadAddinsToTempFolder(filePath, true); + if (null == assembly) + { + return addinType; + } + list = Commands.LoadItems(assembly, StaticUtil.m_ecFullName, filePath, AddinType.Command); + list2 = Applications.LoadItems(assembly, StaticUtil.m_eaFullName, filePath, AddinType.Application); + } + catch (Exception) + { + } + finally + { + assemLoader.UnhookAssemblyResolve(); + } + if (list != null && list.Count > 0) + { + var addin = new Addin(filePath, list); + Commands.AddAddIn(addin); + addinType |= AddinType.Command; + } + if (list2 != null && list2.Count > 0) + { + var addin2 = new Addin(filePath, list2); + Applications.AddAddIn(addin2); + addinType |= AddinType.Application; + } + return addinType; + } + + public void SaveToRevitIni() + { + if (!File.Exists(RevitIniFile.FilePath)) + { + throw new System.IO.FileNotFoundException( + $"can't find the revit.ini file from: {RevitIniFile.FilePath}", + RevitIniFile.FilePath +); + } + Commands.Save(RevitIniFile); + Applications.Save(RevitIniFile); + } + + public void SaveToLocal() + { + SaveToLocalManifest(); + } + + public void SaveToLocalRevitIni() + { + foreach (var keyValuePair in Commands.AddinDict) + { + var key = keyValuePair.Key; + var value = keyValuePair.Value; + var directoryName = Path.GetDirectoryName(value.FilePath); + var iniFile = new IniFile(Path.Combine(directoryName, "revit.ini")); + value.SaveToLocalIni(iniFile); + if (Applications.AddinDict.ContainsKey(key)) + { + var addin = Applications.AddinDict[key]; + addin.SaveToLocalIni(iniFile); + } + } + } + + public void SaveToAimIni() + { + if (!File.Exists(AimIniFile.FilePath)) + { + new FileInfo(AimIniFile.FilePath).Create(); + FileUtils.SetWriteable(AimIniFile.FilePath); + } + Commands.Save(AimIniFile); + Applications.Save(AimIniFile); + } + + public bool HasItemsToSave() + { + foreach (var addin in Commands.AddinDict.Values) + { + if (addin.Save) + { + return true; + } + } + foreach (var addin2 in Applications.AddinDict.Values) + { + if (addin2.Save) + { + return true; + } + } + return false; + } + + public string SaveToAllUserManifest() + { + var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + var currentAddinFolder = Path.Combine(folderPath, $"Autodesk\\Revit\\Addins\\{App.RevitVersion}"); + var manifestFile = new ManifestFile(false); + var numCmdAddins = 0; + Addin savedCmdAddin = null; + foreach (var cmdAddin in Commands.AddinDict.Values) + { + if (cmdAddin.Save) + { + numCmdAddins++; + savedCmdAddin = cmdAddin; + } + foreach (var addinItem in cmdAddin.ItemList) + { + if (addinItem.Save) + { + manifestFile.Commands.Add(addinItem); + } + } + } + var numAppAddins = 0; + Addin savedAppAddin = null; + foreach (var appAddin in Applications.AddinDict.Values) + { + if (appAddin.Save) + { + numCmdAddins++; + savedAppAddin = appAddin; + } + foreach (var appAddinItem in appAddin.ItemList) + { + if (appAddinItem.Save) + { + manifestFile.Applications.Add(appAddinItem); + numAppAddins++; + savedAppAddin = appAddin; + } + } + } + var addinFileName = string.Empty; + string addinFilePath; + if (numCmdAddins <= 1 && numAppAddins <= 1 && numCmdAddins + numAppAddins > 0) + { + if (savedCmdAddin != null) + { + if (savedAppAddin == null || savedCmdAddin.FilePath.Equals(savedAppAddin.FilePath, StringComparison.OrdinalIgnoreCase)) + { + addinFileName = Path.GetFileNameWithoutExtension(savedCmdAddin.FilePath); + } + } + else if (savedAppAddin != null && savedCmdAddin == null) + { + addinFileName = Path.GetFileNameWithoutExtension(savedAppAddin.FilePath); + } + if (string.IsNullOrEmpty(addinFileName)) + { + return string.Empty; + } + addinFilePath = GetProperFilePath(currentAddinFolder, addinFileName, ".addin"); + } + else + { + addinFilePath = GetProperFilePath(currentAddinFolder, "ExternalTool", ".addin"); + } + manifestFile.SaveAs(addinFilePath); + return addinFilePath; + } + + public void SaveToLocalManifest() + { + var dictionary = new Dictionary(); + var dictionary2 = new Dictionary(); + foreach (var cmdKeyValuePair in Commands.AddinDict) + { + var key = cmdKeyValuePair.Key; + var value = cmdKeyValuePair.Value; + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(value.FilePath); + var directoryName = Path.GetDirectoryName(value.FilePath); + var text = Path.Combine(directoryName, $"{fileNameWithoutExtension}.addin"); + var manifestFile = new ManifestFile(true); + foreach (var addinItem in value.ItemList) + { + if (addinItem.Save) + { + manifestFile.Commands.Add(addinItem); + } + } + if (Applications.AddinDict.ContainsKey(key)) + { + var addin = Applications.AddinDict[key]; + foreach (var addinItem2 in addin.ItemList) + { + if (addinItem2.Save) + { + manifestFile.Applications.Add(addinItem2); + } + } + dictionary.Add(key, Applications.AddinDict[key]); + } + manifestFile.SaveAs(text); + } + foreach (var appKeyValuePair in Applications.AddinDict) + { + var key2 = appKeyValuePair.Key; + var value2 = appKeyValuePair.Value; + if (!dictionary.ContainsKey(key2)) + { + var fileNameWithoutExtension2 = Path.GetFileNameWithoutExtension(value2.FilePath); + var directoryName2 = Path.GetDirectoryName(value2.FilePath); + var text2 = Path.Combine(directoryName2, $"{fileNameWithoutExtension2}.addin"); + var manifestFile2 = new ManifestFile(true); + foreach (var addinItem3 in value2.ItemList) + { + if (addinItem3.Save) + { + manifestFile2.Applications.Add(addinItem3); + } + } + if (Commands.AddinDict.ContainsKey(key2)) + { + var addin2 = Commands.AddinDict[key2]; + foreach (var addinItem4 in addin2.ItemList) + { + if (addinItem4.Save) + { + manifestFile2.Commands.Add(addinItem4); + } + } + dictionary2.Add(key2, Commands.AddinDict[key2]); + } + manifestFile2.SaveAs(text2); + } + } + } + + private static string GetProperFilePath(string folder, string fileNameWithoutExt, string ext) + { + var filePath = string.Empty; + var fileIndex = -1; + do + { + fileIndex++; + var text2 = ((fileIndex <= 0) ? (fileNameWithoutExt + ext) : (fileNameWithoutExt + fileIndex + ext)); + filePath = Path.Combine(folder, text2); + } + while (File.Exists(filePath)); + return filePath; + } + } +} diff --git a/AddInManager/AddinType.cs b/AddInManager/AddinType.cs new file mode 100644 index 0000000..1357581 --- /dev/null +++ b/AddInManager/AddinType.cs @@ -0,0 +1,13 @@ +using System; + +namespace AddInManager +{ + [Flags] + public enum AddinType + { + Invalid = 0, + Command = 1, + Application = 2, + Mixed = 3 + } +} diff --git a/AddInManager/Addins.cs b/AddInManager/Addins.cs new file mode 100644 index 0000000..248a2eb --- /dev/null +++ b/AddInManager/Addins.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; + +using Autodesk.Revit.Attributes; + +namespace AddInManager +{ + public abstract class Addins + { + public SortedDictionary AddinDict + { + get => m_addinDict; + set => m_addinDict = value; + } + + public int Count => m_addinDict.Count; + + public Addins() + { + m_addinDict = new SortedDictionary(); + } + + public void SortAddin() + { + foreach (var addin in m_addinDict.Values) + { + addin.SortAddinItem(); + } + } + + public void AddAddIn(Addin addin) + { + var fileName = Path.GetFileName(addin.FilePath); + if (m_addinDict.ContainsKey(fileName)) + { + m_addinDict.Remove(fileName); + } + m_addinDict[fileName] = addin; + } + + public bool RemoveAddIn(Addin addin) + { + var fileName = Path.GetFileName(addin.FilePath); + if (m_addinDict.ContainsKey(fileName)) + { + m_addinDict.Remove(fileName); + return true; + } + return false; + } + + public void AddItem(AddinItem item) + { + var assemblyName = item.AssemblyName; + if (!m_addinDict.ContainsKey(assemblyName)) + { + m_addinDict[assemblyName] = new Addin(item.AssemblyPath); + } + m_addinDict[assemblyName].ItemList.Add(item); + } + + public List LoadItems(Assembly assembly, string fullName, string originalAssemblyFilePath, AddinType type) + { + var list = new List(); + Type[] array = null; + try + { + array = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + array = ex.Types; + if (array == null) + { + return list; + } + } + var list2 = new List(); + var list3 = new List(); + foreach (var type2 in array) + { + try + { + if (!(null == type2) && !type2.IsAbstract) + { + var @interface = type2.GetInterface(fullName); + if (null != @interface) + { + TransactionMode? transactionMode = null; + RegenerationOption? regenerationOption = null; + JournalingMode? journalingMode = null; + if (type != AddinType.Application) + { + var customAttributes = Attribute.GetCustomAttributes(type2, false); + foreach (var attribute in customAttributes) + { + if (attribute is RegenerationAttribute) + { + var regenerationAttribute = (RegenerationAttribute)attribute; + regenerationOption = new RegenerationOption?(regenerationAttribute.Option); + } + if (attribute is TransactionAttribute) + { + var transactionAttribute = (TransactionAttribute)attribute; + transactionMode = new TransactionMode?(transactionAttribute.Mode); + } + if (attribute is JournalingAttribute) + { + var journalingAttribute = (JournalingAttribute)attribute; + journalingMode = new JournalingMode?(journalingAttribute.Mode); + } + if (transactionMode != null && regenerationOption != null) + { + break; + } + } + if (transactionMode == null) + { + list2.Add(type2.Name); + continue; + } + var mode = StaticUtil.m_tsactMode; + if (transactionMode != StaticUtil.m_tsactMode) + { + list3.Add(type2.Name); + continue; + } + } + var addinItem = new AddinItem(originalAssemblyFilePath, Guid.NewGuid(), type2.FullName, type, transactionMode, regenerationOption, journalingMode); + list.Add(addinItem); + } + } + } + catch (Exception) + { + } + } + if (list2.Count > 0) + { + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("The following Classes: "); + foreach (var text in list2) + { + stringBuilder.AppendLine(text); + } + stringBuilder.Append("implements IExternalCommand but doesn't contain both RegenerationAttribute and TransactionAttribute!"); + StaticUtil.ShowWarning(stringBuilder.ToString()); + } + if (list3.Count > 0) + { + var stringBuilder2 = new StringBuilder(); + stringBuilder2.AppendLine("The TransactionMode set to Classes: "); + foreach (var text2 in list3) + { + stringBuilder2.AppendLine(text2); + } + stringBuilder2.Append(" are not the same as the mode set to Add-In Manager!"); + StaticUtil.ShowWarning(stringBuilder2.ToString()); + } + return list; + } + + protected SortedDictionary m_addinDict; + + protected int m_maxCount = 100; + + protected int m_count; + } +} diff --git a/AddInManager/AddinsApplication.cs b/AddInManager/AddinsApplication.cs new file mode 100644 index 0000000..cf31e0d --- /dev/null +++ b/AddInManager/AddinsApplication.cs @@ -0,0 +1,64 @@ +using System; + +namespace AddInManager +{ + public class AddinsApplication : Addins + { + public void ReadItems(IniFile file) + { + var num = file.ReadInt("ExternalApplications", "EACount"); + var i = 1; + while (i <= num) + { + ReadExternalApplication(file, i++); + } + SortAddin(); + } + + private bool ReadExternalApplication(IniFile file, int nodeNumber) + { + var text = file.ReadString("ExternalApplications", $"EAClassName{nodeNumber}"); + var text2 = file.ReadString("ExternalApplications", $"EAAssembly{nodeNumber}"); + if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2)) + { + return false; + } + AddItem(new AddinItem(AddinType.Application) + { + Name = string.Empty, + AssemblyPath = text2, + FullClassName = text + }); + return true; + } + + public void Save(IniFile file) + { + file.WriteSection("ExternalApplications"); + file.Write("ExternalApplications", "EACount", m_maxCount); + var num = 0; + foreach (var addin in m_addinDict.Values) + { + foreach (var addinItem in addin.ItemList) + { + if (num >= m_maxCount) + { + break; + } + if (addinItem.Save) + { + WriteExternalApplication(file, addinItem, ++num); + } + } + } + file.Write("ExternalApplications", "EACount", num); + } + + private bool WriteExternalApplication(IniFile file, AddinItem item, int number) + { + file.Write("ExternalApplications", $"EAClassName{number}", item.FullClassName); + file.Write("ExternalApplications", $"EAAssembly{number}", item.AssemblyPath); + return true; + } + } +} diff --git a/AddInManager/AddinsCommand.cs b/AddInManager/AddinsCommand.cs new file mode 100644 index 0000000..e7217d8 --- /dev/null +++ b/AddInManager/AddinsCommand.cs @@ -0,0 +1,69 @@ +using System; + +namespace AddInManager +{ + public class AddinsCommand : Addins + { + public void ReadItems(IniFile file) + { + var num = file.ReadInt("ExternalCommands", "ECCount"); + var i = 1; + while (i <= num) + { + ReadExternalCommand(file, i++); + } + SortAddin(); + } + + private bool ReadExternalCommand(IniFile file, int nodeNumber) + { + var text = file.ReadString("ExternalCommands", $"ECName{nodeNumber}"); + var text2 = file.ReadString("ExternalCommands", $"ECAssembly{nodeNumber}"); + var text3 = file.ReadString("ExternalCommands", $"ECClassName{nodeNumber}"); + var text4 = file.ReadString("ExternalCommands", $"ECDescription{nodeNumber}"); + if (string.IsNullOrEmpty(text3) || string.IsNullOrEmpty(text2)) + { + return false; + } + AddItem(new AddinItem(AddinType.Command) + { + Name = text, + AssemblyPath = text2, + FullClassName = text3, + Description = text4 + }); + return true; + } + + public void Save(IniFile file) + { + file.WriteSection("ExternalCommands"); + file.Write("ExternalCommands", "ECCount", m_maxCount); + var num = 0; + foreach (var addin in m_addinDict.Values) + { + foreach (var addinItem in addin.ItemList) + { + if (num >= m_maxCount) + { + break; + } + if (addinItem.Save) + { + WriteExternalCommand(file, addinItem, ++num); + } + } + } + file.Write("ExternalCommands", "ECCount", num); + } + + private bool WriteExternalCommand(IniFile file, AddinItem item, int number) + { + file.Write("ExternalCommands", $"ECName{number}", item.Name); + file.Write("ExternalCommands", $"ECClassName{number}", item.FullClassName); + file.Write("ExternalCommands", $"ECAssembly{number}", item.AssemblyPath); + file.Write("ExternalCommands", $"ECDescription{number}", item.Description); + return true; + } + } +} diff --git a/AddInManager/App.cs b/AddInManager/App.cs new file mode 100644 index 0000000..9f9fc2d --- /dev/null +++ b/AddInManager/App.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +using AddInManager.Properties; + +using Autodesk.Private.InfoCenter; +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using Autodesk.Windows; + + +namespace AddInManager +{ + internal class App : IExternalApplication + { + public static string RevitVersion { get; set; } + private static readonly Dictionary KnownAssemblies = new Dictionary( + StringComparer.Ordinal) + { + { "System.Resources.Extensions", "System.Resources.Extensions.dll" }, + { "System.Runtime.CompilerServices.Unsafe", "System.Runtime.CompilerServices.Unsafe.dll" } + }; + private static readonly ConcurrentDictionary AssemblyCache = new ConcurrentDictionary(StringComparer.Ordinal); + private static readonly string BaseDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + public Result OnStartup(UIControlledApplication application) + { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + CreateRibbonPanel(application); + return Result.Succeeded; + } + public Result OnShutdown(UIControlledApplication application) + { + return Result.Cancelled; + } + private static void CreateRibbonPanel(UIControlledApplication application) + { + RevitVersion = application.ControlledApplication.VersionNumber; + var ribbonPanel = application.CreateRibbonPanel("开发工具"); + var pulldownButtonData = new PulldownButtonData("选项", "插件管理"); + var pulldownButton = (PulldownButton)ribbonPanel.AddItem(pulldownButtonData); + pulldownButton.Image = ToImageSource(Resources.Develop_16); + pulldownButton.LargeImage = ToImageSource(Resources.Develop_32); + AddPushButton(pulldownButton, typeof(CAddInManagerManual), "插件管理(手动模式)"); + AddPushButton(pulldownButton, typeof(CAddInManagerFaceless), "插件管理(手动模式,无界面)"); + AddPushButton(pulldownButton, typeof(CAddInManagerReadOnly), "插件管理(只读模式)"); + var tab = ComponentManager.Ribbon.FindTab("Modify"); + if (tab != null) + { + var adwPanel = new Autodesk.Windows.RibbonPanel(); + adwPanel.CopyFrom(GetRibbonPanel(ribbonPanel)); + tab.Panels.Add(adwPanel); + } + + } + internal static BitmapImage ToImageSource(Bitmap bitmap) + { + var ms = new MemoryStream(); + bitmap.Save(ms, ImageFormat.Png); + var image = new BitmapImage(); + image.BeginInit(); + ms.Seek(0, SeekOrigin.Begin); + image.StreamSource = ms; + image.EndInit(); + return image; + } + + private static readonly FieldInfo RibbonPanelField = typeof(Autodesk.Revit.UI.RibbonPanel).GetField("m_RibbonPanel", BindingFlags.Instance | BindingFlags.NonPublic); + + public static Autodesk.Windows.RibbonPanel GetRibbonPanel(Autodesk.Revit.UI.RibbonPanel panel) + { + return RibbonPanelField.GetValue(panel) as Autodesk.Windows.RibbonPanel; + } + + private static void AddPushButton(PulldownButton pullDownButton, Type command, string buttonText) + { + var buttonData = new PushButtonData(command.FullName, buttonText, Assembly.GetAssembly(command).Location, command.FullName); + pullDownButton.AddPushButton(buttonData); + } + + private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + var requestedAssemblyName = new AssemblyName(args.Name).Name; + if (AssemblyCache.TryGetValue(requestedAssemblyName, out var cachedAssembly)) + { + return cachedAssembly; + } + if (KnownAssemblies.TryGetValue(requestedAssemblyName, out var assemblyFile)) + { + var assemblyPath = Path.Combine(BaseDirectory, assemblyFile); + + try + { + var assembly = Assembly.LoadFrom(assemblyPath); + AssemblyCache.TryAdd(requestedAssemblyName, assembly); + return assembly; + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + + return null; + } + + } +} diff --git a/AddInManager/AssemLoader.cs b/AddInManager/AssemLoader.cs new file mode 100644 index 0000000..0cacb10 --- /dev/null +++ b/AddInManager/AssemLoader.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; + +namespace AddInManager +{ + public class AssemLoader + { + public string OriginalFolder { get; set; } + + public string TempFolder { get; set; } + + public AssemLoader() + { + TempFolder = string.Empty; + m_refedFolders = new List(); + m_copiedFiles = new Dictionary(); + } + + public void CopyGeneratedFilesBack() + { + var files = Directory.GetFiles(TempFolder, "*.*", SearchOption.AllDirectories); + foreach (var text in files) + { + if (m_copiedFiles.ContainsKey(text)) + { + var dateTime = m_copiedFiles[text]; + var fileInfo = new FileInfo(text); + if (fileInfo.LastWriteTime > dateTime) + { + var text2 = text.Remove(0, TempFolder.Length); + var text3 = OriginalFolder + text2; + FileUtils.CopyFile(text, text3); + } + } + else + { + var text4 = text.Remove(0, TempFolder.Length); + var text5 = OriginalFolder + text4; + FileUtils.CopyFile(text, text5); + } + } + } + + public void HookAssemblyResolve() + { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + } + + public void UnhookAssemblyResolve() + { + AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve; + } + + public Assembly LoadAddinsToTempFolder(string originalFilePath, bool parsingOnly) + { + if (string.IsNullOrEmpty(originalFilePath) || originalFilePath.StartsWith("\\") || !File.Exists(originalFilePath)) + { + return null; + } + m_parsingOnly = parsingOnly; + OriginalFolder = Path.GetDirectoryName(originalFilePath); + var stringBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(originalFilePath)); + if (parsingOnly) + { + stringBuilder.Append("-Parsing-"); + } + else + { + stringBuilder.Append("-Executing-"); + } + TempFolder = FileUtils.CreateTempFolder(stringBuilder.ToString()); + var assembly = CopyAndLoadAddin(originalFilePath, parsingOnly); + if (null == assembly || !IsAPIReferenced(assembly)) + { + return null; + } + return assembly; + } + + private Assembly CopyAndLoadAddin(string srcFilePath, bool onlyCopyRelated) + { + var text = string.Empty; + if (!FileUtils.FileExistsInFolder(srcFilePath, TempFolder)) + { + var directoryName = Path.GetDirectoryName(srcFilePath); + if (!m_refedFolders.Contains(directoryName)) + { + m_refedFolders.Add(directoryName); + } + var list = new List(); + text = FileUtils.CopyFileToFolder(srcFilePath, TempFolder, onlyCopyRelated, list); + if (string.IsNullOrEmpty(text)) + { + return null; + } + foreach (var fileInfo in list) + { + m_copiedFiles.Add(fileInfo.FullName, fileInfo.LastWriteTime); + } + } + return LoadAddin(text); + } + + private Assembly LoadAddin(string filePath) + { + Assembly assembly = null; + try + { + Monitor.Enter(this); + assembly = Assembly.LoadFile(filePath); + } + finally + { + Monitor.Exit(this); + } + return assembly; + } + + private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + Assembly assembly; + lock (this) + { + new AssemblyName(args.Name); + var text = SearchAssemblyFileInTempFolder(args.Name); + if (File.Exists(text)) + { + assembly = LoadAddin(text); + } + else + { + text = SearchAssemblyFileInOriginalFolders(args.Name); + if (string.IsNullOrEmpty(text)) + { + var array = args.Name.Split(new char[] { ',' }); + var text2 = array[0]; + if (array.Length > 1) + { + var text3 = array[2]; + if (text2.EndsWith(".resources", StringComparison.CurrentCultureIgnoreCase) && !text3.EndsWith("neutral", StringComparison.CurrentCultureIgnoreCase)) + { + text2 = text2.Substring(0, text2.Length - ".resources".Length); + } + text = SearchAssemblyFileInTempFolder(text2); + if (File.Exists(text)) + { + return LoadAddin(text); + } + text = SearchAssemblyFileInOriginalFolders(text2); + } + } + if (string.IsNullOrEmpty(text)) + { + var assemblySelector = new Wpf.AssemblySelectorWindow(args.Name); + if (assemblySelector.ShowDialog() != true) + { + return null; + } + text = assemblySelector.ResultPath; + } + assembly = CopyAndLoadAddin(text, true); + } + } + return assembly; + } + + private string SearchAssemblyFileInTempFolder(string assemName) + { + var array = new string[] { ".dll", ".exe" }; + var text = string.Empty; + var text2 = assemName.Substring(0, assemName.IndexOf(',')); + foreach (var text3 in array) + { + text = $"{TempFolder}\\{text2}{text3}"; + if (File.Exists(text)) + { + return text; + } + } + return string.Empty; + } + + private string SearchAssemblyFileInOriginalFolders(string assemName) + { + var array = new string[] { ".dll", ".exe" }; + var text = string.Empty; + var text2 = assemName.Substring(0, assemName.IndexOf(',')); + foreach (var text3 in array) + { + text = $"{m_dotnetDir}\\{text2}{text3}"; + if (File.Exists(text)) + { + return text; + } + } + foreach (var text4 in array) + { + foreach (var text5 in m_refedFolders) + { + text = $"{text5}\\{text2}{text4}"; + if (File.Exists(text)) + { + return text; + } + } + } + try + { + var directoryInfo = new DirectoryInfo(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName)); + var text6 = $"{directoryInfo.Parent.FullName}\\Regression\\_RegressionTools\\"; + if (Directory.Exists(text6)) + { + foreach (var text7 in Directory.GetFiles(text6, "*.*", SearchOption.AllDirectories)) + { + if (Path.GetFileNameWithoutExtension(text7).Equals(text2, StringComparison.OrdinalIgnoreCase)) + { + return text7; + } + } + } + } + catch (Exception) + { + } + var num = assemName.IndexOf("XMLSerializers", StringComparison.OrdinalIgnoreCase); + if (num != -1) + { + assemName = $"System.XML{assemName.Substring(num + "XMLSerializers".Length)}"; + return SearchAssemblyFileInOriginalFolders(assemName); + } + return null; + } + + private bool IsAPIReferenced(Assembly assembly) + { + if (string.IsNullOrEmpty(m_revitAPIAssemblyFullName)) + { + foreach (var assembly2 in AppDomain.CurrentDomain.GetAssemblies()) + { + if (string.Compare(assembly2.GetName().Name, "RevitAPI", true) == 0) + { + m_revitAPIAssemblyFullName = assembly2.GetName().Name; + break; + } + } + } + foreach (var assemblyName in assembly.GetReferencedAssemblies()) + { + if (m_revitAPIAssemblyFullName == assemblyName.Name) + { + return true; + } + } + return false; + } + + private List m_refedFolders; + + private Dictionary m_copiedFiles; + + private bool m_parsingOnly; + + private static string m_dotnetDir = + $"{Environment.GetEnvironmentVariable("windir")}\\Microsoft.NET\\Framework\\v2.0.50727"; + + public static string m_resolvedAssemPath = string.Empty; + + private string m_revitAPIAssemblyFullName; + } +} diff --git a/AddInManager/CAddInManagerFaceless.cs b/AddInManager/CAddInManagerFaceless.cs new file mode 100644 index 0000000..d2ec081 --- /dev/null +++ b/AddInManager/CAddInManagerFaceless.cs @@ -0,0 +1,19 @@ + +using Autodesk.Revit.Attributes; + +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; + +using System; + +namespace AddInManager +{ + [Transaction(TransactionMode.Manual)] + public class CAddInManagerFaceless : IExternalCommand + { + public Result Execute(ExternalCommandData revit, ref string message, ElementSet elements) + { + return AIM.Instance.ExecuteCommand(revit, ref message, elements, true); + } + } +} diff --git a/AddInManager/CAddInManagerManual.cs b/AddInManager/CAddInManagerManual.cs new file mode 100644 index 0000000..5d268a4 --- /dev/null +++ b/AddInManager/CAddInManagerManual.cs @@ -0,0 +1,40 @@ +using Autodesk.Revit.Attributes; +using Autodesk.Revit.UI; + +using System; +using System.Windows; + +namespace AddInManager +{ + [Transaction(TransactionMode.Manual)] + public class CAddInManagerManual : IExternalCommand + { + public Result Execute(ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements) + { + try + { + var aim = AIM.Instance; + var window = new Wpf.MainWindow(aim) + { + WindowStartupLocation = WindowStartupLocation.CenterScreen + }; + + var dialogResult = window.ShowDialog(); + + // 检查是否需要执行命令 + if (dialogResult == true && aim.ActiveCmd != null && aim.ActiveCmdItem != null) + { + // 调用AIM的ExecuteCommand方法来执行选中的命令 + return aim.ExecuteCommand(commandData, ref message, elements, true); + } + + return Result.Succeeded; + } + catch (Exception ex) + { + message = ex.Message; + return Result.Failed; + } + } + } +} diff --git a/AddInManager/CAddInManagerReadOnly.cs b/AddInManager/CAddInManagerReadOnly.cs new file mode 100644 index 0000000..a199df3 --- /dev/null +++ b/AddInManager/CAddInManagerReadOnly.cs @@ -0,0 +1,33 @@ +using Autodesk.Revit.Attributes; +using Autodesk.Revit.UI; + +using System; +using System.Windows; + +namespace AddInManager +{ + [Transaction(TransactionMode.ReadOnly)] + public class CAddInManagerReadOnly : IExternalCommand + { + public Result Execute(ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements) + { + try + { + var aim = AIM.Instance; + + var window = new Wpf.MainWindow(aim) + { + WindowStartupLocation = WindowStartupLocation.CenterScreen + }; + window.ShowDialog(); + + return Result.Succeeded; + } + catch (Exception ex) + { + message = ex.Message; + return Result.Failed; + } + } + } +} diff --git a/AddInManager/FailedToRunECDialog.cs b/AddInManager/FailedToRunECDialog.cs new file mode 100644 index 0000000..adb373b --- /dev/null +++ b/AddInManager/FailedToRunECDialog.cs @@ -0,0 +1,16 @@ +using System; +using System.Windows; + +using AddInManager.Properties; + +namespace AddInManager +{ + public class FailedToRunECDialog + { + public static MessageBoxResult Show(string ecName) + { + var text = $"选中外部命令 [{ecName}] 返回 \"Result.Failed\",请检查测试脚本"; + return MessageBox.Show(text, Resources.AppName, MessageBoxButton.OK); + } + } +} diff --git a/AddInManager/FileUtils.cs b/AddInManager/FileUtils.cs new file mode 100644 index 0000000..14f8b16 --- /dev/null +++ b/AddInManager/FileUtils.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; + +namespace AddInManager +{ + public static class FileUtils + { + public static DateTime GetModifyTime(string filePath) + { + return File.GetLastWriteTime(filePath); + } + + public static string CreateTempFolder(string prefix) + { + var tempPath = Path.GetTempPath(); + var directoryInfo = new DirectoryInfo(Path.Combine(tempPath, "RevitAddins")); + if (!directoryInfo.Exists) + { + directoryInfo.Create(); + } + foreach (var directoryInfo2 in directoryInfo.GetDirectories()) + { + try + { + Directory.Delete(directoryInfo2.FullName, true); + } + catch (Exception) + { + } + } + var text = string.Format("{0:yyyyMMdd_HHmmss_ffff}", DateTime.Now); + var text2 = Path.Combine(directoryInfo.FullName, prefix + text); + var directoryInfo3 = new DirectoryInfo(text2); + directoryInfo3.Create(); + return directoryInfo3.FullName; + } + + public static void SetWriteable(string fileName) + { + if (File.Exists(fileName)) + { + var fileAttributes = File.GetAttributes(fileName) & ~FileAttributes.ReadOnly; + File.SetAttributes(fileName, fileAttributes); + } + } + + public static bool SameFile(string file1, string file2) + { + return 0 == string.Compare(file1.Trim(), file2.Trim(), true); + } + + public static bool CreateFile(string filePath) + { + if (File.Exists(filePath)) + { + return true; + } + try + { + var directoryName = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + using (new FileInfo(filePath).Create()) + { + SetWriteable(filePath); + } + } + catch (Exception) + { + return false; + } + return File.Exists(filePath); + } + + public static void DeleteFile(string fileName) + { + if (File.Exists(fileName)) + { + var fileAttributes = File.GetAttributes(fileName) & ~FileAttributes.ReadOnly; + File.SetAttributes(fileName, fileAttributes); + try + { + File.Delete(fileName); + } + catch (Exception) + { + } + } + } + + public static bool FileExistsInFolder(string filePath, string destFolder) + { + var text = Path.Combine(destFolder, Path.GetFileName(filePath)); + return File.Exists(text); + } + + public static string CopyFileToFolder(string sourceFilePath, string destFolder, bool onlyCopyRelated, List allCopiedFiles) + { + if (!File.Exists(sourceFilePath)) + { + return null; + } + var directoryName = Path.GetDirectoryName(sourceFilePath); + if (onlyCopyRelated) + { + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(sourceFilePath); + var text = $"{fileNameWithoutExtension}.*"; + var files = Directory.GetFiles(directoryName, text, SearchOption.TopDirectoryOnly); + foreach (var text2 in files) + { + var fileName = Path.GetFileName(text2); + var text3 = Path.Combine(destFolder, fileName); + var flag = CopyFile(text2, text3); + if (flag) + { + var fileInfo = new FileInfo(text3); + allCopiedFiles.Add(fileInfo); + } + } + } + else + { + var folderSize = GetFolderSize(directoryName); + if (folderSize > 50L) + { + var result = FolderTooBigDialog.Show(directoryName, folderSize); + switch (result) + { + case MessageBoxResult.Yes: + CopyDirectory(directoryName, destFolder, allCopiedFiles); + break; + case MessageBoxResult.No: + CopyFileToFolder(sourceFilePath, destFolder, true, allCopiedFiles); + break; + default: + return null; + } + } + else + { + CopyDirectory(directoryName, destFolder, allCopiedFiles); + } + } + var text4 = Path.Combine(destFolder, Path.GetFileName(sourceFilePath)); + if (File.Exists(text4)) + { + return text4; + } + return null; + } + + public static bool CopyFile(string sourceFilename, string destinationFilename) + { + if (!File.Exists(sourceFilename)) + { + return false; + } + var fileAttributes = File.GetAttributes(sourceFilename) & ~FileAttributes.ReadOnly; + File.SetAttributes(sourceFilename, fileAttributes); + if (File.Exists(destinationFilename)) + { + var fileAttributes2 = File.GetAttributes(destinationFilename) & ~FileAttributes.ReadOnly; + File.SetAttributes(destinationFilename, fileAttributes2); + File.Delete(destinationFilename); + } + try + { + if (!Directory.Exists(Path.GetDirectoryName(destinationFilename))) + { + Directory.CreateDirectory(Path.GetDirectoryName(destinationFilename)); + } + File.Copy(sourceFilename, destinationFilename, true); + } + catch (Exception) + { + return false; + } + return File.Exists(destinationFilename); + } + + public static void CopyDirectory(string sourceDir, string desDir, List allCopiedFiles) + { + try + { + var directories = Directory.GetDirectories(sourceDir, "*.*", SearchOption.AllDirectories); + foreach (var text in directories) + { + var text2 = text.Replace(sourceDir, string.Empty); + var text3 = desDir + text2; + if (!Directory.Exists(text3)) + { + Directory.CreateDirectory(text3); + } + } + var files = Directory.GetFiles(sourceDir, "*.*", SearchOption.AllDirectories); + foreach (var text4 in files) + { + var text5 = text4.Replace(sourceDir, string.Empty); + var text6 = desDir + text5; + if (!Directory.Exists(Path.GetDirectoryName(text6))) + { + Directory.CreateDirectory(Path.GetDirectoryName(text6)); + } + if (CopyFile(text4, text6)) + { + allCopiedFiles.Add(new FileInfo(text6)); + } + } + } + catch (Exception) + { + } + } + + public static long GetFolderSize(string folderPath) + { + var directoryInfo = new DirectoryInfo(folderPath); + var num = 0L; + foreach (var fileSystemInfo in directoryInfo.GetFileSystemInfos()) + { + if (fileSystemInfo is FileInfo) + { + num += ((FileInfo)fileSystemInfo).Length; + } + else + { + num += GetFolderSize(fileSystemInfo.FullName); + } + } + return num / 1024L / 1024L; + } + + private const string TempFolderName = "RevitAddins"; + } +} diff --git a/AddInManager/FolderTooBigDialog.cs b/AddInManager/FolderTooBigDialog.cs new file mode 100644 index 0000000..4456102 --- /dev/null +++ b/AddInManager/FolderTooBigDialog.cs @@ -0,0 +1,24 @@ +using AddInManager.Properties; + +using System; +using System.Text; +using System.Windows; + +namespace AddInManager +{ + public abstract class FolderTooBigDialog + { + public static MessageBoxResult Show(string folderPath, long sizeInMB) + { + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine($"文件夹[{folderPath}]"); + stringBuilder.AppendLine($"有{sizeInMB}MB大小"); + stringBuilder.AppendLine("AddinManager尝试复制所有文件到临时文件夹"); + stringBuilder.AppendLine("选择[Yes]复制所有文件到临时文件夹"); + stringBuilder.AppendLine("选择[No]仅复制测试DLL文件到临时文件夹"); + stringBuilder.AppendLine("选择[Cancel]取消操作"); + var text = stringBuilder.ToString(); + return MessageBox.Show(text, Resources.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Information, MessageBoxResult.Yes); + } + } +} diff --git a/AddInManager/IAddinNode.cs b/AddInManager/IAddinNode.cs new file mode 100644 index 0000000..f05171d --- /dev/null +++ b/AddInManager/IAddinNode.cs @@ -0,0 +1,11 @@ +using System; + +namespace AddInManager +{ + public interface IAddinNode + { + bool Save { get; set; } + + bool Hidden { get; set; } + } +} diff --git a/AddInManager/IniFile.cs b/AddInManager/IniFile.cs new file mode 100644 index 0000000..1fc7866 --- /dev/null +++ b/AddInManager/IniFile.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace AddInManager +{ + public class IniFile + { + public string FilePath { get; } + + public IniFile(string filePath) + { + FilePath = filePath; + if (!File.Exists(FilePath)) + { + FileUtils.CreateFile(FilePath); + FileUtils.SetWriteable(FilePath); + } + } + + public void WriteSection(string iniSection) + { + WritePrivateProfileSection(iniSection, null, FilePath); + } + + public void Write(string iniSection, string iniKey, object iniValue) + { + WritePrivateProfileString(iniSection, iniKey, iniValue.ToString(), FilePath); + } + + public string ReadString(string iniSection, string iniKey) + { + var stringBuilder = new StringBuilder(255); + GetPrivateProfileString(iniSection, iniKey, string.Empty, stringBuilder, 255, FilePath); + return stringBuilder.ToString(); + } + + public int ReadInt(string iniSection, string iniKey) + { + return GetPrivateProfileInt(iniSection, iniKey, 0, FilePath); + } + + [DllImport("kernel32.dll")] + private static extern int WritePrivateProfileSection(string lpAppName, string lpString, string lpFileName); + + [DllImport("kernel32", CharSet = CharSet.Auto)] + private static extern int WritePrivateProfileString(string section, string key, string val, string filePath); + + [DllImport("kernel32")] + private static extern int GetPrivateProfileInt(string section, string key, int def, string filePath); + + [DllImport("kernel32", CharSet = CharSet.Auto)] + private static extern int GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder retVal, int size, string filePath); + } +} diff --git a/AddInManager/ManifestFile.cs b/AddInManager/ManifestFile.cs new file mode 100644 index 0000000..ccf0d2a --- /dev/null +++ b/AddInManager/ManifestFile.cs @@ -0,0 +1,392 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using System.Xml; + +namespace AddInManager +{ + public class ManifestFile + { + public ManifestFile() + { + Local = false; + Applications = new List(); + Commands = new List(); + } + + public ManifestFile(string fileName) : this() + { + FileName = fileName; + if (!string.IsNullOrEmpty(m_filePath)) return; + var text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AddIn"); + m_filePath = Path.Combine(text, FileName); + } + + public ManifestFile(bool local) : this() + { + Local = local; + } + + public void Load() + { + m_xmlDoc = new XmlDocument(); + m_xmlDoc.Load(m_filePath); + var documentElement = m_xmlDoc.DocumentElement; + if (!documentElement.Name.Equals(ROOT_NODE)) + { + throw new System.ArgumentException(INCORRECT_NODE); + } + if (documentElement.ChildNodes.Count == 0) + { + throw new System.ArgumentException(EMPTY_ADDIN); + } + Applications.Clear(); + Commands.Clear(); + foreach (var obj in documentElement.ChildNodes) + { + var xmlNode = (XmlNode)obj; + if (!xmlNode.Name.Equals(ADDIN_NODE) || xmlNode.Attributes.Count != 1) + { + throw new System.ArgumentException(INCORRECT_NODE); + } + var xmlAttribute = xmlNode.Attributes[0]; + if (xmlAttribute.Value.Equals(APPLICATION_NODE)) + { + ParseExternalApplications(xmlNode); + } + else + { + if (!xmlAttribute.Value.Equals(COMMAND_NODE)) + { + throw new System.ArgumentException(INCORRECT_NODE); + } + ParseExternalCommands(xmlNode); + } + } + } + + public void Save() + { + SaveAs(m_filePath); + } + + public void SaveAs(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new System.ArgumentNullException(FILENAME_NULL_OR_EMPTY); + } + if (!filePath.ToLower().EndsWith(ADDIN)) + { + throw new System.ArgumentException(FILENAME_INCORRECT_WARNING + filePath); + } + var directoryName = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + var fileInfo = new FileInfo(filePath); + m_xmlDoc = new XmlDocument(); + CreateXMLForManifest(); + if (File.Exists(filePath)) + { + File.SetAttributes(filePath, FileAttributes.Normal); + } + TextWriter textWriter = new StreamWriter(filePath, false, Encoding.UTF8); + var xmlTextWriter = new XmlTextWriter(textWriter); + xmlTextWriter.Formatting = Formatting.Indented; + m_xmlDoc.Save(xmlTextWriter); + xmlTextWriter.Close(); + m_filePath = fileInfo.FullName; + FileName = Path.GetFileName(fileInfo.FullName); + } + + public string FileName { get; set; } + + public bool Local { get; set; } + + public string FilePath + { + get + { + if (string.IsNullOrEmpty(m_filePath)) + { + var text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AddIn"); + m_filePath = Path.Combine(text, "AimInternal.ini"); + } + return m_filePath; + } + set => m_filePath = value; + } + + public List Applications { get; set; } + + public List Commands { get; set; } + + private XmlDocument CreateXMLForManifest() + { + var xmlNode = m_xmlDoc.AppendChild(m_xmlDoc.CreateElement(ROOT_NODE)); + foreach (var addinItem in Applications) + { + var xmlElement = m_xmlDoc.CreateElement(ADDIN_NODE); + xmlElement.SetAttribute(TYPE_ATTRIBUTE, APPLICATION_NODE); + xmlNode.AppendChild(xmlElement); + AddApplicationToXmlElement(xmlElement, addinItem); + var xmlElement2 = m_xmlDoc.CreateElement(VENDORID); + xmlElement2.InnerText = "ADSK"; + xmlElement.AppendChild(xmlElement2); + xmlElement2 = m_xmlDoc.CreateElement(VENDORDESCRIPTION); + xmlElement2.InnerText = "Autodesk, www.autodesk.com"; + xmlElement.AppendChild(xmlElement2); + } + foreach (var addinItem2 in Commands) + { + var xmlElement3 = m_xmlDoc.CreateElement(ADDIN_NODE); + xmlElement3.SetAttribute(TYPE_ATTRIBUTE, COMMAND_NODE); + xmlNode.AppendChild(xmlElement3); + AddCommandToXmlElement(xmlElement3, addinItem2); + var xmlElement4 = m_xmlDoc.CreateElement(VENDORID); + xmlElement4.InnerText = "ADSK"; + xmlElement3.AppendChild(xmlElement4); + xmlElement4 = m_xmlDoc.CreateElement(VENDORDESCRIPTION); + xmlElement4.InnerText = "Autodesk, www.autodesk.com"; + xmlElement3.AppendChild(xmlElement4); + } + return m_xmlDoc; + } + + private void AddAddInItemToXmlElement(XmlElement xmlEle, AddinItem addinItem) + { + if (!string.IsNullOrEmpty(addinItem.AssemblyPath)) + { + var xmlElement = m_xmlDoc.CreateElement(ASSEMBLY); + if (Local) + { + xmlElement.InnerText = addinItem.AssemblyName; + } + else + { + xmlElement.InnerText = addinItem.AssemblyPath; + } + xmlEle.AppendChild(xmlElement); + } + if (!string.IsNullOrEmpty(addinItem.ClientIdString)) + { + var xmlElement2 = m_xmlDoc.CreateElement(CLIENTID); + xmlElement2.InnerText = addinItem.ClientIdString; + xmlEle.AppendChild(xmlElement2); + } + if (!string.IsNullOrEmpty(addinItem.FullClassName)) + { + var xmlElement3 = m_xmlDoc.CreateElement(FULLCLASSNAME); + xmlElement3.InnerText = addinItem.FullClassName; + xmlEle.AppendChild(xmlElement3); + } + } + + private void AddApplicationToXmlElement(XmlElement appEle, AddinItem currentApp) + { + if (!string.IsNullOrEmpty(currentApp.Name)) + { + var xmlElement = m_xmlDoc.CreateElement(NAME_NODE); + xmlElement.InnerText = currentApp.Name; + appEle.AppendChild(xmlElement); + } + AddAddInItemToXmlElement(appEle, currentApp); + } + + private void AddCommandToXmlElement(XmlElement commandEle, AddinItem command) + { + AddAddInItemToXmlElement(commandEle, command); + XmlElement xmlElement; + if (!string.IsNullOrEmpty(command.Name)) + { + xmlElement = m_xmlDoc.CreateElement(TEXT); + xmlElement.InnerText = command.Name; + commandEle.AppendChild(xmlElement); + } + if (!string.IsNullOrEmpty(command.Description)) + { + xmlElement = m_xmlDoc.CreateElement(DESCRIPTION); + xmlElement.InnerText = command.Description; + commandEle.AppendChild(xmlElement); + } + var text = command.VisibilityMode.ToString(); + if (!string.IsNullOrEmpty(text)) + { + text = text.Replace(",", " |"); + } + xmlElement = m_xmlDoc.CreateElement(VISIBILITYMODE); + xmlElement.InnerText = text; + commandEle.AppendChild(xmlElement); + } + + private void ParseExternalApplications(XmlNode nodeApplication) + { + var addinItem = new AddinItem(AddinType.Application); + parseApplicationItems(addinItem, nodeApplication); + Applications.Add(addinItem); + } + + private void ParseExternalCommands(XmlNode nodeCommand) + { + var addinItem = new AddinItem(AddinType.Command); + ParseCommandItems(addinItem, nodeCommand); + Commands.Add(addinItem); + } + + private void parseApplicationItems(AddinItem addinApp, XmlNode nodeAddIn) + { + ParseAddInItem(addinApp, nodeAddIn); + var xmlElement = nodeAddIn[NAME_NODE]; + if (xmlElement != null && !string.IsNullOrEmpty(xmlElement.InnerText)) + { + addinApp.Name = xmlElement.InnerText; + } + } + + private void ParseCommandItems(AddinItem command, XmlNode nodeAddIn) + { + ParseAddInItem(command, nodeAddIn); + var xmlElement = nodeAddIn[TEXT]; + if (xmlElement != null) + { + command.Name = xmlElement.InnerText; + } + xmlElement = nodeAddIn[DESCRIPTION]; + if (xmlElement != null) + { + command.Description = xmlElement.InnerText; + } + xmlElement = nodeAddIn[VISIBILITYMODE]; + if (xmlElement != null && !string.IsNullOrEmpty(xmlElement.InnerText)) + { + command.VisibilityMode = parseVisibilityMode(xmlElement.InnerText); + } + } + + private void ParseAddInItem(AddinItem addinItem, XmlNode nodeAddIn) + { + var xmlElement = nodeAddIn[ASSEMBLY]; + if (xmlElement != null) + { + if (Local) + { + addinItem.AssemblyName = xmlElement.InnerText; + } + else + { + addinItem.AssemblyPath = xmlElement.InnerText; + } + } + xmlElement = nodeAddIn[CLIENTID]; + if (xmlElement != null) + { + try + { + if (!string.IsNullOrEmpty(xmlElement.InnerText)) + { + addinItem.ClientId = new Guid(xmlElement.InnerText); + } + else + { + addinItem.ClientId = Guid.Empty; + } + } + catch (Exception) + { + addinItem.ClientId = Guid.Empty; + addinItem.ClientIdString = xmlElement.InnerText; + } + } + xmlElement = nodeAddIn[FULLCLASSNAME]; + if (xmlElement != null) + { + addinItem.FullClassName = xmlElement.InnerText; + } + } + + private VisibilityMode parseVisibilityMode(string visibilityModeString) + { + var visibilityMode = VisibilityMode.AlwaysVisible; + VisibilityMode visibilityMode3; + try + { + var text = "|"; + var array = text.ToCharArray(); + var array2 = visibilityModeString.Replace(" | ", "|").Split(array); + foreach (var text2 in array2) + { + var visibilityMode2 = (VisibilityMode)Enum.Parse(typeof(VisibilityMode), text2); + visibilityMode |= visibilityMode2; + } + visibilityMode3 = visibilityMode; + } + catch (Exception) + { + throw new System.ArgumentException(UNKNOW_VISIBILITYMODE); + } + return visibilityMode3; + } + + private string getFullPath(string fileName) + { + FileInfo fileInfo = null; + try + { + fileInfo = new FileInfo(fileName); + } + catch (Exception ex) + { + throw new System.ArgumentException(fileName + Environment.NewLine + ex.ToString()); + } + return fileInfo.FullName; + } + + private string m_filePath; + + private string ROOT_NODE = "RevitAddIns"; + + private string ADDIN_NODE = "AddIn"; + + private string APPLICATION_NODE = "Application"; + + private string COMMAND_NODE = "Command"; + + private string TYPE_ATTRIBUTE = "Type"; + + private string INCORRECT_NODE = "incorrect node in addin file!"; + + private string EMPTY_ADDIN = "empty addin file!"; + + private string ASSEMBLY = "Assembly"; + + private string CLIENTID = "ClientId"; + + private string FULLCLASSNAME = "FullClassName"; + + private string NAME_NODE = "Name"; + + private string TEXT = "Text"; + + private string DESCRIPTION = "Description"; + + private string VENDORID = "VendorId"; + + private string VENDORDESCRIPTION = "VendorDescription"; + + private string VISIBILITYMODE = "VisibilityMode"; + + private string UNKNOW_VISIBILITYMODE = "Unrecognizable VisibilityMode!"; + + private string ADDIN = ".addin"; + + private string FILENAME_INCORRECT_WARNING = "File name is incorrect, not .addin file ."; + + private string FILENAME_NULL_OR_EMPTY = "File name for RevitAddInManifest is null or empty"; + + private XmlDocument m_xmlDoc; + } +} diff --git a/AddInManager/Properties/Resources.Designer.cs b/AddInManager/Properties/Resources.Designer.cs new file mode 100644 index 0000000..0f42d68 --- /dev/null +++ b/AddInManager/Properties/Resources.Designer.cs @@ -0,0 +1,137 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace AddInManager.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AddInManager.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 AddinManager 的本地化字符串。 + /// + internal static string AppName { + get { + return ResourceManager.GetString("AppName", resourceCulture); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap Develop_16 { + get { + object obj = ResourceManager.GetObject("Develop_16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap Develop_32 { + get { + object obj = ResourceManager.GetObject("Develop_32", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找类似 取消加载 的本地化字符串。 + /// + internal static string LoadCancelled { + get { + return ResourceManager.GetString("LoadCancelled", resourceCulture); + } + } + + /// + /// 查找类似 加载失败 的本地化字符串。 + /// + internal static string LoadFailed { + get { + return ResourceManager.GetString("LoadFailed", resourceCulture); + } + } + + /// + /// 查找类似 Revit Addin Files(*.dll)|*.dll 的本地化字符串。 + /// + internal static string LoadFileFilter { + get { + return ResourceManager.GetString("LoadFileFilter", resourceCulture); + } + } + + /// + /// 查找类似 加载成功 的本地化字符串。 + /// + internal static string LoadSucceed { + get { + return ResourceManager.GetString("LoadSucceed", resourceCulture); + } + } + + /// + /// 查找类似 未选中项 的本地化字符串。 + /// + internal static string NoItemsSelected { + get { + return ResourceManager.GetString("NoItemsSelected", resourceCulture); + } + } + } +} diff --git a/AddInManager/Properties/Resources.resx b/AddInManager/Properties/Resources.resx new file mode 100644 index 0000000..c51c79b --- /dev/null +++ b/AddInManager/Properties/Resources.resx @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + AddinManager + + + + ..\Resources\Develop_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\Develop_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + 取消加载 + + + 加载失败 + + + Revit Addin Files(*.dll)|*.dll + + + 加载成功 + + + 未选中项 + + \ No newline at end of file diff --git a/AddInManager/Properties/Settings.Designer.cs b/AddInManager/Properties/Settings.Designer.cs new file mode 100644 index 0000000..d24e934 --- /dev/null +++ b/AddInManager/Properties/Settings.Designer.cs @@ -0,0 +1,38 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace AddInManager.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("RevitAddInManager")] + public string AppFolder { + get { + return ((string)(this["AppFolder"])); + } + set { + this["AppFolder"] = value; + } + } + } +} diff --git a/AddInManager/Properties/Settings.settings b/AddInManager/Properties/Settings.settings new file mode 100644 index 0000000..7d75a79 --- /dev/null +++ b/AddInManager/Properties/Settings.settings @@ -0,0 +1,9 @@ + + + + + + RevitAddInManager + + + \ No newline at end of file diff --git a/AddInManager/RelayCommand.cs b/AddInManager/RelayCommand.cs new file mode 100644 index 0000000..2d68ceb --- /dev/null +++ b/AddInManager/RelayCommand.cs @@ -0,0 +1,32 @@ +using System; +using System.Windows.Input; + +namespace AddInManager +{ + internal class RelayCommand : ICommand + { + private Action execute; + private Func canExecute; + + public event EventHandler CanExecuteChanged; + + + public RelayCommand(Action execute, Func canExecute = null) + { + if (execute == null) + throw new System.ArgumentNullException(); + this.execute = execute; + this.canExecute = canExecute; + } + + public bool CanExecute(object parameter) + { + return canExecute == null || canExecute(parameter); + } + + public void Execute(object parameter) + { + execute(parameter); + } + } +} \ No newline at end of file diff --git a/AddInManager/Resources/Develop_16.png b/AddInManager/Resources/Develop_16.png new file mode 100644 index 0000000000000000000000000000000000000000..d6eefc3c995ee5009404c9e70879b6293f20a4ba GIT binary patch literal 331 zcmV-R0kr;!P)#ZFC&RD{#4R94D!&O2S%6+qOLbJ_%rT34*}aBl{;Kf#-QSd$Ynw zi2l6Rvuce5NYHKo!jdsI4OWvqf^X7tlLP@DO}oHqa!N1^Lyt+_i2}isR@Du`S`na} ziR`uz;tgM#rnMmeFhw#YVe{x5sfmEqh>@&L=SVjMP18I8kl3^gRuwLedY=6a0i#&y zt6H7kj@U0DQynA0{N~RG38ZO3&hX2$q`+(oXo^*}`kgVY61d_600960Br#UK00006 dNkl27vJgax6(~dTR^;=uLcW@X=t0^j{if^ig&5 zlK~*8Fq&!rEY2WvKp9O*0Ql@ZO2uZg>3}@LrYi{m9Mc;?pUf_5Uk^7D;cFCNjE9G% z&1e*0jE5VE@HGlB#>2zXW;6;wXR;nW*u#xP_!nXAbzzY4ml(yL` zBsRSXVgQ8IBkq0|if$5{o;n(00B(Co9Bb(p+n5dQ+$;ugC#9^v1%Up0NzU$6ch0k= zTZ=oO0pMWUIUp#Gv!%8GjY5#w*+vD31HN1*{X&L#_v*$Yd#~u-fG8pUZ9$S2p2fK1 w4L*QNYX1TN0RR8bdwBo=000I_L_t&o0BVa?oo&XQLjV8(07*qoM6N<$g3f#LT>t<8 literal 0 HcmV?d00001 diff --git a/AddInManager/StaticUtil.cs b/AddInManager/StaticUtil.cs new file mode 100644 index 0000000..494f603 --- /dev/null +++ b/AddInManager/StaticUtil.cs @@ -0,0 +1,29 @@ +using Autodesk.Revit.Attributes; +using Autodesk.Revit.UI; + +using System; +using System.Windows; + +namespace AddInManager +{ + public static class StaticUtil + { + public static void ShowWarning(Exception e) + { + ShowWarning(e.Message); + } + + public static void ShowWarning(string msg) + { + MessageBox.Show(msg, "插件管理", MessageBoxButton.OK, MessageBoxImage.Exclamation); + } + + public static string m_ecFullName = typeof(IExternalCommand).FullName; + + public static string m_eaFullName = typeof(IExternalApplication).FullName; + + public static RegenerationOption m_regenOption; + + public static TransactionMode m_tsactMode= TransactionMode.Manual; + } +} diff --git a/AddInManager/VisibilityMode.cs b/AddInManager/VisibilityMode.cs new file mode 100644 index 0000000..ddc2d5a --- /dev/null +++ b/AddInManager/VisibilityMode.cs @@ -0,0 +1,19 @@ +using System; + +namespace AddInManager +{ + [Flags] + public enum VisibilityMode + { + AlwaysVisible = 0, + NotVisibleInProject = 1, + NotVisibleInFamily = 2, + NotVisibleWhenNoActiveDocument = 4, + NotVisibleInArchitecture = 8, + NotVisibleInStructure = 16, + NotVisibleInMechanical = 32, + NotVisibleInElectrical = 64, + NotVisibleInPlumbing = 128, + NotVisibleInMEP = 224 + } +} diff --git a/AddInManager/Wpf/AssemblySelectorWindow.xaml b/AddInManager/Wpf/AssemblySelectorWindow.xaml new file mode 100644 index 0000000..c408db1 --- /dev/null +++ b/AddInManager/Wpf/AssemblySelectorWindow.xaml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AddInManager/Wpf/MainWindow.xaml.cs b/AddInManager/Wpf/MainWindow.xaml.cs new file mode 100644 index 0000000..0d61d3b --- /dev/null +++ b/AddInManager/Wpf/MainWindow.xaml.cs @@ -0,0 +1,1274 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +using Microsoft.Win32; + +namespace AddInManager.Wpf +{ + public partial class MainWindow : Window + { + private readonly AIM m_aim; + private List m_allCommandItems; // 存储所有命令项用于搜索 + + public MainWindow(AIM aim) + { + aim.AddinManager.Commands?.AddinDict?.Clear(); + aim.AddinManager.Applications?.AddinDict?.Clear(); + aim.AddinManager.ReadAddinsFromAimIni(); + + InitializeComponent(); + m_aim = aim; + Title = Properties.Resources.AppName; + Loaded += MainWindow_Loaded; + m_allCommandItems = new List(); + } + + private void MainWindow_Loaded(object sender, RoutedEventArgs e) + { + CommandsTreeView_RefreshData(); + ApplicationsTreeView_RefreshData(); + DisableControl(); + removeButton.IsEnabled = false; + + if (searchTextBox.Template.FindName("PART_ClearButton", searchTextBox) is Button clearButton) + { + clearButton.Click += ClearSearchButton_Click; + } + + if (commandsTreeView.Items.Count > 0) + { + var firstItem = commandsTreeView.Items[0] as TreeViewItem; + if (firstItem != null) + { + firstItem.IsSelected = true; + firstItem.BringIntoView(); + firstItem.Focus(); // 确保获得焦点 + } + } + } + + #region 搜索功能 + + #region 搜索功能 + + private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + var searchText = searchTextBox.Text?.Trim().ToLower(); + + if (string.IsNullOrEmpty(searchText)) + { + // 如果搜索框为空,恢复所有项的初始状态 + RestoreAllItems(); + } + else + { + // 执行搜索过滤 + FilterCommandsTreeView(searchText); + } + } + + private void ClearSearchButton_Click(object sender, RoutedEventArgs e) + { + searchTextBox.Clear(); + searchTextBox.Focus(); + RestoreAllItems(); + } + + // (重写) 核心过滤逻辑 + private void FilterCommandsTreeView(string searchText) + { + foreach (TreeViewItem parentItem in commandsTreeView.Items) + { + var parentShouldBeVisible = false; + + // 1. 检查父节点自身是否匹配 + var parentText = GetHeaderText(parentItem); + var parentMatches = !string.IsNullOrEmpty(parentText) && parentText.ToLower().Contains(searchText); + + // 2. 遍历所有子节点 + foreach (TreeViewItem childItem in parentItem.Items) + { + var childText = GetHeaderText(childItem); + var childMatches = !string.IsNullOrEmpty(childText) && childText.ToLower().Contains(searchText); + + // 如果子节点匹配,或者父节点匹配,则该子节点可见 + if (childMatches || parentMatches) + { + childItem.Visibility = Visibility.Visible; + parentShouldBeVisible = true; // 只要有任何一个子节点可见,父节点就必须可见 + } + else + { + childItem.Visibility = Visibility.Collapsed; + } + } + + // 3. 根据自身匹配或子节点可见性,来决定父节点的最终状态 + if (parentShouldBeVisible || parentMatches) + { + parentItem.Visibility = Visibility.Visible; + parentItem.IsExpanded = true; // 展开以显示匹配的子项 + } + else + { + parentItem.Visibility = Visibility.Collapsed; + } + } + } + + // (重写) 恢复所有项的可见性 + private void RestoreAllItems() + { + foreach (TreeViewItem parentItem in commandsTreeView.Items) + { + parentItem.Visibility = Visibility.Visible; + // 注意:这里我们不再强制展开所有项,除非您希望如此。 + // 如果需要恢复时全部展开,可以取消下面这行的注释。 + // parentItem.IsExpanded = true; + + foreach (TreeViewItem childItem in parentItem.Items) + { + childItem.Visibility = Visibility.Visible; + } + } + } + + // (新增) 辅助方法,用于从Header(StackPanel)中获取真实的文本 + private string GetHeaderText(TreeViewItem item) + { + if (item?.Header is StackPanel stackPanel) + { + // 遍历StackPanel中的所有子元素 + foreach (var child in stackPanel.Children) + { + // 找到TextBlock并返回其文本 + if (child is TextBlock textBlock) + { + return textBlock.Text; + } + } + } + // 如果Header不是预期的格式,返回空字符串 + return string.Empty; + } + + #endregion + + #endregion + + #region 工具栏功能 + + private void SelectAllButton_Click(object sender, RoutedEventArgs e) + { + SetAllItemsSelection(commandsTreeView, true); + } + + private void SelectNoneButton_Click(object sender, RoutedEventArgs e) + { + SetAllItemsSelection(commandsTreeView, false); + } + + private void ExpandAllButton_Click(object sender, RoutedEventArgs e) + { + SetAllItemsExpanded(commandsTreeView, true); + } + + private void CollapseAllButton_Click(object sender, RoutedEventArgs e) + { + SetAllItemsExpanded(commandsTreeView, false); + } + private void AppSelectAllButton_Click(object sender, RoutedEventArgs e) + { + SetAllItemsSelection(applicationsTreeView, true); + } + + private void AppSelectNoneButton_Click(object sender, RoutedEventArgs e) + { + SetAllItemsSelection(applicationsTreeView, false); + } + private void AppExpandAllButton_Click(object sender, RoutedEventArgs e) + { + SetAllItemsExpanded(applicationsTreeView, true); + } + + private void AppCollapseAllButton_Click(object sender, RoutedEventArgs e) + { + SetAllItemsExpanded(applicationsTreeView, false); + } + + private void SetAllItemsExpanded(TreeView treeView, bool isExpanded) + { + foreach (TreeViewItem item in treeView.Items) + { + SetTreeViewItemExpanded(item, isExpanded); + } + } + + private void SetTreeViewItemExpanded(TreeViewItem item, bool isExpanded) + { + if (item == null) return; + + item.IsExpanded = isExpanded; + + // 递归处理子项 + foreach (TreeViewItem childItem in item.Items) + { + SetTreeViewItemExpanded(childItem, isExpanded); + } + } + + private void SetAllItemsSelection(TreeView treeView, bool isSelected) + { + foreach (TreeViewItem parentItem in treeView.Items) + { + if (parentItem.Visibility == Visibility.Visible) + { + // 设置父节点CheckBox + SetItemCheckBox(parentItem, isSelected); + + if (parentItem.Tag is Addin addin) + { + addin.Save = isSelected; + } + + foreach (TreeViewItem childItem in parentItem.Items) + { + if (childItem.Visibility == Visibility.Visible) + { + // 设置子节点CheckBox + SetItemCheckBox(childItem, isSelected); + + if (childItem.Tag is AddinItem addinItem) + { + addinItem.Save = isSelected; + } + } + } + } + } + + // 保存更改 + m_aim.AddinManager.SaveToAimIni(); + + ShowStatusLabel(isSelected ? "已选择所有可见项目" : "已取消选择所有项目"); + } + + private void InvertAllItemsSelection() + { + foreach (TreeViewItem parentItem in commandsTreeView.Items) + { + if (parentItem.Visibility == Visibility.Visible) + { + // 反转父节点CheckBox + var currentState = GetItemCheckBoxState(parentItem); + SetItemCheckBox(parentItem, !currentState); + + if (parentItem.Tag is Addin addin) + { + addin.Save = !addin.Save; + } + + foreach (TreeViewItem childItem in parentItem.Items) + { + if (childItem.Visibility == Visibility.Visible) + { + // 反转子节点CheckBox + var childCurrentState = GetItemCheckBoxState(childItem); + SetItemCheckBox(childItem, !childCurrentState); + + if (childItem.Tag is AddinItem addinItem) + { + addinItem.Save = !addinItem.Save; + } + } + } + } + } + + // 保存更改 + m_aim.AddinManager.SaveToAimIni(); + + ShowStatusLabel("已反转所有可见项目的选择状态"); + } + + private void SetItemCheckBox(TreeViewItem item, bool isChecked) + { + var checkBox = FindCheckBoxInTreeViewItem(item); + if (checkBox != null) + { + checkBox.IsChecked = isChecked; + } + } + + private bool GetItemCheckBoxState(TreeViewItem item) + { + var checkBox = FindCheckBoxInTreeViewItem(item); + return checkBox?.IsChecked == true; + } + + private CheckBox FindCheckBoxInTreeViewItem(TreeViewItem item) + { + if (item?.Header is StackPanel stackPanel) + { + foreach (var child in stackPanel.Children) + { + if (child is CheckBox checkBox) + { + return checkBox; + } + } + } + return null; + } + + private void TreeViewCheckBox_Changed(object sender, RoutedEventArgs e) + { + if (sender is CheckBox checkBox) + { + var item = FindTreeViewItemFromCheckBox(checkBox); + if (item == null) return; + + var isChecked = checkBox.IsChecked == true; + + // 更新对应的数据模型 + if (item.Tag is Addin addin) + { + addin.Save = isChecked; + + // 父节点状态变化时,同步更新所有子节点 + UpdateChildrenCheckBoxes(item, isChecked); + } + else if (item.Tag is AddinItem addinItem) + { + addinItem.Save = isChecked; + + // 子节点状态变化时,检查是否需要更新父节点状态 + UpdateParentCheckBoxState(item); + } + + // 保存更改 + m_aim.AddinManager.SaveToAimIni(); + } + } + + private void UpdateChildrenCheckBoxes(TreeViewItem parentItem, bool isChecked) + { + foreach (TreeViewItem childItem in parentItem.Items) + { + // 更新子节点CheckBox状态 + var childCheckBox = FindCheckBoxInTreeViewItem(childItem); + if (childCheckBox != null) + { + // 临时移除事件处理,避免递归调用 + childCheckBox.Checked -= TreeViewCheckBox_Changed; + childCheckBox.Unchecked -= TreeViewCheckBox_Changed; + + childCheckBox.IsChecked = isChecked; + + // 重新添加事件处理 + childCheckBox.Checked += TreeViewCheckBox_Changed; + childCheckBox.Unchecked += TreeViewCheckBox_Changed; + } + + // 更新子节点数据模型 + if (childItem.Tag is AddinItem addinItem) + { + addinItem.Save = isChecked; + } + } + } + + private void UpdateParentCheckBoxState(TreeViewItem childItem) + { + if (childItem.Parent is TreeViewItem parentItem) + { + var allChecked = true; + var anyChecked = false; + + // 检查所有子节点的状态 + foreach (TreeViewItem sibling in parentItem.Items) + { + var siblingCheckBox = FindCheckBoxInTreeViewItem(sibling); + if (siblingCheckBox != null) + { + var isChecked = siblingCheckBox.IsChecked == true; + if (isChecked) + { + anyChecked = true; + } + else + { + allChecked = false; + } + } + } + + // 更新父节点CheckBox状态 + var parentCheckBox = FindCheckBoxInTreeViewItem(parentItem); + if (parentCheckBox != null) + { + // 临时移除事件处理,避免递归调用 + parentCheckBox.Checked -= TreeViewCheckBox_Changed; + parentCheckBox.Unchecked -= TreeViewCheckBox_Changed; + + if (allChecked) + { + parentCheckBox.IsChecked = true; + } + else if (anyChecked) + { + parentCheckBox.IsChecked = null; // 部分选中状态 + } + else + { + parentCheckBox.IsChecked = false; + } + + // 重新添加事件处理 + parentCheckBox.Checked += TreeViewCheckBox_Changed; + parentCheckBox.Unchecked += TreeViewCheckBox_Changed; + } + + // 更新父节点数据模型 + if (parentItem.Tag is Addin parentAddin) + { + parentAddin.Save = allChecked || anyChecked; + } + } + } + + #endregion + + private void LoadButton_Click(object sender, RoutedEventArgs e) + { + var openFileDialog = new OpenFileDialog + { + Filter = Properties.Resources.LoadFileFilter + }; + + if (openFileDialog.ShowDialog() != true) + { + ShowStatusError(Properties.Resources.LoadCancelled); + return; + } + + var fileName = openFileDialog.FileName; + var addinType = m_aim.AddinManager.LoadAddin(fileName); + + if (addinType == AddinType.Invalid) + { + ShowStatusError(Properties.Resources.LoadFailed + fileName); + return; + } + + ShowStatusLabel(Properties.Resources.LoadSucceed + fileName); + m_aim.AddinManager.SaveToAimIni(); + CommandsTreeView_RefreshData(); + ApplicationsTreeView_RefreshData(); + + switch (addinType) + { + case AddinType.Command: + case AddinType.Mixed: + externalToolsTabControl.SelectedItem = commandsTabPage; + commandsTreeView.Focus(); + break; + case AddinType.Application: + externalToolsTabControl.SelectedItem = applicationsTabPage; + applicationsTreeView.Focus(); + break; + } + + notesTextBox.Text = string.Empty; + RemoveButton_RefreshData(); + } + + private void CommandsTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + var item = e.NewValue as TreeViewItem; + if (item?.Tag == null) + { + m_aim.ActiveCmd = null; + m_aim.ActiveCmdItem = null; + RefreshData(); + return; + } + + if (item.Tag is Addin addin) + { + m_aim.ActiveCmd = addin; + m_aim.ActiveCmdItem = null; + notesTextBox.Text = m_aim.ActiveCmd.FilePath; + } + else if (item.Tag is AddinItem addinItem && item.Parent is TreeViewItem parentItem) + { + if (parentItem.Tag is Addin parentAddin) + { + m_aim.ActiveCmd = parentAddin; + m_aim.ActiveCmdItem = addinItem; + notesTextBox.Text = m_aim.ActiveCmd.FilePath; + } + } + + RefreshData(); + RemoveButton_RefreshData(); + } + + private void ApplicationsTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + var item = e.NewValue as TreeViewItem; + if (item?.Tag == null) + { + m_aim.ActiveApp = null; + m_aim.ActiveAppItem = null; + DisableControl(); + RemoveButton_RefreshData(); + return; + } + + if (item.Tag is Addin addin) + { + m_aim.ActiveApp = addin; + m_aim.ActiveAppItem = null; + notesTextBox.Text = m_aim.ActiveApp.FilePath; + } + else if (item.Tag is AddinItem addinItem && item.Parent is TreeViewItem parentItem) + { + if (parentItem.Tag is Addin parentAddin) + { + m_aim.ActiveApp = parentAddin; + m_aim.ActiveAppItem = addinItem; + notesTextBox.Text = m_aim.ActiveApp.FilePath; + } + } + + DisableControl(); + RemoveButton_RefreshData(); + } + + private void ApplicationsTreeView_MouseUp(object sender, MouseButtonEventArgs e) + { + RemoveButton_RefreshData(); + } + + private void ApplicationsTreeView_LostFocus(object sender, RoutedEventArgs e) + { + // 检查焦点是否转移到了removeButton + var focusedElement = Keyboard.FocusedElement as FrameworkElement; + if (focusedElement != removeButton) + { + DisableControl(); + removeButton.IsEnabled = false; + } + } + + private void DisableControl() + { + nametextBox.Text = ""; + descriptionTextBox.Text = ""; + nametextBox.IsEnabled = false; + descriptionTextBox.IsEnabled = false; + runButton.IsEnabled = false; + } + + private void ExternalToolsTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (externalToolsTabControl.SelectedIndex == 1) + { + DisableControl(); + applicationsTreeView.Focus(); + RemoveButton_RefreshData(); + return; + } + commandsTreeView.Focus(); + RefreshData(); + RemoveButton_RefreshData(); // 添加这行 + } + + private void RefreshData() + { + var selectedItem = commandsTreeView.SelectedItem as TreeViewItem; + if (selectedItem != null && !HasChildren(selectedItem)) + { + if (m_aim.ActiveCmdItem != null) + { + nametextBox.Text = m_aim.ActiveCmdItem.Name; + descriptionTextBox.Text = m_aim.ActiveCmdItem.Description; + } + nametextBox.IsEnabled = true; + descriptionTextBox.IsEnabled = true; + runButton.IsEnabled = true; + } + else + { + DisableControl(); + } + RemoveButton_RefreshData(); + } + + private bool HasChildren(TreeViewItem item) + { + return item.Items.Count > 0; + } + + private void RunButton_Click(object sender, RoutedEventArgs e) + { + Run(); + } + + private void Run() + { + try + { + if (m_aim.ActiveCmdItem == null) + { + ShowStatusError("没有选中可执行的命令"); + return; + } + + // 设置对话框结果为成功,这样AIM会知道用户选择了要执行命令 + DialogResult = true; + Close(); + } + catch (Exception ex) + { + ShowStatusError($"准备执行命令时发生错误: {ex.Message}"); + } + } + + // (重写并修复) 移除按钮点击事件 + private void RemoveButton_Click(object sender, RoutedEventArgs e) + { + TreeView activeTreeView; + if (externalToolsTabControl.SelectedIndex == 0) + { + activeTreeView = commandsTreeView; + } + else if (externalToolsTabControl.SelectedIndex == 1) + { + activeTreeView = applicationsTreeView; + } + else + { + return; + } + + if (!(activeTreeView.SelectedItem is TreeViewItem selectedItem)) return; + + // 1. 记录原始索引 + var originalIndex = -1; + TreeViewItem parentItem = null; + var isChildNode = false; + + // 判断是子节点还是父节点 + var parent = VisualTreeHelper.GetParent(selectedItem); + while (parent != null && !(parent is TreeView)) + { + if (parent is TreeViewItem item) + { + parentItem = item; + isChildNode = true; + break; + } + parent = VisualTreeHelper.GetParent(parent); + } + + if (isChildNode) // 如果是子节点 + { + originalIndex = parentItem.Items.IndexOf(selectedItem); + } + else // 如果是父节点 + { + originalIndex = activeTreeView.Items.IndexOf(selectedItem); + } + + // 2. 执行数据移除 + if (externalToolsTabControl.SelectedIndex == 0) + { + m_aim.AddinManager.Commands.RemoveAddIn(m_aim.ActiveCmd); + m_aim.ActiveCmd = null; + m_aim.ActiveCmdItem = null; + } + else + { + m_aim.AddinManager.Applications.RemoveAddIn(m_aim.ActiveApp); + m_aim.ActiveApp = null; + m_aim.ActiveAppItem = null; + } + m_aim.AddinManager.SaveToAimIni(); + + // 3. 刷新UI + CommandsTreeView_RefreshData(); + ApplicationsTreeView_RefreshData(); + + // 4. 计算新的安全索引并选中 + if (activeTreeView.Items.Count > 0) + { + // 简单处理:总是尝试选中一个有效的项 + var newIndex = Math.Max(0, Math.Min(originalIndex, activeTreeView.Items.Count - 1)); + + if (activeTreeView.Items[newIndex] is TreeViewItem itemToSelect) + { + itemToSelect.IsSelected = true; + itemToSelect.BringIntoView(); + activeTreeView.Focus(); + } + } + else + { + // 如果列表空了,清空备注 + notesTextBox.Text = string.Empty; + } + + RemoveButton_RefreshData(); + } + + private void RemoveButton_LostFocus(object sender, RoutedEventArgs e) + { + RemoveButton_RefreshData(); + } + + private void RemoveButton_RefreshData() + { + // 检查当前活动的标签页 + if (externalToolsTabControl.SelectedIndex == 0) // Commands tab + { + // 检查是否有选中的项目 + if (commandsTreeView.SelectedItem != null && + (m_aim.ActiveCmd != null || m_aim.ActiveCmdItem != null)) + { + removeButton.IsEnabled = true; + if (m_aim.ActiveCmd != null) + { + notesTextBox.Text = m_aim.ActiveCmd.FilePath; + } + } + else + { + removeButton.IsEnabled = false; + } + } + else if (externalToolsTabControl.SelectedIndex == 1) // Applications tab + { + // 检查是否有选中的项目 + if (applicationsTreeView.SelectedItem != null && + (m_aim.ActiveApp != null || m_aim.ActiveAppItem != null)) + { + removeButton.IsEnabled = true; + if (m_aim.ActiveApp != null) + { + notesTextBox.Text = m_aim.ActiveApp.FilePath; + } + } + else + { + removeButton.IsEnabled = false; + } + } + else + { + removeButton.IsEnabled = false; + } + } + + private void SaveSplitButton_Click(object sender, RoutedEventArgs e) + { + //// 检查是否有可保存项的逻辑 (如果需要) + //if (!m_aim.AddinManager.HasItemsToSave()) + //{ + // // 也可以选择在这里禁用按钮,而不是显示消息框 + // // MessageBox.Show(...); + // return; + //} + + if (m_aim.AddinManager.AppCount != 0 || m_aim.AddinManager.CmdCount != 0) + { + saveContextMenu.PlacementTarget = saveSplitButton; + saveContextMenu.IsOpen = true; + } + } + + private void NameTextBox_LostFocus(object sender, RoutedEventArgs e) + { + if (m_aim.ActiveCmdItem != null) + { + m_aim.ActiveCmdItem.Name = nametextBox.Text; + m_aim.AddinManager.SaveToAimIni(); + } + } + + private void DescriptionTextBox_LostFocus(object sender, RoutedEventArgs e) + { + if (m_aim.ActiveCmdItem != null) + { + m_aim.ActiveCmdItem.Description = descriptionTextBox.Text; + m_aim.AddinManager.SaveToAimIni(); + } + } + + + private void CommandsTreeView_LostFocus(object sender, RoutedEventArgs e) + { + // 检查焦点是否转移到了相关控件 + var focusedElement = Keyboard.FocusedElement as FrameworkElement; + if (focusedElement != nametextBox && + focusedElement != descriptionTextBox && + focusedElement != runButton && + focusedElement != removeButton) + { + DisableControl(); + removeButton.IsEnabled = false; + } + } + + private void CommandsTreeView_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + if (e.ChangedButton != MouseButton.Left) return; + + var selectedItem = commandsTreeView.SelectedItem as TreeViewItem; + if (selectedItem != null && !HasChildren(selectedItem)) + { + // 确保选中的是可执行的命令项 + if (m_aim.ActiveCmdItem != null) + { + Run(); + } + else + { + ShowStatusError("选中的项目不是可执行的命令"); + } + } + } + + private void SaveToAddinMenuItem_Click(object sender, RoutedEventArgs e) + { + if (!m_aim.AddinManager.HasItemsToSave()) + { + MessageBox.Show(Properties.Resources.NoItemsSelected, Properties.Resources.AppName, MessageBoxButton.OK, MessageBoxImage.Exclamation); + return; + } + m_aim.AddinManager.SaveToAllUserManifest(); + ShowStatusLabel("保存成功,请关闭窗口加载插件"); + } + + private void SaveLocalMenuItem_Click(object sender, RoutedEventArgs e) + { + if (!m_aim.AddinManager.HasItemsToSave()) + { + MessageBox.Show(Properties.Resources.NoItemsSelected, Properties.Resources.AppName, MessageBoxButton.OK, MessageBoxImage.Exclamation); + return; + } + m_aim.AddinManager.SaveToLocal(); + ShowStatusLabel("保存成功"); + } + + private void ShowStatusLabel(string msg) + { + notesTextBox.Foreground = Brushes.Black; + notesTextBox.Text = msg; + } + + private void ShowStatusError(string msg) + { + notesTextBox.Foreground = Brushes.Red; + notesTextBox.Text = msg; + } + + private void CommandsTreeView_RefreshData() + { + RefreshTreeView(commandsTreeView, m_aim.AddinManager.Commands); + + // 清除搜索框 + searchTextBox.Text = string.Empty; + + // 清空存储的命令项列表,防止重复 + m_allCommandItems.Clear(); + } + + private void ApplicationsTreeView_RefreshData() + { + RefreshTreeView(applicationsTreeView, m_aim.AddinManager.Applications); + } + + private void RefreshTreeView(TreeView tree, Addins addins) + { + if (addins == null) return; + + // 清除现有项目,防止重复 + tree.Items.Clear(); + + foreach (var kvp in addins.AddinDict) + { + var key = kvp.Key; + var value = kvp.Value; + + var node = new TreeViewItem + { + Tag = value, + IsExpanded = true // 默认展开 + }; + + // 创建带CheckBox的Header + var stackPanel = new StackPanel { Orientation = Orientation.Horizontal }; + var checkBox = new CheckBox + { + IsChecked = value.Save, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 5, 0), + IsThreeState = true // 支持三态(选中、未选中、部分选中) + }; + checkBox.Checked += TreeViewCheckBox_Changed; + checkBox.Unchecked += TreeViewCheckBox_Changed; + checkBox.Indeterminate += TreeViewCheckBox_Changed; + + var textBlock = new TextBlock + { + Text = key, + VerticalAlignment = VerticalAlignment.Center + }; + + stackPanel.Children.Add(checkBox); + stackPanel.Children.Add(textBlock); + node.Header = stackPanel; + + // 用于确定父节点初始状态 + var hasCheckedChildren = false; + var hasUncheckedChildren = false; + + foreach (var addinItem in value.ItemList) + { + var childNode = new TreeViewItem + { + Tag = addinItem + }; + + // 创建子节点的带CheckBox的Header + var childStackPanel = new StackPanel { Orientation = Orientation.Horizontal }; + var childCheckBox = new CheckBox + { + IsChecked = addinItem.Save, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 5, 0) + }; + childCheckBox.Checked += TreeViewCheckBox_Changed; + childCheckBox.Unchecked += TreeViewCheckBox_Changed; + + var childTextBlock = new TextBlock + { + Text = addinItem.FullClassName, + VerticalAlignment = VerticalAlignment.Center + }; + + childStackPanel.Children.Add(childCheckBox); + childStackPanel.Children.Add(childTextBlock); + childNode.Header = childStackPanel; + + node.Items.Add(childNode); + + // 统计子节点状态 + if (addinItem.Save) + { + hasCheckedChildren = true; + } + else + { + hasUncheckedChildren = true; + } + } + + // 设置父节点CheckBox的初始状态 + if (hasCheckedChildren && hasUncheckedChildren) + { + checkBox.IsChecked = null; // 部分选中 + } + else if (hasCheckedChildren) + { + checkBox.IsChecked = true; // 全选 + } + else + { + checkBox.IsChecked = false; // 全不选 + } + + tree.Items.Add(node); + } + + if (tree.Items.Count > 0) + { + var lastItem = tree.Items[tree.Items.Count - 1] as TreeViewItem; + if (lastItem != null) + { + lastItem.IsSelected = true; + lastItem.BringIntoView(); + } + } + + // 刷新RemoveButton状态 + RemoveButton_RefreshData(); + } + // (新增) 辅助方法:从一个UI元素向上遍历可视化树,找到其所属的 TreeViewItem + private TreeViewItem FindTreeViewItem(DependencyObject source) + { + while (source != null && !(source is TreeViewItem)) + { + source = VisualTreeHelper.GetParent(source); + } + return source as TreeViewItem; + } + // (重写并修复) 在右键菜单打开前,根据当前选中的项动态设置菜单项的可用状态 + private void CommandsTreeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) + { + // 1. 默认将所有菜单项禁用,这是关键! + ContextMenuRun.IsEnabled = false; + ContextMenuLoad.IsEnabled = true; // "加载" 总是可用的 + ContextMenuRemove.IsEnabled = false; + ContextMenuReload.IsEnabled = false; + ContextMenuOpenInExplorer.IsEnabled = false; + ContextMenuAssemblyInfo.IsEnabled = false; + + // 2. 准确查找鼠标右键点击的 TreeViewItem + var clickedItem = FindTreeViewItem(e.OriginalSource as DependencyObject); + + // 3. 如果点击的是空白区域 (没有找到 TreeViewItem),则直接返回 + if (clickedItem == null || clickedItem.Tag == null) + { + return; + } + + // 4. 根据找到的项的类型,逐一启用对应的菜单项 + if (clickedItem.Tag is Addin addin) + { + // 当右键点击父节点 (Addin) 时 + ContextMenuRemove.IsEnabled = true; + ContextMenuReload.IsEnabled = File.Exists(addin.FilePath); + ContextMenuOpenInExplorer.IsEnabled = File.Exists(addin.FilePath); + } + else if (clickedItem.Tag is AddinItem addinItem) + { + // 当右键点击子节点 (AddinItem) 时 + ContextMenuRun.IsEnabled = true; + ContextMenuRemove.IsEnabled = true; + ContextMenuReload.IsEnabled = File.Exists(addinItem.AssemblyPath); + ContextMenuOpenInExplorer.IsEnabled = File.Exists(addinItem.AssemblyPath); + ContextMenuAssemblyInfo.IsEnabled = File.Exists(addinItem.AssemblyPath); + } + } + // 运行 + private void ContextMenuRun_Click(object sender, RoutedEventArgs e) + { + if (commandsTreeView.SelectedItem is TreeViewItem item && item.Tag is AddinItem) + { + Run(); + } + } + + // 加载 + private void ContextMenuLoad_Click(object sender, RoutedEventArgs e) + { + // 直接调用已有的加载按钮逻辑 + LoadButton_Click(sender, e); + } + + // 移除 + private void ContextMenuRemove_Click(object sender, RoutedEventArgs e) + { + // 直接调用已有的移除按钮逻辑 + if (commandsTreeView.SelectedItem != null) + { + RemoveButton_Click(sender, e); + } + } + + // (重写) 重新加载 + private void ContextMenuReload_Click(object sender, RoutedEventArgs e) + { + if (!(commandsTreeView.SelectedItem is TreeViewItem selectedItem)) return; + + Addin addinToReload = null; + // 判断选中项是Addin(父)还是AddinItem(子) + if (selectedItem.Tag is Addin addin) + { + addinToReload = addin; + } + else if (selectedItem.Tag is AddinItem) + { + // 如果是子节点,需要找到其父节点 Addin + var parent = VisualTreeHelper.GetParent(selectedItem); + while (parent != null) + { + if (parent is TreeViewItem parentItem && parentItem.Tag is Addin parentAddin) + { + addinToReload = parentAddin; + break; + } + parent = VisualTreeHelper.GetParent(parent); + } + } + + if (addinToReload == null) + { + ShowStatusError("无法找到插件信息进行重新加载。"); + return; + } + + var filePath = addinToReload.FilePath; + if (!File.Exists(filePath)) + { + ShowStatusError($"插件文件不存在,无法重新加载: {filePath}"); + return; + } + + // 1. 从数据模型中移除 + m_aim.AddinManager.Commands.RemoveAddIn(addinToReload); + m_aim.AddinManager.SaveToAimIni(); + + // 2. 重新加载该文件 + var addinType = m_aim.AddinManager.LoadAddin(filePath); + + // 3. 刷新整个UI + CommandsTreeView_RefreshData(); + ApplicationsTreeView_RefreshData(); + + if (addinType == AddinType.Invalid) + { + ShowStatusError($"重新加载失败: {filePath}"); + } + else + { + ShowStatusLabel($"重新加载成功: {filePath}"); + } + } + + // 在资源管理器中显示 + private void ContextMenuOpenInExplorer_Click(object sender, RoutedEventArgs e) + { + var filePath = string.Empty; + if (commandsTreeView.SelectedItem is TreeViewItem item) + { + if (item.Tag is Addin addin) + { + filePath = addin.FilePath; + } + else if (item.Tag is AddinItem addinItem) + { + filePath = addinItem.AssemblyPath; + } + } + + if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) + { + try + { + // /select, 会打开文件夹并选中该文件 + Process.Start("explorer.exe", $"/select,\"{filePath}\""); + } + catch (Exception ex) + { + ShowStatusError($"无法打开文件位置: {ex.Message}"); + } + } + else + { + ShowStatusError("文件路径无效或文件不存在。"); + } + } + + // 查看程序集信息 + private void ContextMenuAssemblyInfo_Click(object sender, RoutedEventArgs e) + { + if (commandsTreeView.SelectedItem is TreeViewItem item && item.Tag is AddinItem addinItem) + { + var filePath = addinItem.AssemblyPath; + if (!File.Exists(filePath)) + { + ShowStatusError($"程序集文件不存在: {filePath}"); + return; + } + + try + { + // 注意:LoadFrom会锁定文件。对于更复杂的场景,可能需要使用不同的加载上下文。 + var assembly = Assembly.LoadFrom(filePath); + var assemblyName = assembly.GetName(); + var referencedAssemblies = assembly.GetReferencedAssemblies(); + + var info = new System.Text.StringBuilder(); + info.AppendLine($"程序集: {assemblyName.Name}"); + info.AppendLine($"版本: {assemblyName.Version}"); + info.AppendLine($"完整名称: {assembly.FullName}"); + info.AppendLine("\n--- 依赖项 ---"); + + foreach (var refAssembly in referencedAssemblies) + { + info.AppendLine(refAssembly.FullName); + } + + // 这里我们用MessageBox来显示信息。 + // 在实际项目中,您可能想创建一个新的窗口来更友好地展示这些信息。 + MessageBox.Show(info.ToString(), "程序集信息", MessageBoxButton.OK, MessageBoxImage.Information); + } + catch (Exception ex) + { + ShowStatusError($"无法加载程序集信息: {ex.Message}"); + } + } + } + private TreeViewItem FindTreeViewItemFromCheckBox(CheckBox checkBox) + { + DependencyObject parent = checkBox; + + // 向上遍历可视化树,直到找到TreeViewItem + while (parent != null) + { + parent = VisualTreeHelper.GetParent(parent); + if (parent is TreeViewItem treeViewItem) + { + return treeViewItem; + } + } + + // 如果通过可视化树没找到,尝试通过逻辑树查找 + parent = checkBox; + while (parent != null) + { + parent = LogicalTreeHelper.GetParent(parent); + if (parent is TreeViewItem treeViewItem) + { + return treeViewItem; + } + } + + return null; + } + + // (新增) 在右键按下时,强制选中鼠标下的TreeViewItem + private void CommandsTreeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + var treeView = sender as TreeView; + if (treeView == null) return; + + var hitTestResult = VisualTreeHelper.HitTest(treeView, e.GetPosition(treeView)); + if (hitTestResult == null) return; + + var dependencyObject = hitTestResult.VisualHit; + while (dependencyObject != null) + { + if (dependencyObject is TreeViewItem item) + { + // 找到了TreeViewItem,将其设为选中状态 + item.IsSelected = true; + e.Handled = true; // 阻止事件继续传播,防止其他意外行为 + return; + } + dependencyObject = VisualTreeHelper.GetParent(dependencyObject); + } + } + } +} diff --git a/AddInManager/Wpf/Styles/ModernStyles.xaml b/AddInManager/Wpf/Styles/ModernStyles.xaml new file mode 100644 index 0000000..da9a2db --- /dev/null +++ b/AddInManager/Wpf/Styles/ModernStyles.xaml @@ -0,0 +1,862 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AddInManager/app.config b/AddInManager/app.config new file mode 100644 index 0000000..5f7d4c0 --- /dev/null +++ b/AddInManager/app.config @@ -0,0 +1,15 @@ + + + + +
+ + + + + + RevitAddInManager + + + + \ No newline at end of file