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");
}
}
}