Files
XplorePlane/XplorePlane/App.xaml.cs
T

422 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}