| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- 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();
- /// <summary>
- /// Runtime 根目录(业务依赖、Plugins、native 库统一存放于此)。
- /// 根目录只保留 EXE + 配置类文件夹(Config/Logs/Products/Recipe/zh-CN)。
- /// </summary>
- public static string RuntimeDir => PathHelper.RuntimeDir;
- protected override Window CreateShell()
- {
- return Container.Resolve<MainWindow>();
- }
- 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);
- }
- /// <summary>
- /// 全局窗口 Loaded 钩子:主窗口计算全局缩放因子,弹窗/子窗体跟随。
- /// (UiScaler.Attach 内部有幂等保护,重复触发不会重复挂接。)
- /// </summary>
- 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);
- }
- /// <summary>
- /// 把 Runtime\dll\<arch> 加入 native DLL 搜索路径(P/Invoke、DllImport 用)。
- /// 通过 SetDllDirectory 让 LoadLibrary 在该目录查找 native 库。
- /// 架构按当前进程位数自动选择 x86 / x64,兼容 32 位与 64 位部署。
- /// </summary>
- 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<string, Assembly> _resolvedCache =
- new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
- /// <summary>
- /// 在 Runtime 目录下递归查找缺失程序集;同名 DLL 已加载过则直接返回缓存,跳过重复加载避免报错。
- /// 同时兼容旧的 EXE 根目录布局(迁移期内根目录残留 DLL 也能找到)。
- /// </summary>
- 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;
- }
- /// <summary>
- /// 在指定根目录下递归查找匹配名称的 .dll,找到第一个即返回。
- /// 多个位置存在同名 DLL 时优先返回 Plugins 子目录的版本(避免插件重复依赖冲突)。
- /// </summary>
- 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<SystemConfiguration>(json) ?? new SystemConfiguration();
- }
- }
- catch { SystemConfig = new SystemConfiguration(); }
- // 应用插件日志配置(根目录/路径公式/默认等级)
- TeamAAS.Logging.PluginLogger.Configure(SystemConfig.PluginLog);
- UpdateFontResources();
- ApplyTheme();
- }
- /// <summary>
- /// 初始化多语言系统
- /// </summary>
- 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}");
- }
- }
- /// <summary>
- /// 同步 PropertyGrid 语言设置(culture 开集,直接使用语言代码,如 "en-US"、"ja-JP")
- /// </summary>
- 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。
- // 主程序只调用门面接口,不感知实现细节——保持主程序简洁。
- // ────────────────────────────────────────────────────────────────────
- /// <summary>
- /// 按 SystemConfiguration 完整应用(启动/设置页保存时调用):
- /// 读取配置后转发给 TeamAAS.Theme.ThemeManager。
- /// </summary>
- 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);
- }
- /// <summary>
- /// 保存系统配置到 Config\SystemConfiguration.json(主题/主色/字体/标题)。
- /// </summary>
- 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<MainWindowViewModel>();
- // 注册设备管理器并加载配置(相机/机器人/通讯/供料器)
- // CameraManager 使用懒汉单例,DI 容器直接复用单例实例
- containerRegistry.RegisterInstance<ICameraManager>(CameraManager.Instance);
- containerRegistry.RegisterInstance<IRobotManager>(RobotManager.Instance);
- containerRegistry.RegisterInstance<CommunicationManager>(CommunicationManager.Instance);
- containerRegistry.RegisterInstance<IFeederManager>(FeederManager.Instance);
- containerRegistry.RegisterInstance<IPluginLoader>(PluginLoader.Instance);
- containerRegistry.RegisterInstance<MotionManager>(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<ICameraManager>(cameraManager);
- sdkRegistry.Register(robotManager);
- sdkRegistry.Register<IRobotManager>(robotManager);
- sdkRegistry.Register(commManager);
- sdkRegistry.Register(feederManager);
- sdkRegistry.Register<IFeederManager>(feederManager);
- sdkRegistry.Register(motionManager);
- containerRegistry.RegisterSingleton<SettingViewModel>();
- containerRegistry.RegisterForNavigation<HomeView>("HomeView");
- containerRegistry.RegisterForNavigation<ProductView>("ProductView");
- containerRegistry.RegisterForNavigation<SettingView>("SettingView");
- containerRegistry.RegisterForNavigation<CalibrationView>("CalibrationView");
- containerRegistry.RegisterForNavigation<StatisticsView>("StatisticsView");
- containerRegistry.RegisterForNavigation<UserView>("UserView");
- }
- }
- }
|