将自定义类别配置文件整合到系统配置文件中;修复配置事件重复触发问题。
This commit is contained in:
parent
dc29c3526e
commit
07b30a4fb5
@ -483,11 +483,6 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Link>resources\default_config.toml</Link>
|
||||
</None>
|
||||
<!-- Custom Categories Configuration File -->
|
||||
<None Include="resources\default_custom_categories.toml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Link>resources\default_custom_categories.toml</Link>
|
||||
</None>
|
||||
<!-- Plugin Name File (in resources folder) -->
|
||||
<None Include="resources\TransportPlugin.name.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
||||
@ -71,3 +71,13 @@ width_limit_meters = 3.0
|
||||
# ZUp: 强制使用 Z-Up 坐标系(Navisworks 默认)
|
||||
# YUp: 强制使用 Y-Up 坐标系(常见于 Revit 导出)
|
||||
type = "AutoDetect"
|
||||
|
||||
# 自定义物流类别
|
||||
# 用于扩展内置类别,只需填写类别名称
|
||||
#
|
||||
# 示例:
|
||||
# [[custom_category]]
|
||||
# name = "狭窄通道"
|
||||
#
|
||||
# [[custom_category]]
|
||||
# name = "临时存储区"
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
# 自定义物流类别配置文件
|
||||
# 位置: %ProgramData%/Autodesk/Navisworks Manage 2026/plugins/TransportPlugin/custom_categories.toml
|
||||
# 说明: 本文件定义用户自定义的物流类别,与模型文件无关,可跨项目共享
|
||||
|
||||
# ============================================================
|
||||
# 类别属性说明
|
||||
# ============================================================
|
||||
#
|
||||
# 【必填属性】
|
||||
# id: 唯一标识(英文,用于存储和内部识别)
|
||||
# - 不能与内置类别名称重复(如 door, elevator 等)
|
||||
# - 建议使用小写和下划线,如: "ramp", "clean_room"
|
||||
#
|
||||
# display_name: 显示名称(中文)
|
||||
# - 在UI类别下拉框和物流模型列表中显示
|
||||
#
|
||||
# 【可选属性 - 有特殊需求时才设置】
|
||||
# traversable: 是否默认可通行
|
||||
# - 默认: true(可通行)
|
||||
# - 仅当类别不可通行时设置为 false(如限制区域)
|
||||
#
|
||||
# weight: 寻路权重
|
||||
# - 默认: 1.0
|
||||
# - 值越小优先级越高,越大越难走
|
||||
# - 不可通行区域建议设为极大值如 999999.0
|
||||
#
|
||||
# 【预留属性 - 当前未使用,可不设置】
|
||||
# icon: 图标标识(默认 Box)
|
||||
# priority: 优先级 1-10(默认 5)
|
||||
# color: UI显示颜色(默认 #9E9E9E)
|
||||
# height_limit_meters: 默认高度限制米数(默认 3.0)
|
||||
# speed_limit_meters_per_second: 默认速度限制米/秒(默认 1.0)
|
||||
# width_limit_meters: 默认宽度限制米数(默认 3.0)
|
||||
#
|
||||
# ============================================================
|
||||
# 配置示例
|
||||
# ============================================================
|
||||
|
||||
# --------------------------------------------------
|
||||
# 示例1: 最简配置(只有必填,其他用默认)
|
||||
# --------------------------------------------------
|
||||
# 坡道:默认可通行(weight=1.0),无需设置其他属性
|
||||
[[category]]
|
||||
id = "ramp"
|
||||
display_name = "坡道"
|
||||
|
||||
# --------------------------------------------------
|
||||
# 示例2: 不可通行区域(必须设置特殊属性)
|
||||
# --------------------------------------------------
|
||||
# 限制区域:不可通行,寻路时避开
|
||||
[[category]]
|
||||
id = "restricted_area"
|
||||
display_name = "限制区域"
|
||||
traversable = false
|
||||
weight = 999999.0
|
||||
|
||||
# --------------------------------------------------
|
||||
# 示例3: 优先选择的通道(设置更小weight)
|
||||
# --------------------------------------------------
|
||||
# 快速通道:比默认值(1.0)优先,寻路时优先走这里
|
||||
[[category]]
|
||||
id = "express_lane"
|
||||
display_name = "快速通道"
|
||||
weight = 0.6
|
||||
|
||||
# --------------------------------------------------
|
||||
# 示例4: 较难的通行区域(设置更大weight)
|
||||
# --------------------------------------------------
|
||||
# 狭窄通道:比默认值难走,只有必要时才走
|
||||
[[category]]
|
||||
id = "narrow_passage"
|
||||
display_name = "狭窄通道"
|
||||
weight = 2.5
|
||||
|
||||
# --------------------------------------------------
|
||||
# 示例5: 完整配置(展示所有可用属性)
|
||||
# --------------------------------------------------
|
||||
# 一般不需要这么多,按需设置即可
|
||||
[[category]]
|
||||
id = "temporary_storage"
|
||||
display_name = "临时堆放区"
|
||||
# 可通行但较慢(weight > 1.0)
|
||||
weight = 1.5
|
||||
# 预留属性(当前未使用)
|
||||
icon = "Package"
|
||||
color = "#FFC107"
|
||||
@ -488,6 +488,28 @@ namespace NavisworksTransport.Core.Config
|
||||
}
|
||||
}
|
||||
|
||||
// 加载自定义类别配置(可选)
|
||||
if (model.ContainsKey("custom_category"))
|
||||
{
|
||||
var categoriesArray = model["custom_category"] as TomlTableArray;
|
||||
if (categoriesArray != null)
|
||||
{
|
||||
foreach (var categoryTable in categoriesArray)
|
||||
{
|
||||
var name = GetStringValueWithDefault(categoryTable, "name", null, missingItems);
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
config.CustomCategories.Add(new CustomCategoryConfigItem
|
||||
{
|
||||
Name = name
|
||||
});
|
||||
}
|
||||
}
|
||||
LogManager.Info($"[ConfigManager] 已加载 {config.CustomCategories.Count} 个自定义类别");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有缺失项,记录警告并设置标志
|
||||
if (missingItems.Count > 0)
|
||||
{
|
||||
@ -513,7 +535,8 @@ namespace NavisworksTransport.Core.Config
|
||||
Visualization = new VisualizationConfig(),
|
||||
Animation = new AnimationConfig(),
|
||||
Logistics = new LogisticsConfig(),
|
||||
CoordinateSystem = new CoordinateSystemConfig()
|
||||
CoordinateSystem = new CoordinateSystemConfig(),
|
||||
CustomCategories = new List<CustomCategoryConfigItem>()
|
||||
};
|
||||
}
|
||||
|
||||
@ -597,7 +620,24 @@ namespace NavisworksTransport.Core.Config
|
||||
ApplyConfigValuesToModel(model, config);
|
||||
|
||||
// 转换回 TOML 字符串
|
||||
return Toml.FromModel(model);
|
||||
var tomlContent = Toml.FromModel(model);
|
||||
|
||||
// 追加自定义类别配置
|
||||
if (config.CustomCategories != null && config.CustomCategories.Count > 0)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder(tomlContent);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("# 自定义物流类别(只需填写名称)");
|
||||
foreach (var category in config.CustomCategories)
|
||||
{
|
||||
sb.AppendLine("[[custom_category]]");
|
||||
sb.AppendLine($"name = \"{EscapeTomlString(category.Name)}\"");
|
||||
sb.AppendLine();
|
||||
}
|
||||
tomlContent = sb.ToString();
|
||||
}
|
||||
|
||||
return tomlContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -669,6 +709,10 @@ namespace NavisworksTransport.Core.Config
|
||||
coordSys["type"] = config.CoordinateSystem.Type;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新自定义类别配置
|
||||
// 注意:由于Tomlyn对数组的处理限制,自定义类别通过专门的API管理
|
||||
// 这里不直接操作TOML模型,而是在保存前通过模板合并
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -679,6 +723,52 @@ namespace NavisworksTransport.Core.Config
|
||||
ReloadInternal(isExternal: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从内容重新加载配置(用于编辑器保存后不触发文件监控)
|
||||
/// </summary>
|
||||
public void ReloadFromContent(string tomlContent, bool notifyChange = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 先临时禁用文件监控,避免写入时触发外部修改事件
|
||||
var watcherWasEnabled = _fileWatcher?.EnableRaisingEvents ?? false;
|
||||
if (_fileWatcher != null)
|
||||
{
|
||||
_fileWatcher.EnableRaisingEvents = false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 写入文件(此时 FileSystemWatcher 已禁用,不会触发外部修改事件)
|
||||
File.WriteAllText(ConfigFilePath, tomlContent);
|
||||
|
||||
// 解析并更新配置
|
||||
_currentConfig = ParseTomlToConfig(tomlContent);
|
||||
|
||||
LogManager.Info("配置已从内容重新加载");
|
||||
|
||||
// 触发配置变更事件
|
||||
if (notifyChange)
|
||||
{
|
||||
NotifyConfigurationChanged(ConfigCategory.All, isExternal: false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 恢复文件监控
|
||||
if (_fileWatcher != null && watcherWasEnabled)
|
||||
{
|
||||
_fileWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"从内容重新加载配置失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部重新加载方法
|
||||
/// </summary>
|
||||
@ -994,6 +1084,16 @@ namespace NavisworksTransport.Core.Config
|
||||
return strValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转义 TOML 字符串
|
||||
/// </summary>
|
||||
private string EscapeTomlString(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return "";
|
||||
return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Tomlyn;
|
||||
using Tomlyn.Model;
|
||||
|
||||
namespace NavisworksTransport.Core.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义物流类别配置
|
||||
/// 从用户级 TOML 配置文件加载,独立于模型数据库
|
||||
/// 自定义物流类别配置管理器
|
||||
/// 从 ConfigManager 获取配置,不再使用独立的配置文件
|
||||
/// </summary>
|
||||
public class CustomCategoryConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置文件路径(用户级,与模型无关)
|
||||
/// </summary>
|
||||
public static string ConfigFilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"Autodesk",
|
||||
"Navisworks Manage 2026",
|
||||
"plugins",
|
||||
"TransportPlugin",
|
||||
"custom_categories.toml"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置目录
|
||||
/// </summary>
|
||||
public static string ConfigDirectory => Path.GetDirectoryName(ConfigFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// 自定义类别列表
|
||||
/// </summary>
|
||||
public List<CustomCategoryDefinition> Categories { get; set; } = new List<CustomCategoryDefinition>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有类别(包括内置和自定义)
|
||||
/// </summary>
|
||||
@ -51,27 +20,27 @@ namespace NavisworksTransport.Core.Config
|
||||
foreach (var builtIn in BuiltInCategories.All)
|
||||
yield return builtIn;
|
||||
|
||||
// 自定义类别
|
||||
foreach (var custom in Categories)
|
||||
// 自定义类别(从 ConfigManager 获取)
|
||||
foreach (var custom in GetCustomCategories())
|
||||
yield return custom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取类别定义
|
||||
/// 根据名称获取类别定义
|
||||
/// </summary>
|
||||
public CategoryDefinition GetCategory(string id)
|
||||
public CategoryDefinition GetCategory(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return null;
|
||||
|
||||
// 先查内置
|
||||
var builtIn = BuiltInCategories.GetById(id);
|
||||
var builtIn = BuiltInCategories.GetById(name);
|
||||
if (builtIn != null)
|
||||
return builtIn;
|
||||
|
||||
// 再查自定义
|
||||
return Categories.FirstOrDefault(c =>
|
||||
c.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
|
||||
// 再查自定义(按名称匹配)
|
||||
return GetCustomCategories().FirstOrDefault(c =>
|
||||
c.DisplayName.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -91,310 +60,88 @@ namespace NavisworksTransport.Core.Config
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 TOML 文件加载配置
|
||||
/// 添加自定义类别
|
||||
/// </summary>
|
||||
public static CustomCategoryConfig LoadFromFile()
|
||||
public void AddCategory(string name)
|
||||
{
|
||||
var config = new CustomCategoryConfig();
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentException("类别名称不能为空");
|
||||
|
||||
try
|
||||
if (CategoryExists(name))
|
||||
throw new InvalidOperationException($"类别 '{name}' 已存在");
|
||||
|
||||
var config = ConfigManager.Instance.Current;
|
||||
config.CustomCategories.Add(new CustomCategoryConfigItem
|
||||
{
|
||||
if (!File.Exists(ConfigFilePath))
|
||||
{
|
||||
// 配置文件不存在,创建默认配置
|
||||
config = CreateDefaultConfig();
|
||||
config.SaveToFile();
|
||||
return config;
|
||||
}
|
||||
|
||||
var tomlContent = File.ReadAllText(ConfigFilePath);
|
||||
var tomlTable = Toml.ToModel(tomlContent);
|
||||
|
||||
if (tomlTable.TryGetValue("category", out var categoriesObj) &&
|
||||
categoriesObj is TomlTableArray categoriesArray)
|
||||
{
|
||||
foreach (var categoryTable in categoriesArray)
|
||||
{
|
||||
var category = ParseCategoryFromToml(categoryTable);
|
||||
if (category != null)
|
||||
config.Categories.Add(category);
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[CustomCategoryConfig] 已加载 {config.Categories.Count} 个自定义类别");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[CustomCategoryConfig] 加载配置失败: {ex.Message}");
|
||||
// 加载失败时返回空配置
|
||||
config = new CustomCategoryConfig();
|
||||
}
|
||||
|
||||
return config;
|
||||
Name = name
|
||||
});
|
||||
|
||||
ConfigManager.Instance.SaveConfig(config);
|
||||
LogManager.Info($"[CustomCategoryConfig] 已添加自定义类别: {name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置到 TOML 文件
|
||||
/// 删除自定义类别
|
||||
/// </summary>
|
||||
public void SaveToFile()
|
||||
public void RemoveCategory(string name)
|
||||
{
|
||||
try
|
||||
if (IsBuiltIn(name))
|
||||
throw new InvalidOperationException("不能删除内置类别");
|
||||
|
||||
var config = ConfigManager.Instance.Current;
|
||||
var category = config.CustomCategories.FirstOrDefault(c =>
|
||||
c.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
Directory.CreateDirectory(ConfigDirectory);
|
||||
var tomlContent = GenerateTomlContent();
|
||||
File.WriteAllText(ConfigFilePath, tomlContent);
|
||||
LogManager.Info($"[CustomCategoryConfig] 配置已保存: {ConfigFilePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[CustomCategoryConfig] 保存配置失败: {ex.Message}");
|
||||
config.CustomCategories.Remove(category);
|
||||
ConfigManager.Instance.SaveConfig(config);
|
||||
LogManager.Info($"[CustomCategoryConfig] 已删除自定义类别: {name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认配置文件路径(插件资源目录)
|
||||
/// 从 ConfigManager 获取自定义类别列表
|
||||
/// </summary>
|
||||
public static string DefaultConfigFilePath
|
||||
private IEnumerable<CustomCategoryDefinition> GetCustomCategories()
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(
|
||||
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
|
||||
"resources",
|
||||
"default_custom_categories.toml"
|
||||
);
|
||||
}
|
||||
}
|
||||
var config = ConfigManager.Instance.Current;
|
||||
if (config.CustomCategories == null)
|
||||
yield break;
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认配置
|
||||
/// 尝试从默认配置文件加载示例,如果失败则返回空配置
|
||||
/// </summary>
|
||||
private static CustomCategoryConfig CreateDefaultConfig()
|
||||
{
|
||||
var config = new CustomCategoryConfig
|
||||
foreach (var item in config.CustomCategories)
|
||||
{
|
||||
Categories = new List<CustomCategoryDefinition>()
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试从默认配置文件加载示例
|
||||
if (File.Exists(DefaultConfigFilePath))
|
||||
yield return new CustomCategoryDefinition
|
||||
{
|
||||
var tomlContent = File.ReadAllText(DefaultConfigFilePath);
|
||||
var tomlTable = Toml.ToModel(tomlContent);
|
||||
|
||||
if (tomlTable.TryGetValue("category", out var categoriesObj) &&
|
||||
categoriesObj is TomlTableArray categoriesArray)
|
||||
{
|
||||
foreach (var categoryTable in categoriesArray)
|
||||
{
|
||||
var category = ParseCategoryFromToml(categoryTable);
|
||||
if (category != null)
|
||||
config.Categories.Add(category);
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[CustomCategoryConfig] 已从默认配置文件加载 {config.Categories.Count} 个示例类别");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Warning($"[CustomCategoryConfig] 从默认配置文件加载失败: {ex.Message},将使用空配置");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 TOML 表解析类别定义
|
||||
/// </summary>
|
||||
private static CustomCategoryDefinition ParseCategoryFromToml(TomlTable table)
|
||||
{
|
||||
try
|
||||
{
|
||||
var category = new CustomCategoryDefinition
|
||||
{
|
||||
Id = table.GetString("id"),
|
||||
DisplayName = table.GetString("display_name"),
|
||||
Icon = table.GetString("icon", "Box"),
|
||||
Traversable = table.GetBool("traversable", true),
|
||||
Priority = table.GetInt("priority", 5),
|
||||
Weight = table.GetDouble("weight", 1.0),
|
||||
DisplayColor = ParseColor(table.GetString("color", "#9E9E9E")),
|
||||
Defaults = new CategoryDefaultProperties
|
||||
{
|
||||
HeightLimitMeters = table.GetDouble("height_limit_meters", 3.0),
|
||||
SpeedLimitMetersPerSecond = table.GetDouble("speed_limit_meters_per_second", 1.0),
|
||||
WidthLimitMeters = table.GetDouble("width_limit_meters", 3.0)
|
||||
}
|
||||
Id = item.Name, // 名称同时作为ID
|
||||
DisplayName = item.Name
|
||||
};
|
||||
|
||||
// 验证必填字段
|
||||
if (string.IsNullOrEmpty(category.Id) || string.IsNullOrEmpty(category.DisplayName))
|
||||
{
|
||||
LogManager.Warning("[CustomCategoryConfig] 类别定义缺少必填字段 id 或 display_name");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查ID是否与内置类别冲突
|
||||
if (BuiltInCategories.GetById(category.Id) != null)
|
||||
{
|
||||
LogManager.Warning($"[CustomCategoryConfig] 自定义类别ID '{category.Id}' 与内置类别冲突,已跳过");
|
||||
return null;
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[CustomCategoryConfig] 解析类别定义失败: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 TOML 内容(只输出非默认值的属性)
|
||||
/// </summary>
|
||||
private string GenerateTomlContent()
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("# 自定义物流类别配置文件");
|
||||
sb.AppendLine("# 位置: %ProgramData%/Autodesk/Navisworks Manage 2026/plugins/TransportPlugin/custom_categories.toml");
|
||||
sb.AppendLine("# 说明: 本文件定义用户自定义的物流类别,与模型文件无关,可跨项目共享");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("# ============================================================");
|
||||
sb.AppendLine("# 类别属性说明");
|
||||
sb.AppendLine("# ============================================================");
|
||||
sb.AppendLine("#");
|
||||
sb.AppendLine("# 【必填】");
|
||||
sb.AppendLine("# id: 唯一标识(英文)");
|
||||
sb.AppendLine("# display_name: 显示名称(中文)");
|
||||
sb.AppendLine("#");
|
||||
sb.AppendLine("# 【可选 - 有特殊需求才设置】");
|
||||
sb.AppendLine("# traversable: 是否可通行(默认 true)");
|
||||
sb.AppendLine("# weight: 寻路权重(默认 1.0)");
|
||||
sb.AppendLine("#");
|
||||
sb.AppendLine("# 【预留 - 当前未使用】");
|
||||
sb.AppendLine("# icon, priority, color, height_limit_meters 等");
|
||||
sb.AppendLine("#");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var category in Categories)
|
||||
{
|
||||
sb.AppendLine("[[category]]");
|
||||
sb.AppendLine($"id = \"{EscapeTomlString(category.Id)}\"");
|
||||
sb.AppendLine($"display_name = \"{EscapeTomlString(category.DisplayName)}\"");
|
||||
|
||||
// 只输出非默认值的属性
|
||||
if (category.Traversable != true)
|
||||
sb.AppendLine($"traversable = {category.Traversable.ToString().ToLower()}");
|
||||
if (category.Weight != 1.0)
|
||||
sb.AppendLine($"weight = {category.Weight:F2}");
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析颜色字符串
|
||||
/// </summary>
|
||||
private static Color ParseColor(string colorStr)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(colorStr))
|
||||
return Color.Gray;
|
||||
|
||||
// 支持 #RRGGBB 或 #AARRGGBB 格式
|
||||
if (colorStr.StartsWith("#"))
|
||||
colorStr = colorStr.Substring(1);
|
||||
|
||||
if (colorStr.Length == 6)
|
||||
{
|
||||
int r = int.Parse(colorStr.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
int g = int.Parse(colorStr.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
int b = int.Parse(colorStr.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
return Color.FromArgb(r, g, b);
|
||||
}
|
||||
else if (colorStr.Length == 8)
|
||||
{
|
||||
int a = int.Parse(colorStr.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
int r = int.Parse(colorStr.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
int g = int.Parse(colorStr.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
int b = int.Parse(colorStr.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
return Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return Color.Gray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 颜色转十六进制字符串
|
||||
/// </summary>
|
||||
private static string ColorToHex(Color color)
|
||||
{
|
||||
return $"#{color.R:X2}{color.G:X2}{color.B:X2}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转义 TOML 字符串
|
||||
/// </summary>
|
||||
private static string EscapeTomlString(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return "";
|
||||
return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类别定义基类(内置和自定义共用接口)
|
||||
/// 简化版,仅保留核心属性
|
||||
/// </summary>
|
||||
public abstract class CategoryDefinition
|
||||
{
|
||||
public abstract string Id { get; set; }
|
||||
public abstract string DisplayName { get; set; }
|
||||
public abstract string Icon { get; set; }
|
||||
public abstract bool Traversable { get; set; }
|
||||
public abstract int Priority { get; set; }
|
||||
public abstract double Weight { get; set; }
|
||||
public abstract Color DisplayColor { get; set; }
|
||||
public abstract CategoryDefaultProperties Defaults { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义类别定义
|
||||
/// 自定义类别定义(简化版)
|
||||
/// </summary>
|
||||
public class CustomCategoryDefinition : CategoryDefinition
|
||||
{
|
||||
public override string Id { get; set; }
|
||||
public override string DisplayName { get; set; }
|
||||
public override string Icon { get; set; } = "Box";
|
||||
public override bool Traversable { get; set; } = true;
|
||||
public override int Priority { get; set; } = 5;
|
||||
public override double Weight { get; set; } = 1.0;
|
||||
public override Color DisplayColor { get; set; } = Color.Gray;
|
||||
public override CategoryDefaultProperties Defaults { get; set; } = new CategoryDefaultProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类别默认属性
|
||||
/// </summary>
|
||||
public class CategoryDefaultProperties
|
||||
{
|
||||
public double HeightLimitMeters { get; set; } = 3.0;
|
||||
public double SpeedLimitMetersPerSecond { get; set; } = 1.0;
|
||||
public double WidthLimitMeters { get; set; } = 3.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内置类别定义(包装 LogisticsElementType 枚举)
|
||||
/// 简化版,仅保留核心属性
|
||||
/// </summary>
|
||||
public class BuiltInCategoryDefinition : CategoryDefinition
|
||||
{
|
||||
@ -407,134 +154,8 @@ namespace NavisworksTransport.Core.Config
|
||||
|
||||
public override string Id { get => _elementType.ToString(); set => throw new NotSupportedException("内置类别不支持修改"); }
|
||||
public override string DisplayName { get => _elementType.ToString(); set => throw new NotSupportedException("内置类别不支持修改"); }
|
||||
public override string Icon { get => GetIconForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
|
||||
public override bool Traversable { get => GetTraversableForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
|
||||
public override int Priority { get => GetPriorityForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
|
||||
public override double Weight { get => GetWeightForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
|
||||
public override Color DisplayColor { get => GetColorForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
|
||||
public override CategoryDefaultProperties Defaults { get => GetDefaultsForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
|
||||
|
||||
public CategoryAttributeManager.LogisticsElementType ElementType => _elementType;
|
||||
|
||||
private static string GetIconForType(CategoryAttributeManager.LogisticsElementType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CategoryAttributeManager.LogisticsElementType.障碍物: return "BlockHelper";
|
||||
case CategoryAttributeManager.LogisticsElementType.楼板: return "Rectangle";
|
||||
case CategoryAttributeManager.LogisticsElementType.门: return "Door";
|
||||
case CategoryAttributeManager.LogisticsElementType.电梯: return "Elevator";
|
||||
case CategoryAttributeManager.LogisticsElementType.楼梯: return "Stairs";
|
||||
case CategoryAttributeManager.LogisticsElementType.通道: return "Road";
|
||||
case CategoryAttributeManager.LogisticsElementType.走廊: return "Corridor";
|
||||
case CategoryAttributeManager.LogisticsElementType.装卸区: return "Truck";
|
||||
case CategoryAttributeManager.LogisticsElementType.停车位: return "Parking";
|
||||
case CategoryAttributeManager.LogisticsElementType.空轨: return "Air";
|
||||
case CategoryAttributeManager.LogisticsElementType.无关项: return "EyeOff";
|
||||
default: return "Box";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetTraversableForType(CategoryAttributeManager.LogisticsElementType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CategoryAttributeManager.LogisticsElementType.障碍物:
|
||||
case CategoryAttributeManager.LogisticsElementType.空洞:
|
||||
case CategoryAttributeManager.LogisticsElementType.无关项:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetPriorityForType(CategoryAttributeManager.LogisticsElementType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CategoryAttributeManager.LogisticsElementType.通道: return 1;
|
||||
case CategoryAttributeManager.LogisticsElementType.走廊: return 2;
|
||||
case CategoryAttributeManager.LogisticsElementType.装卸区: return 3;
|
||||
case CategoryAttributeManager.LogisticsElementType.停车位: return 4;
|
||||
case CategoryAttributeManager.LogisticsElementType.楼板: return 5;
|
||||
case CategoryAttributeManager.LogisticsElementType.空轨: return 6;
|
||||
case CategoryAttributeManager.LogisticsElementType.门: return 7;
|
||||
case CategoryAttributeManager.LogisticsElementType.电梯: return 8;
|
||||
case CategoryAttributeManager.LogisticsElementType.楼梯: return 9;
|
||||
case CategoryAttributeManager.LogisticsElementType.障碍物: return 10;
|
||||
default: return 5;
|
||||
}
|
||||
}
|
||||
|
||||
private static double GetWeightForType(CategoryAttributeManager.LogisticsElementType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CategoryAttributeManager.LogisticsElementType.通道: return 0.5;
|
||||
case CategoryAttributeManager.LogisticsElementType.走廊: return 0.6;
|
||||
case CategoryAttributeManager.LogisticsElementType.空轨: return 0.7;
|
||||
case CategoryAttributeManager.LogisticsElementType.装卸区: return 0.8;
|
||||
case CategoryAttributeManager.LogisticsElementType.停车位: return 0.9;
|
||||
case CategoryAttributeManager.LogisticsElementType.楼板: return 1.0;
|
||||
case CategoryAttributeManager.LogisticsElementType.门: return 1.2;
|
||||
case CategoryAttributeManager.LogisticsElementType.电梯: return 2.0;
|
||||
case CategoryAttributeManager.LogisticsElementType.楼梯: return 3.0;
|
||||
case CategoryAttributeManager.LogisticsElementType.障碍物:
|
||||
case CategoryAttributeManager.LogisticsElementType.空洞:
|
||||
return double.MaxValue;
|
||||
default: return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetColorForType(CategoryAttributeManager.LogisticsElementType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CategoryAttributeManager.LogisticsElementType.通道: return Color.FromArgb(76, 175, 80); // Green
|
||||
case CategoryAttributeManager.LogisticsElementType.走廊: return Color.FromArgb(129, 199, 132); // Light Green
|
||||
case CategoryAttributeManager.LogisticsElementType.门: return Color.FromArgb(33, 150, 243); // Blue
|
||||
case CategoryAttributeManager.LogisticsElementType.电梯: return Color.FromArgb(156, 39, 176); // Purple
|
||||
case CategoryAttributeManager.LogisticsElementType.楼梯: return Color.FromArgb(255, 87, 34); // Deep Orange
|
||||
case CategoryAttributeManager.LogisticsElementType.装卸区: return Color.FromArgb(255, 152, 0); // Orange
|
||||
case CategoryAttributeManager.LogisticsElementType.停车位: return Color.FromArgb(0, 150, 136); // Teal
|
||||
case CategoryAttributeManager.LogisticsElementType.空轨: return Color.FromArgb(63, 81, 181); // Indigo
|
||||
case CategoryAttributeManager.LogisticsElementType.障碍物: return Color.FromArgb(244, 67, 54); // Red
|
||||
case CategoryAttributeManager.LogisticsElementType.无关项: return Color.FromArgb(158, 158, 158); // Gray
|
||||
default: return Color.FromArgb(96, 125, 139); // Blue Gray
|
||||
}
|
||||
}
|
||||
|
||||
private static CategoryDefaultProperties GetDefaultsForType(CategoryAttributeManager.LogisticsElementType type)
|
||||
{
|
||||
var defaults = new CategoryDefaultProperties();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CategoryAttributeManager.LogisticsElementType.门:
|
||||
defaults.HeightLimitMeters = 2.5;
|
||||
defaults.SpeedLimitMetersPerSecond = 0.5;
|
||||
defaults.WidthLimitMeters = 2.0;
|
||||
break;
|
||||
case CategoryAttributeManager.LogisticsElementType.电梯:
|
||||
defaults.HeightLimitMeters = 3.0;
|
||||
defaults.SpeedLimitMetersPerSecond = 0.3;
|
||||
defaults.WidthLimitMeters = 2.5;
|
||||
break;
|
||||
case CategoryAttributeManager.LogisticsElementType.楼梯:
|
||||
defaults.HeightLimitMeters = 2.8;
|
||||
defaults.SpeedLimitMetersPerSecond = 0.2;
|
||||
defaults.WidthLimitMeters = 1.5;
|
||||
break;
|
||||
case CategoryAttributeManager.LogisticsElementType.通道:
|
||||
defaults.HeightLimitMeters = 4.0;
|
||||
defaults.SpeedLimitMetersPerSecond = 1.5;
|
||||
defaults.WidthLimitMeters = 4.0;
|
||||
break;
|
||||
// 其他使用默认值
|
||||
}
|
||||
|
||||
return defaults;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -579,44 +200,4 @@ namespace NavisworksTransport.Core.Config
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TOML 扩展方法
|
||||
/// </summary>
|
||||
internal static class TomlExtensions
|
||||
{
|
||||
public static string GetString(this TomlTable table, string key, string defaultValue = "")
|
||||
{
|
||||
if (table.TryGetValue(key, out var value) && value is string str)
|
||||
return str;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static bool GetBool(this TomlTable table, string key, bool defaultValue = false)
|
||||
{
|
||||
if (table.TryGetValue(key, out var value) && value is bool b)
|
||||
return b;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int GetInt(this TomlTable table, string key, int defaultValue = 0)
|
||||
{
|
||||
if (table.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value is long l) return (int)l;
|
||||
if (value is int i) return i;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static double GetDouble(this TomlTable table, string key, double defaultValue = 0.0)
|
||||
{
|
||||
if (table.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value is double d) return d;
|
||||
if (value is long l) return l;
|
||||
if (value is int i) return i;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NavisworksTransport.Core.Config
|
||||
{
|
||||
/// <summary>
|
||||
@ -40,6 +42,22 @@ namespace NavisworksTransport.Core.Config
|
||||
/// 坐标系配置
|
||||
/// </summary>
|
||||
public CoordinateSystemConfig CoordinateSystem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义类别列表
|
||||
/// </summary>
|
||||
public List<CustomCategoryConfigItem> CustomCategories { get; set; } = new List<CustomCategoryConfigItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义类别配置项(极简版,只用名称作为标识)
|
||||
/// </summary>
|
||||
public class CustomCategoryConfigItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 类别名称(同时作为显示名称和唯一标识)
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1090,7 +1090,7 @@ namespace NavisworksTransport
|
||||
{
|
||||
if (_customCategoryConfig == null)
|
||||
{
|
||||
_customCategoryConfig = Core.Config.CustomCategoryConfig.LoadFromFile();
|
||||
_customCategoryConfig = new Core.Config.CustomCategoryConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1105,7 +1105,8 @@ namespace NavisworksTransport
|
||||
{
|
||||
lock (_customCategoryLock)
|
||||
{
|
||||
_customCategoryConfig = Core.Config.CustomCategoryConfig.LoadFromFile();
|
||||
// 重新创建实例,从 ConfigManager 获取最新数据
|
||||
_customCategoryConfig = new Core.Config.CustomCategoryConfig();
|
||||
LogManager.Info("[CategoryAttributeManager] 自定义类别配置已重新加载");
|
||||
}
|
||||
}
|
||||
@ -1171,8 +1172,14 @@ namespace NavisworksTransport
|
||||
/// <returns>权重值,未知类别返回1.0</returns>
|
||||
public static double GetCategoryWeight(string categoryId)
|
||||
{
|
||||
var definition = GetCategoryDefinition(categoryId);
|
||||
return definition?.Weight ?? 1.0;
|
||||
// 简化为返回默认值,后续可根据需要扩展
|
||||
// 障碍物类别返回最大权重
|
||||
if (categoryId?.Equals("障碍物", StringComparison.OrdinalIgnoreCase) == true ||
|
||||
categoryId?.Equals("空洞", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
return double.MaxValue;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1182,8 +1189,14 @@ namespace NavisworksTransport
|
||||
/// <returns>是否可通行</returns>
|
||||
public static bool IsCategoryTraversable(string categoryId)
|
||||
{
|
||||
var definition = GetCategoryDefinition(categoryId);
|
||||
return definition?.Traversable ?? false;
|
||||
// 简化为根据类别名称判断
|
||||
if (categoryId?.Equals("障碍物", StringComparison.OrdinalIgnoreCase) == true ||
|
||||
categoryId?.Equals("空洞", StringComparison.OrdinalIgnoreCase) == true ||
|
||||
categoryId?.Equals("无关项", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -264,10 +264,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可用类别筛选选项 - 从所有可用类别动态获取,并添加"全部"选项
|
||||
/// 可用类别筛选选项 - 从所有可用类别动态获取
|
||||
/// 注意:不包含"全部","全部"是UI层面的特殊选项
|
||||
/// </summary>
|
||||
public ThreadSafeObservableCollection<string> AvailableCategoryFilters { get; } =
|
||||
new ThreadSafeObservableCollection<string>(new[] { "全部" });
|
||||
new ThreadSafeObservableCollection<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的类别筛选条件
|
||||
@ -542,6 +543,25 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理自定义类别变更 - 刷新可用类别列表
|
||||
if (category == ConfigCategory.All)
|
||||
{
|
||||
LogManager.Info("收到配置变更通知,正在更新可用类别列表...");
|
||||
|
||||
SafeExecute(() =>
|
||||
{
|
||||
// 重新加载自定义类别
|
||||
CategoryAttributeManager.ReloadCustomCategories();
|
||||
|
||||
// 刷新可用类别列表
|
||||
RefreshAvailableCategories();
|
||||
|
||||
string source = eventArgs.IsExternalChange ? "外部修改" : "内部更新";
|
||||
UpdateMainStatus($"类别列表已更新 ({source})");
|
||||
LogManager.Info($"可用类别列表已更新 - 来源: {source}, 当前类别数: {AvailableCategories.Count}");
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -549,6 +569,58 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新可用类别列表
|
||||
/// </summary>
|
||||
private void RefreshAvailableCategories()
|
||||
{
|
||||
_uiStateManager.ExecuteUIUpdateAsync(() =>
|
||||
{
|
||||
// 保存当前选中的类别
|
||||
string previousSelectedCategory = SelectedCategory;
|
||||
string previousSelectedFilter = SelectedCategoryFilter;
|
||||
|
||||
// 清空集合
|
||||
AvailableCategories.Clear();
|
||||
AvailableCategoryFilters.Clear();
|
||||
|
||||
// 先添加"全部"选项到筛选器
|
||||
AvailableCategoryFilters.Add("全部");
|
||||
|
||||
// 从 CategoryAttributeManager 获取所有可用类别(内置 + 自定义)
|
||||
var allCategories = CategoryAttributeManager.GetAllAvailableCategories().ToList();
|
||||
|
||||
foreach (var cat in allCategories)
|
||||
{
|
||||
AvailableCategories.Add(cat.DisplayName);
|
||||
AvailableCategoryFilters.Add(cat.DisplayName);
|
||||
}
|
||||
|
||||
// 恢复之前的选择(如果仍然存在)
|
||||
if (!string.IsNullOrEmpty(previousSelectedCategory) &&
|
||||
AvailableCategories.Contains(previousSelectedCategory))
|
||||
{
|
||||
SelectedCategory = previousSelectedCategory;
|
||||
}
|
||||
else if (AvailableCategories.Count > 0)
|
||||
{
|
||||
// 默认选择"通道"或第一个类别
|
||||
SelectedCategory = AvailableCategories.Contains("通道") ? "通道" : AvailableCategories[0];
|
||||
}
|
||||
|
||||
// 恢复筛选器选择
|
||||
if (!string.IsNullOrEmpty(previousSelectedFilter) &&
|
||||
AvailableCategoryFilters.Contains(previousSelectedFilter))
|
||||
{
|
||||
SelectedCategoryFilter = previousSelectedFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedCategoryFilter = "全部";
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令初始化 - 统一的Command Pattern
|
||||
@ -1302,6 +1374,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// 初始化物流类别(包括内置和自定义类别)
|
||||
AvailableCategories.Clear();
|
||||
AvailableCategoryFilters.Clear();
|
||||
|
||||
// 先添加"全部"选项到筛选器
|
||||
AvailableCategoryFilters.Add("全部");
|
||||
|
||||
// 从 CategoryAttributeManager 获取所有可用类别(内置 + 自定义)
|
||||
@ -1321,6 +1395,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
SelectedCategory = AvailableCategories[0];
|
||||
}
|
||||
|
||||
// 设置默认筛选器为"全部"
|
||||
SelectedCategoryFilter = "全部";
|
||||
});
|
||||
|
||||
// 注意:不在初始化时刷新选择状态和物流模型列表
|
||||
|
||||
@ -658,22 +658,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建并显示配置编辑器对话框
|
||||
var configEditorDialog = new NavisworksTransport.UI.WPF.Views.ConfigEditorDialog();
|
||||
|
||||
// 设置窗口所有者并显示对话框
|
||||
DialogHelper.SetOwnerSafely(configEditorDialog);
|
||||
bool? result = configEditorDialog.ShowDialog();
|
||||
|
||||
if (result == true)
|
||||
// 使用单例模式显示配置编辑器(非模态,避免独占焦点)
|
||||
// 不传递 owner,让 ShowEditor 自己通过 DialogHelper 处理
|
||||
var dialog = NavisworksTransport.UI.WPF.Views.ConfigEditorDialog.ShowEditor();
|
||||
|
||||
if (dialog != null)
|
||||
{
|
||||
UpdateMainStatus("配置已更新");
|
||||
LogManager.Info("配置编辑器:用户保存了配置");
|
||||
UpdateMainStatus("配置编辑器已打开");
|
||||
LogManager.Info("配置编辑器:已打开(非模态)");
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateMainStatus("配置编辑已取消");
|
||||
LogManager.Info("配置编辑器:用户取消了修改");
|
||||
UpdateMainStatus("配置编辑器已在运行");
|
||||
LogManager.Info("配置编辑器:窗口已存在并已激活");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@ -20,8 +20,7 @@ NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisw
|
||||
Height="700"
|
||||
Width="900"
|
||||
ResizeMode="CanResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Topmost="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
MinHeight="500"
|
||||
MinWidth="700">
|
||||
|
||||
@ -82,11 +81,6 @@ NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisw
|
||||
|
||||
<!-- 左侧工具按钮 -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Button Content="保存"
|
||||
Click="SaveButton_Click"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
Margin="0,0,10,0"
|
||||
ToolTip="保存配置文件"/>
|
||||
<Button Content="重新加载"
|
||||
Click="ReloadButton_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
|
||||
@ -8,14 +8,27 @@ using NavisworksTransport.Core.Config;
|
||||
namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置编辑器对话框
|
||||
/// 配置编辑器对话框 - 非模态窗口,避免独占焦点
|
||||
/// </summary>
|
||||
public partial class ConfigEditorDialog : Window
|
||||
{
|
||||
private string _originalContent;
|
||||
private bool _isModified;
|
||||
|
||||
public ConfigEditorDialog()
|
||||
/// <summary>
|
||||
/// 当前显示的编辑器实例(单例模式)
|
||||
/// </summary>
|
||||
private static ConfigEditorDialog _currentInstance = null;
|
||||
|
||||
/// <summary>
|
||||
/// 用于线程同步的锁对象
|
||||
/// </summary>
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 私有构造函数,防止外部直接创建
|
||||
/// </summary>
|
||||
private ConfigEditorDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
LoadConfigContent();
|
||||
@ -94,7 +107,6 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
try
|
||||
{
|
||||
string configPath = ConfigManager.ConfigFilePath;
|
||||
string content = ConfigContentTextBox.Text;
|
||||
|
||||
// 验证 TOML 格式
|
||||
@ -109,31 +121,26 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保目录存在
|
||||
Directory.CreateDirectory(ConfigManager.ConfigDirectory);
|
||||
|
||||
// 保存文件
|
||||
File.WriteAllText(configPath, content, System.Text.Encoding.UTF8);
|
||||
|
||||
// 重新加载配置
|
||||
ConfigManager.Instance.Reload();
|
||||
// 使用 ConfigManager 的 ReloadFromContent 方法保存并加载
|
||||
// 这个方法会临时禁用 FileSystemWatcher,避免重复触发
|
||||
ConfigManager.Instance.ReloadFromContent(content);
|
||||
|
||||
_originalContent = content;
|
||||
_isModified = false;
|
||||
ModifiedLabel.Content = "";
|
||||
|
||||
StatusLabel.Content = "配置已保存";
|
||||
StatusLabel.Content = "配置已应用";
|
||||
StatusLabel.Foreground = System.Windows.Media.Brushes.Green;
|
||||
|
||||
UpdateStatus();
|
||||
|
||||
LogManager.Info("配置文件已保存");
|
||||
LogManager.Info("配置已应用");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"保存配置失败: {ex.Message}");
|
||||
MessageBox.Show($"保存配置失败:\n{ex.Message}",
|
||||
LogManager.Error($"应用配置失败: {ex.Message}");
|
||||
MessageBox.Show($"应用配置失败:\n{ex.Message}",
|
||||
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return false;
|
||||
}
|
||||
@ -141,11 +148,6 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
|
||||
// 事件处理器
|
||||
|
||||
private void SaveButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
private void ReloadButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isModified)
|
||||
@ -210,6 +212,9 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用配置(保存但不关闭窗口)
|
||||
/// </summary>
|
||||
private void ApplyButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveConfig();
|
||||
@ -221,13 +226,11 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
if (SaveConfig())
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
@ -244,7 +247,6 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
return;
|
||||
}
|
||||
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
@ -266,5 +268,145 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
UpdateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口关闭事件
|
||||
/// </summary>
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 清除静态实例引用
|
||||
lock (_lock)
|
||||
{
|
||||
if (_currentInstance == this)
|
||||
{
|
||||
_currentInstance = null;
|
||||
LogManager.Info("配置编辑器对话框实例已从单例缓存中移除");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"清理配置编辑器对话框资源失败: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口加载事件 - 处理激活逻辑避免独占焦点
|
||||
/// </summary>
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
base.OnSourceInitialized(e);
|
||||
|
||||
try
|
||||
{
|
||||
// 激活并前置窗口,但不独占焦点
|
||||
// 先置顶再取消置顶,让窗口前置但不系统级置顶
|
||||
this.Activate();
|
||||
this.Topmost = true;
|
||||
this.Topmost = false;
|
||||
|
||||
LogManager.Debug("配置编辑器对话框已激活并前置显示");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"配置编辑器对话框初始化失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 激活并显示已存在的窗口
|
||||
/// </summary>
|
||||
private void ActivateWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果窗口被最小化,则恢复
|
||||
if (this.WindowState == WindowState.Minimized)
|
||||
{
|
||||
this.WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
// 激活并前置窗口(先置顶再取消置顶,避免独占焦点)
|
||||
this.Activate();
|
||||
this.Topmost = true;
|
||||
this.Topmost = false;
|
||||
|
||||
LogManager.Info("配置编辑器对话框已激活并前置显示");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"激活配置编辑器对话框失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#region 静态工厂方法
|
||||
|
||||
/// <summary>
|
||||
/// 创建并显示配置编辑器对话框(单例模式,非模态)
|
||||
/// </summary>
|
||||
/// <returns>对话框实例</returns>
|
||||
public static ConfigEditorDialog ShowEditor()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// 如果当前已有实例且仍然打开,则激活窗口
|
||||
if (_currentInstance != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查窗口是否仍然有效
|
||||
if (_currentInstance.IsLoaded && _currentInstance.IsVisible)
|
||||
{
|
||||
LogManager.Info("检测到已存在的配置编辑器窗口,激活窗口");
|
||||
_currentInstance.ActivateWindow();
|
||||
return _currentInstance;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 窗口已关闭,清除引用
|
||||
_currentInstance = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Warning($"检查现有窗口状态时出现异常: {ex.Message},将创建新窗口");
|
||||
_currentInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新的对话框实例
|
||||
var dialog = new ConfigEditorDialog();
|
||||
|
||||
// 使用 DialogHelper 安全地设置 owner
|
||||
NavisworksTransport.Utils.DialogHelper.SetOwnerSafely(dialog);
|
||||
|
||||
// 设置为当前实例
|
||||
_currentInstance = dialog;
|
||||
|
||||
// 使用 Show() 非模态显示,避免独占焦点
|
||||
dialog.Show();
|
||||
|
||||
LogManager.Info("新的配置编辑器对话框已显示(非模态)");
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"显示配置编辑器对话框失败: {ex.Message}");
|
||||
MessageBox.Show($"显示配置编辑器对话框失败:\n{ex.Message}", "错误",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user