using Prism.DryIoc; using Prism.Ioc; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text.Json; using System.Windows; using System.Windows.Media; using TeamAAS.Camera.Interfaces; using TeamAAS.Robot.Interfaces; using TeamAAS.Robot; using TeamAAS.Dialogs; using TeamAAS.ViewModels; using TeamAAS.Views; using TeamAAS.FlowEngine; using TeamAAS.FlowEditor.Execution; using TeamAAS.FlowEngine.Interfaces; using TeamAAS.FlowEngine.Execution; using System.Diagnostics; using TeamAAS.Communication; using TeamAAS.Camera; using TeamAAS.Core; using TeamAAS.Feeder.Interfaces; using TeamAAS.Feeder; using TeamAAS.Motion; using TeamAAS.Models; using TeamAAS.Localization; using TeamAAS.Theme; using PropertyGridLib.Localization; namespace TeamAAS { public partial class App : PrismApplication { public static SystemConfiguration SystemConfig { get; set; } = new SystemConfiguration(); /// /// Runtime 根目录(业务依赖、Plugins、native 库统一存放于此)。 /// 根目录只保留 EXE + 配置类文件夹(Config/Logs/Products/Recipe/zh-CN)。 /// public static string RuntimeDir => PathHelper.RuntimeDir; protected override Window CreateShell() { return Container.Resolve(); } protected override void OnStartup(StartupEventArgs e) { // 1) 把 native DLL 搜索路径指向 Runtime\dll\x64(OpenCV 等非 .NET 库) SetupNativeDllSearchPath(); // 2) 注册 AssemblyResolve:在 Runtime 下递归查找缺失程序集,重复 DLL 直接跳过 AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; // 3) 加载插件(Plugins 现在位于 Runtime\Plugins) string pluginsPath = PathHelper.PluginsDir; PluginLoader.Instance.LoadFrom(pluginsPath); // 4) 加载系统配置(字体/标题/主题/主色) LoadSystemConfig(); // 5) 初始化多语言系统 InitializeLocalization(); // 6) 初始化全局变量管理器(加载机器级 Global 固定变量) GlobalVariableManager.Instance.LoadGlobalScoped(); // 7) 初始化 ResultRegistry(订阅全局变量变化) ResultRegistry.Initialize(); // 7.1) 探测可用视觉引擎(反射扫描 + 尝试加载已知引擎类库), // 并按配置设置当前引擎(Vision.EngineName)——主页画面/标定页随引擎切换 TeamAAS.Vision.VisualEngineManager.Instance.Discover(); TeamAAS.Vision.VisualEngineManager.Instance.SetCurrent(SystemConfig?.Vision?.EngineName); // 8) 全局窗口自适应:所有 Window(主窗/子窗体/对话框/弹窗)Loaded 后自动挂 UiScaler, // 以后新增任何窗体都无需手动接线 EventManager.RegisterClassHandler(typeof(Window), FrameworkElement.LoadedEvent, new RoutedEventHandler(OnAnyWindowLoaded), true); // 9) 全局异常兑底:任何未处理异常先落主页日志(含堆栈),UI 线程异常标记已处理避免闪退; // 进程级/任务级异常虽救不回来,但至少留下死因可查。触发场景如:VM 控件污染 // 应用级画刷资源导致 HC 样式 String→Brush 强转失败(详见 Plugins.Vm/VmThemeGuard) DispatcherUnhandledException += (s, args) => { AppLogger.Error("UI 线程未处理异常: " + args.Exception.Message, args.Exception, nameof(App)); args.Handled = true; }; AppDomain.CurrentDomain.UnhandledException += (s, args) => AppLogger.Error("致命未处理异常(进程即将退出): " + args.ExceptionObject, args.ExceptionObject as Exception, nameof(App)); System.Threading.Tasks.TaskScheduler.UnobservedTaskException += (s, args) => { AppLogger.Error("未观察的任务异常: " + args.Exception.Message, args.Exception, nameof(App)); args.SetObserved(); }; base.OnStartup(e); } /// /// 全局窗口 Loaded 钩子:主窗口计算全局缩放因子,弹窗/子窗体跟随。 /// (UiScaler.Attach 内部有幂等保护,重复触发不会重复挂接。) /// private static void OnAnyWindowLoaded(object sender, RoutedEventArgs e) { if (!(sender is Window window)) return; if (Application.Current.MainWindow == window) UiScaler.AttachMain(window); else UiScaler.Attach(window); } /// /// 把 Runtime\dll\ 加入 native DLL 搜索路径(P/Invoke、DllImport 用)。 /// 通过 SetDllDirectory 让 LoadLibrary 在该目录查找 native 库。 /// 架构按当前进程位数自动选择 x86 / x64,兼容 32 位与 64 位部署。 /// private static void SetupNativeDllSearchPath() { try { string arch = IntPtr.Size == 8 ? "x64" : "x86"; string nativeDir = Path.Combine(RuntimeDir, "dll", arch); if (Directory.Exists(nativeDir)) { SetDllDirectory(nativeDir); } } catch { /* 失败不影响启动,缺失 native 库时由调用方报错 */ } } [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool SetDllDirectory(string lpPathName); private static readonly Dictionary _resolvedCache = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// 在 Runtime 目录下递归查找缺失程序集;同名 DLL 已加载过则直接返回缓存,跳过重复加载避免报错。 /// 同时兼容旧的 EXE 根目录布局(迁移期内根目录残留 DLL 也能找到)。 /// private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) { try { string name = new AssemblyName(e.Name).Name; if (string.IsNullOrEmpty(name)) return null; // 1) 已加载过 → 直接返回(跳过重复,避免重复加载报错 if (_resolvedCache.TryGetValue(name, out var cached)) return cached; // 2) Runtime 目录递归查找(包含 Plugins 子目录、dll 子目录等) var found = TryFindAssembly(RuntimeDir, name); if (found != null) { _resolvedCache[name] = found; return found; } // 3) 兼容:根目录残留 DLL 也能找到(迁移期间用) var rootPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, name + ".dll"); if (File.Exists(rootPath)) { var asm = Assembly.LoadFrom(rootPath); _resolvedCache[name] = asm; return asm; } } catch { } return null; } /// /// 在指定根目录下递归查找匹配名称的 .dll,找到第一个即返回。 /// 多个位置存在同名 DLL 时优先返回 Plugins 子目录的版本(避免插件重复依赖冲突)。 /// private static Assembly TryFindAssembly(string rootDir, string name) { if (!Directory.Exists(rootDir)) return null; string target = name + ".dll"; string firstHit = null; // 优先 Plugins 子目录(插件依赖隔离) var pluginsSub = Path.Combine(rootDir, "Plugins"); if (Directory.Exists(pluginsSub)) { var p = Path.Combine(pluginsSub, target); if (File.Exists(p)) firstHit = p; } // 再扫描 Runtime 全部子目录(深度优先) if (firstHit == null) { foreach (var file in Directory.EnumerateFiles(rootDir, target, SearchOption.AllDirectories)) { firstHit = file; break; } } if (firstHit == null) return null; try { return Assembly.LoadFrom(firstHit); } catch { return null; } } protected override void OnInitialized() { // 运行引擎接线:注入通讯管理器 + 启动心跳监听(触发运行产品执行) FlowRunner.Instance.Communication = CoreManager.Communication; FlowRunner.Instance.StartHeartbeat(); base.OnInitialized(); } protected override void OnExit(ExitEventArgs e) { // 退出前把所有相机停止采集并断开,避免设备句柄残留导致下次打不开 try { CameraManager.Instance.CloseAll(); } catch { } try { MotionManager.Instance.CloseAll(); } catch { } base.OnExit(e); } private static void LoadSystemConfig() { try { var path = PathHelper.SystemConfigurationFile; if (File.Exists(path)) { var json = File.ReadAllText(path); SystemConfig = JsonSerializer.Deserialize(json) ?? new SystemConfiguration(); } } catch { SystemConfig = new SystemConfiguration(); } // 应用插件日志配置(根目录/路径公式/默认等级) TeamAAS.Logging.PluginLogger.Configure(SystemConfig.PluginLog); UpdateFontResources(); ApplyTheme(); } /// /// 初始化多语言系统 /// private static void InitializeLocalization() { try { // 1. 加载所有语言包(.json 人类可读格式) // PostBuild 会把根目录的 Localization 移到 Runtime\Localization,两个路径都兼容 var localizationPath = PathHelper.ResolveLocalizationDir(); GlobalLocalizationManager.Instance.LoadLanguagePacks(localizationPath); // 2. 设置回退语言为中文(当前语言=回退语言时,直接显示中文原文) GlobalLocalizationManager.Instance.FallbackLanguage = "zh-CN"; // 3. 注入 PropertyGrid 全局翻译钩子:把 [DisplayName]/[Description]/[Category] // 特性里的中文原文交给 GlobalLocalizationManager 查语言包翻译(插件零改动) LocalizationManager.TextResolver = text => GlobalLocalizationManager.Instance.Translate(text); // 4. 从系统配置加载上次保存的语言 var savedLanguage = SystemConfig?.Language ?? "zh-CN"; GlobalLocalizationManager.Instance.CurrentLanguage = savedLanguage; // 5. 同步 PropertyGrid 语言(culture 开集,直接用语言代码) SyncPropertyGridLanguage(); // 6. 监听语言切换事件(设置页切换语言后,PropertyGrid 实时刷新) GlobalLocalizationManager.Instance.LanguageChanged += (s, e) => { SyncPropertyGridLanguage(); }; Console.WriteLine($"[Localization] 多语言系统已初始化,当前语言: {savedLanguage}"); } catch (Exception ex) { Console.WriteLine($"[Localization] 初始化失败: {ex.Message}"); } } /// /// 同步 PropertyGrid 语言设置(culture 开集,直接使用语言代码,如 "en-US"、"ja-JP") /// private static void SyncPropertyGridLanguage() { var currentLang = GlobalLocalizationManager.Instance.CurrentLanguage; LocalizationManager.CurrentCulture = currentLang; } public static void UpdateFontResources() { var font = SystemConfig?.Font ?? new FontSizeSettings(); var resources = Application.Current?.Resources; if (resources == null) return; resources["NavButtonFontSize"] = font.NavButtonFont; resources["NavButtonWidth"] = font.NavButtonWidth; resources["NavButtonHeight"] = font.NavButtonHeight; resources["NavIconSize"] = font.NavIconSize; resources["TopBarHeight"] = font.TopBarHeight; resources["CenterTitleFontSize"] = font.CenterTitleFont; resources["ThemeIconFontSize"] = font.ThemeIconSize; resources["StatusBarHeight"] = font.StatusBarHeight; resources["StatusBarFontSize"] = font.StatusBarFont; var title = SystemConfig?.Title ?? "TeamAAS"; resources["CenterTitleText"] = title; if (Application.Current.MainWindow != null) Application.Current.MainWindow.Title = title; } // ──────────────────────────────────────────────────────────────────── // 主题与主色已下沉到 TeamAAS.Core\TeamAAS.Global\Theme(命名空间 TeamAAS.Theme): // ThemeManager(门面:ApplyTheme/UpdateTheme/UpdateAccent/RepairTheme) // + VmResourceGuard(资源守护)+ ThemeOverlay(覆盖字典/主色派生)+ ColorUtility。 // 主程序只调用门面接口,不感知实现细节——保持主程序简洁。 // ──────────────────────────────────────────────────────────────────── /// /// 按 SystemConfiguration 完整应用(启动/设置页保存时调用): /// 读取配置后转发给 TeamAAS.Theme.ThemeManager。 /// public static void ApplyTheme() { var cfg = SystemConfig ?? new SystemConfiguration(); bool dark = string.Equals(cfg.Theme, "Dark", StringComparison.OrdinalIgnoreCase); AppLogger.Info($"ApplyTheme: dark={dark}, accent={cfg.AccentColor}", nameof(App)); ThemeManager.ApplyTheme(dark, cfg.AccentColor); } /// /// 保存系统配置到 Config\SystemConfiguration.json(主题/主色/字体/标题)。 /// public static void SaveSystemConfig() { try { var path = PathHelper.SystemConfigurationFile; var json = JsonSerializer.Serialize(SystemConfig, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(path, json); // 保存后立即应用插件日志配置 TeamAAS.Logging.PluginLogger.Configure(SystemConfig.PluginLog); } catch (Exception ex) { AppLogger.Error("系统配置保存失败", ex, nameof(App)); } } protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton(); // 注册设备管理器并加载配置(相机/机器人/通讯/供料器) // CameraManager 使用懒汉单例,DI 容器直接复用单例实例 containerRegistry.RegisterInstance(CameraManager.Instance); containerRegistry.RegisterInstance(RobotManager.Instance); containerRegistry.RegisterInstance(CommunicationManager.Instance); containerRegistry.RegisterInstance(FeederManager.Instance); containerRegistry.RegisterInstance(PluginLoader.Instance); containerRegistry.RegisterInstance(MotionManager.Instance); var commManager = CommunicationManager.Instance; commManager.LoadConfig(); var cameraManager = CameraManager.Instance; cameraManager.LoadConfig(); var robotManager = RobotManager.Instance; robotManager.LoadConfig(); var feederManager = FeederManager.Instance; feederManager.LoadConfig(); // 运动控制设备(设备内核新增):加载 Config\MotionDevices.json,无配置文件时为空列表 var motionManager = MotionManager.Instance; motionManager.LoadConfig(); // 将 DI 单例赋值给 CoreManager,确保全局同一实例 CoreManager.Camera = cameraManager; CoreManager.Robot = robotManager; CoreManager.Communication = commManager; CoreManager.Feeder = feederManager; CoreManager.Motion = motionManager; // SDK 服务注册表接线:把同一批单例注册进 ServiceRegistry(适配器,零行为变化)。 // 具体类型 + 接口双注册:CoreManager 门面按具体类型取,插件按接口经 IPluginContext 取。 var sdkRegistry = TeamAAS.Global.ServiceRegistry.Default; sdkRegistry.Register(cameraManager); sdkRegistry.Register(cameraManager); sdkRegistry.Register(robotManager); sdkRegistry.Register(robotManager); sdkRegistry.Register(commManager); sdkRegistry.Register(feederManager); sdkRegistry.Register(feederManager); sdkRegistry.Register(motionManager); containerRegistry.RegisterSingleton(); containerRegistry.RegisterForNavigation("HomeView"); containerRegistry.RegisterForNavigation("ProductView"); containerRegistry.RegisterForNavigation("SettingView"); containerRegistry.RegisterForNavigation("CalibrationView"); containerRegistry.RegisterForNavigation("StatisticsView"); containerRegistry.RegisterForNavigation("UserView"); } } }