422 lines
17 KiB
C#
422 lines
17 KiB
C#
using DryIoc;
|
||
using Prism.DryIoc;
|
||
using Prism.Ioc;
|
||
using Prism.Modularity;
|
||
using Serilog;
|
||
using System;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Threading;
|
||
using System.Windows;
|
||
using Telerik.Windows.Controls;
|
||
using XP.Camera;
|
||
using XP.Common.Configs;
|
||
using XP.Common.Database.Implementations;
|
||
using XP.Common.Database.Interfaces;
|
||
using XP.Common.GeneralForm.Views;
|
||
using XP.Common.Dump.Configs;
|
||
using XP.Common.Dump.Implementations;
|
||
using XP.Common.Dump.Interfaces;
|
||
using XP.Common.Helpers;
|
||
using XP.Common.Localization.Configs;
|
||
using XP.Common.Localization.Extensions;
|
||
using XP.Common.Localization.Implementations;
|
||
using XP.Common.Localization.Interfaces;
|
||
using XP.Common.Logging;
|
||
using XP.Common.Logging.Implementations;
|
||
using XP.Common.Logging.Interfaces;
|
||
using XP.Common.Module;
|
||
using XP.Hardware.Detector.Module;
|
||
using XP.Hardware.MotionControl.Module;
|
||
using XP.Hardware.PLC;
|
||
using XP.Hardware.RaySource.Module;
|
||
using XP.Hardware.RaySource.Services;
|
||
using XplorePlane.Services;
|
||
using XplorePlane.Services.AppState;
|
||
using XplorePlane.Services.Camera;
|
||
using XplorePlane.Services.Cnc;
|
||
using XplorePlane.Services.Matrix;
|
||
using XplorePlane.Services.Measurement;
|
||
using XplorePlane.Services.Recipe;
|
||
using XplorePlane.ViewModels;
|
||
using XplorePlane.ViewModels.Cnc;
|
||
using XplorePlane.Views;
|
||
using XplorePlane.Views.Cnc;
|
||
|
||
namespace XplorePlane
|
||
{
|
||
/// <summary>
|
||
/// Interaction logic for App.xaml
|
||
/// </summary>
|
||
public partial class App : Application
|
||
{
|
||
protected override void OnStartup(StartupEventArgs e)
|
||
{
|
||
// 设置 Telerik Windows11 主题,缩小 Ribbon 整体尺寸
|
||
StyleManager.ApplicationTheme = new Windows11Theme();
|
||
|
||
// 强制使用中文 UI,确保 ImageProcessing 库显示中文
|
||
var zhCN = new CultureInfo("zh-CN");
|
||
Thread.CurrentThread.CurrentCulture = zhCN;
|
||
Thread.CurrentThread.CurrentUICulture = zhCN;
|
||
CultureInfo.DefaultThreadCurrentCulture = zhCN;
|
||
CultureInfo.DefaultThreadCurrentUICulture = zhCN;
|
||
|
||
// 配置 Serilog 日志系统
|
||
ConfigureLogging();
|
||
|
||
// 捕获未处理的异常
|
||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||
|
||
try
|
||
{
|
||
base.OnStartup(e);
|
||
|
||
// Initialize Prism with DryIoc
|
||
var bootstrapper = new AppBootstrapper();
|
||
bootstrapper.Run();
|
||
}
|
||
catch (FileNotFoundException ex)
|
||
{
|
||
Log.Fatal(ex, "Required DLL not found: {FileName}", ex.FileName);
|
||
MessageBox.Show(
|
||
$"Required library not found: {ex.FileName}\n\nPlease ensure all required DLLs are present in the Libs/ImageProcessing/ directory.",
|
||
"Missing Library",
|
||
MessageBoxButton.OK,
|
||
MessageBoxImage.Error);
|
||
Shutdown(1);
|
||
}
|
||
catch (TypeLoadException ex)
|
||
{
|
||
Log.Fatal(ex, "Failed to load type from DLL: {TypeName}", ex.TypeName);
|
||
MessageBox.Show(
|
||
$"Failed to load required type: {ex.TypeName}\n\nPlease ensure the correct version of DLLs are present in the Libs/ImageProcessing/ directory.",
|
||
"Library Load Error",
|
||
MessageBoxButton.OK,
|
||
MessageBoxImage.Error);
|
||
Shutdown(1);
|
||
}
|
||
}
|
||
|
||
private void ConfigureLogging()
|
||
{
|
||
// 加载Serilog配置 | Load Serilog configuration
|
||
SerilogConfig serilogConfig = ConfigLoader.LoadSerilogConfig();
|
||
// 初始化Serilog(全局唯一)| Initialize Serilog (global singleton)
|
||
SerilogInitializer.Initialize(serilogConfig);
|
||
|
||
// 记录应用启动日志 | Log application startup
|
||
Log.Information("========================================");
|
||
Log.Information("XplorePlane 应用程序启动");
|
||
Log.Information("========================================");
|
||
}
|
||
|
||
protected override void OnExit(ExitEventArgs e)
|
||
{
|
||
Log.Information("========================================");
|
||
Log.Information("XplorePlane 应用程序退出");
|
||
Log.Information("========================================");
|
||
|
||
// 释放全局状态服务资源
|
||
try
|
||
{
|
||
var bootstrapper = AppBootstrapper.Instance;
|
||
if (bootstrapper != null)
|
||
{
|
||
var appStateService = bootstrapper.Container.Resolve<IAppStateService>();
|
||
appStateService?.Dispose();
|
||
Log.Information("全局状态服务资源已释放");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error(ex, "全局状态服务资源释放失败");
|
||
}
|
||
|
||
// 释放射线源资源
|
||
try
|
||
{
|
||
var bootstrapper = AppBootstrapper.Instance;
|
||
if (bootstrapper != null)
|
||
{
|
||
var raySourceService = bootstrapper.Container.Resolve<IRaySourceService>();
|
||
raySourceService?.Dispose();
|
||
Log.Information("射线源资源已释放");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error(ex, "射线源资源释放失败");
|
||
}
|
||
|
||
// 先停止导航相机实时采集,再释放资源,避免回调死锁
|
||
try
|
||
{
|
||
var bootstrapper = AppBootstrapper.Instance;
|
||
if (bootstrapper != null)
|
||
{
|
||
var cameraVm = bootstrapper.Container.Resolve<NavigationPropertyPanelViewModel>();
|
||
cameraVm?.Dispose();
|
||
Log.Information("导航相机 ViewModel 已释放");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error(ex, "导航相机 ViewModel 释放失败");
|
||
}
|
||
|
||
// 释放导航相机服务资源
|
||
try
|
||
{
|
||
var bootstrapper = AppBootstrapper.Instance;
|
||
if (bootstrapper != null)
|
||
{
|
||
var cameraService = bootstrapper.Container.Resolve<ICameraService>();
|
||
cameraService?.Dispose();
|
||
Log.Information("导航相机服务资源已释放");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error(ex, "导航相机服务资源释放失败");
|
||
}
|
||
|
||
// 释放SQLite数据库资源 | Release SQLite database resources
|
||
try
|
||
{
|
||
var bootstrapper = AppBootstrapper.Instance;
|
||
if (bootstrapper != null)
|
||
{
|
||
var dbContext = bootstrapper.Container.Resolve<IDbContext>(); // 从Prism容器获取IDbContext实例(单例)| Get IDbContext instance from Prism container (singleton)
|
||
dbContext?.Dispose();
|
||
Log.Information("数据库资源已成功释放 | Database resources released successfully");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error(ex, "数据库资源释放失败,忽略该错误继续退出 | Database resource release failed, ignoring error and continuing exit");
|
||
}
|
||
|
||
Log.CloseAndFlush();
|
||
base.OnExit(e);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理未捕获的异常
|
||
/// </summary>
|
||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||
{
|
||
var exception = e.ExceptionObject as Exception;
|
||
Log.Fatal(exception, "应用程序发生未处理的异常");
|
||
|
||
MessageBox.Show(
|
||
$"应用程序发生严重错误:\n\n{exception?.Message}\n\n请查看日志文件获取详细信息。",
|
||
"严重错误",
|
||
MessageBoxButton.OK,
|
||
MessageBoxImage.Error);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理 UI 线程未捕获的异常
|
||
/// </summary>
|
||
private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||
{
|
||
Log.Error(e.Exception, "UI 线程发生未处理的异常");
|
||
|
||
MessageBox.Show(
|
||
$"应用程序发生错误:\n\n{e.Exception.Message}\n\n请查看日志文件获取详细信息。",
|
||
"错误",
|
||
MessageBoxButton.OK,
|
||
MessageBoxImage.Error);
|
||
|
||
// 标记为已处理,防止应用程序崩溃
|
||
e.Handled = true;
|
||
}
|
||
}
|
||
|
||
public class AppBootstrapper : PrismBootstrapper
|
||
{
|
||
public static AppBootstrapper Instance { get; private set; }
|
||
|
||
public new IContainerProvider Container => base.Container;
|
||
|
||
public AppBootstrapper()
|
||
{
|
||
Instance = this;
|
||
}
|
||
|
||
private bool _modulesInitialized = false;
|
||
|
||
private string? _cameraError;
|
||
|
||
protected override Window CreateShell()
|
||
{
|
||
// 提前初始化模块,确保硬件服务在 MainWindow XAML 解析前已注册
|
||
if (!_modulesInitialized)
|
||
{
|
||
base.InitializeModules();
|
||
_modulesInitialized = true;
|
||
}
|
||
|
||
var shell = Container.Resolve<MainWindow>();
|
||
|
||
// 主窗口加载完成后再连接相机,确保所有模块和原生 DLL 已完成初始化
|
||
shell.Loaded += (s, e) =>
|
||
{
|
||
TryConnectCamera();
|
||
|
||
// 通知 ViewModel 相机状态已确定,启动实时预览或显示错误
|
||
try
|
||
{
|
||
var cameraVm = Container.Resolve<NavigationPropertyPanelViewModel>();
|
||
cameraVm.OnCameraReady();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error(ex, "通知相机 ViewModel 失败");
|
||
}
|
||
|
||
if (_cameraError != null)
|
||
{
|
||
HexMessageBox.Show(_cameraError, MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
};
|
||
|
||
return shell;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在主线程上检索并连接导航相机。
|
||
/// pylon SDK 要求在主线程(STA)上操作,不能放到后台线程。
|
||
/// </summary>
|
||
private void TryConnectCamera()
|
||
{
|
||
var camera = Container.Resolve<ICameraController>();
|
||
|
||
try
|
||
{
|
||
var info = camera.Open();
|
||
Log.Information("导航相机已连接: {ModelName} (SN: {SerialNumber})", info.ModelName, info.SerialNumber);
|
||
}
|
||
catch (DeviceNotFoundException)
|
||
{
|
||
Log.Warning("未检测到导航相机");
|
||
_cameraError = "未检测到导航相机,请检查连接后重启软件。";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Warning(ex, "导航相机自动连接失败: {Message}", ex.Message);
|
||
_cameraError = $"导航相机连接失败: {ex.Message}";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 模块已在 CreateShell 中提前初始化,此处跳过避免重复加载
|
||
/// </summary>
|
||
protected override void InitializeModules()
|
||
{
|
||
if (!_modulesInitialized)
|
||
{
|
||
base.InitializeModules();
|
||
_modulesInitialized = true;
|
||
}
|
||
}
|
||
|
||
protected override void RegisterTypes(IContainerRegistry containerRegistry)
|
||
{
|
||
// 注册 Serilog 的 ILogger 实例
|
||
containerRegistry.RegisterInstance<ILogger>(Log.Logger);
|
||
|
||
// 注册 XP.Common.ILoggerService 适配器
|
||
containerRegistry.RegisterSingleton<ILoggerService, SerilogLoggerService>();
|
||
|
||
// 注册视图和视图模型
|
||
containerRegistry.RegisterForNavigation<MainWindow>();
|
||
containerRegistry.Register<MainViewModel>();
|
||
containerRegistry.RegisterSingleton<NavigationPropertyPanelViewModel>();
|
||
|
||
// 注册图像处理服务与视图
|
||
containerRegistry.RegisterSingleton<IImageProcessingService, ImageProcessingService>();
|
||
containerRegistry.Register<ImageProcessingViewModel>();
|
||
containerRegistry.RegisterForNavigation<ImageProcessingPanelView>();
|
||
|
||
// 注册流水线服务(单例,共享 IImageProcessingService)
|
||
containerRegistry.RegisterSingleton<IPipelineExecutionService, PipelineExecutionService>();
|
||
containerRegistry.RegisterSingleton<IPipelinePersistenceService, PipelinePersistenceService>();
|
||
|
||
// 注册全局状态服务(单例)
|
||
containerRegistry.RegisterSingleton<IAppStateService, AppStateService>();
|
||
|
||
// 注册检测配方服务(单例)
|
||
containerRegistry.RegisterSingleton<IRecipeService, RecipeService>();
|
||
|
||
// 注册流水线 ViewModel(每次解析创建新实例)
|
||
containerRegistry.Register<PipelineEditorViewModel>();
|
||
containerRegistry.Register<OperatorToolboxViewModel>();
|
||
|
||
// 注册硬件库的 ViewModel(供 ViewModelLocator 自动装配)
|
||
containerRegistry.Register<XP.Hardware.RaySource.ViewModels.RaySourceOperateViewModel>();
|
||
|
||
// 注册 SQLite 配置和数据库上下文(FilamentLifetimeService 依赖)
|
||
var sqliteConfig = XP.Common.Helpers.ConfigLoader.LoadSqliteConfig();
|
||
containerRegistry.RegisterInstance(sqliteConfig);
|
||
containerRegistry.RegisterSingleton<IDbContext, SqliteContext>();
|
||
|
||
// 注册硬件库的 ViewModel(供 ViewModelLocator 自动装配)
|
||
//containerRegistry.Register<XP.Hardware.RaySource.ViewModels.RaySourceOperateViewModel>();
|
||
// 手动注册射线源模块的所有服务(确保 DryIoc 容器中可用,避免模块加载顺序问题)
|
||
//var raySourceConfig = XP.Hardware.RaySource.Config.ConfigLoader.LoadConfig();
|
||
//containerRegistry.RegisterInstance(raySourceConfig);
|
||
//containerRegistry.RegisterSingleton<XP.Hardware.RaySource.Abstractions.IRaySourceFactory, XP.Hardware.RaySource.Factories.RaySourceFactory>();
|
||
//containerRegistry.RegisterSingleton<IRaySourceService, XP.Hardware.RaySource.Services.RaySourceService>();
|
||
//containerRegistry.RegisterSingleton<XP.Hardware.RaySource.Services.IFilamentLifetimeService, XP.Hardware.RaySource.Services.FilamentLifetimeService>();
|
||
|
||
// 手动注册通用模块的服务(本地化、Dump)
|
||
containerRegistry.RegisterSingleton<ILocalizationConfig, LocalizationConfig>();
|
||
containerRegistry.RegisterSingleton<ILocalizationService, ResxLocalizationService>();
|
||
containerRegistry.RegisterSingleton<DumpConfig>(() => XP.Common.Helpers.ConfigLoader.LoadDumpConfig());
|
||
containerRegistry.RegisterSingleton<IDumpService, DumpService>();
|
||
|
||
// ── CNC / 矩阵编排 / 测量数据服务(单例)──
|
||
containerRegistry.RegisterSingleton<ICncProgramService, CncProgramService>();
|
||
containerRegistry.RegisterSingleton<IMatrixService, MatrixService>();
|
||
containerRegistry.RegisterSingleton<IMeasurementDataService, MeasurementDataService>();
|
||
|
||
// ── CNC / 矩阵 ViewModel(瞬态)──
|
||
containerRegistry.Register<CncEditorViewModel>();
|
||
containerRegistry.Register<MatrixEditorViewModel>();
|
||
containerRegistry.Register<MeasurementStatsViewModel>();
|
||
|
||
// ── CNC / 矩阵导航视图 ──
|
||
containerRegistry.RegisterForNavigation<CncPageView>();
|
||
containerRegistry.RegisterForNavigation<MatrixPageView>();
|
||
|
||
// ── 导航相机服务(单例)──
|
||
containerRegistry.RegisterSingleton<ICameraFactory, CameraFactory>();
|
||
containerRegistry.RegisterSingleton<ICameraController>(() =>
|
||
new CameraFactory().CreateController("Basler"));
|
||
containerRegistry.RegisterSingleton<ICameraService, CameraService>();
|
||
|
||
Log.Information("依赖注入容器配置完成");
|
||
}
|
||
|
||
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
|
||
{
|
||
// 所有模块服务已在 RegisterTypes 中手动注册
|
||
// CommonModule: ILocalizationService, IDumpService
|
||
// RaySourceModule: IRaySourceService, IRaySourceFactory, IFilamentLifetimeService
|
||
|
||
// 注册通用模块(必须最先加载)| Register common module (must be loaded first)
|
||
moduleCatalog.AddModule<CommonModule>();
|
||
|
||
// 注册其他模块 | Register other modules
|
||
moduleCatalog.AddModule<PLCModule>();
|
||
moduleCatalog.AddModule<DetectorModule>();
|
||
moduleCatalog.AddModule<RaySourceModule>();
|
||
moduleCatalog.AddModule<MotionControlModule>();
|
||
|
||
base.ConfigureModuleCatalog(moduleCatalog);
|
||
}
|
||
}
|
||
} |