diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9fe3cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +.vs/ProjectEvaluation/xploreplane.metadata.v10.bin +.vs/ProjectEvaluation/xploreplane.projects.v10.bin +.vs/ProjectEvaluation/xploreplane.strings.v10.bin +.vs/XplorePlane/DesignTimeBuild/.dtbcache.v2 +.vs/XplorePlane/FileContentIndex/241be4f9-f3c1-44c3-a625-51f3a7efa276.vsidx +.vs/XplorePlane/FileContentIndex/a28e3b89-b000-44c7-aab5-785c933af59b.vsidx +.vs/XplorePlane/FileContentIndex/a475b41e-8352-4745-8040-08886d83ddf4.vsidx +.vs/XplorePlane/FileContentIndex/bdb864e9-e54b-49df-bf87-9b121265e567.vsidx +.vs/XplorePlane/v18/.futdcache.v2 +.vs/XplorePlane/v18/.suo +.vs/XplorePlane/v18/DocumentLayout.backup.json +.vs/XplorePlane/v18/DocumentLayout.json +XplorePlane/obj/project.assets.json +XplorePlane/obj/* + +# 排除 Libs 目录中的 DLL 和 PDB 文件(但保留目录结构) +XplorePlane/Libs/Hardware/*.dll +XplorePlane/Libs/Hardware/*.pdb +XplorePlane/Libs/Native/*.dll +XplorePlane/Libs/Native/*.pdb + +# 保留 .gitkeep 文件以维持目录结构 +!XplorePlane/Libs/**/.gitkeep + +# 排除构建输出 +XplorePlane/bin/ +XplorePlane/obj/ + +# 排除日志文件 +logs/ +*.log \ No newline at end of file diff --git a/README.md b/README.md index 3de4e5d..f4fab06 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,109 @@ -#简介 -TODO: 简要介绍你的项目。通过此节说明此项目的目标或动机。 +## XplorePlane 平面CT软件 -#入门 -TODO: 指导用户在自己的系统上设置和运行代码。在本节中,可讨论: -1. 安装过程 -2. 软件依赖项 -3. 最新发布 -4. API 参考 +### 系统目标 -#生成与测试 -TODO: 说明并展示如何生成代码和运行测试。 +XplorePlane 系统用于控制平面 CT 设备的各个子系统(射线源、探测器、运动控制)并完成采集图像的处理与分析,为研发与调试提供统一的软件平台。 -#参与 -TODO: 说明其他用户和开发人员可如何帮助改善代码。 +**总体架构风格** -如需深入了解如何创建优秀的自述文件,请参阅以下[指南](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops)。还可从以下自述文件中寻求灵感: -- [ASP.NET Core](https://github.com/aspnet/Home) -- [Visual Studio Code](https://github.com/Microsoft/vscode) -- [Chakra Core](https://github.com/Microsoft/ChakraCore) \ No newline at end of file +- 客户端框架: WPF + Prism MVVM(项目 XplorePlane,目标框架 net8.0-windows) +- 图像处理内核: 独立类库 ImageProcessing.Core(算子基类与参数模型)和 ImageProcessing.Processors(具体算子实现),基于 EmguCV +- 基础设施: 日志使用 Serilog,序列化使用 Newtonsoft.Json,资源统一通过 WPF 资源系统管理 + +**开发目标** + +在现有图像处理与 UI 基础上,引入并集成: + +- 射线源子系统(X-Ray Source) +- 探测器子系统(Detector) +- 运动控制子系统(Motion Control) +- 通过统一的 CT 扫描工作流,在 UI 中实现一键式扫描、实时状态监控与图像后处理 + +### 项目框架 + +```css +XplorePlane/ +├── XplorePlane.csproj # .NET 8 WPF project file +│ +├── App.xaml # Application + global ResourceDictionary +├── App.xaml.cs +│ +├── Views/ +│ └── MainWindow.xaml # Main window (Grid + StackPanel layout) +│ └── MainWindow.xaml.cs # Code-behind (minimal – only TreeView event) +│ +├── ViewModels/ +│ └── MainViewModel.cs # Root VM: navigation, callouts, props, commands +│ └── NavGroupNode.cs # Tree group node VM +│ └── NavLeafNode.cs # Tree leaf node VM +│ └── InspectionCalloutVM.cs # Overlay callout card VM +│ └── CalloutRowVM.cs # Single callout data row VM +│ └── RelayCommand.cs # ICommand implementation +│ +├── Models/ +│ └── FeatureProperties.cs # Bindable domain model for right panel +│ +└── Assets/ + └── Icons/ # 28×28 toolbar icon PNGs + +``` + + +### XplorePlane.Hardware(硬件库) + + + + + + + +### XplorePlane.ImageProcessing (图像库) + + + + + + + + + +### 日志系统 + +项目已集成 Serilog 日志框架,提供统一的日志服务: + +- **日志框架**: Serilog 4.3.1 +- **日志输出**: 控制台、文件(按天滚动)、调试输出 +- **日志路径**: `logs/xploreplane-YYYYMMDD.log` +- **配置文件**: `App.config` +- **服务接口**: `ILoggerService` + +**使用示例**: + +```csharp +public class MyService +{ + private readonly ILoggerService _logger; + + public MyService(ILoggerService logger) + { + // 使用泛型自动推断模块名 + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + } + + public void DoSomething() + { + _logger.Info("执行操作"); + _logger.Debug("调试信息:参数={Value}", someValue); + } +} +``` + +详细使用指南请参考:`Doc/Logging.README.md` + +### TO-DO List + +- [x] 软件基于 WPF + Prism 基础的框架 +- [x] 日志库的引用 +- [ ] 界面的布局 +- [ ] 打通与硬件层的调用流程 +- [ ] 打通与图像层的调用流程 diff --git a/XplorePlane/App.config b/XplorePlane/App.config new file mode 100644 index 0000000..c4fcc58 --- /dev/null +++ b/XplorePlane/App.config @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XplorePlane/App.xaml.cs b/XplorePlane/App.xaml.cs index 38bec1d..033ef7b 100644 --- a/XplorePlane/App.xaml.cs +++ b/XplorePlane/App.xaml.cs @@ -1,8 +1,11 @@ -using System.Windows; +using System; +using System.Windows; using XplorePlane.Views; using XplorePlane.ViewModels; +using XplorePlane.Services; using Prism.Ioc; using Prism.DryIoc; +using Serilog; namespace XplorePlane { @@ -13,12 +16,58 @@ namespace XplorePlane { protected override void OnStartup(StartupEventArgs e) { + // 配置 Serilog 日志系统 + SerilogConfig.Configure(); + + // 捕获未处理的异常 + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + DispatcherUnhandledException += OnDispatcherUnhandledException; + base.OnStartup(e); // Initialize Prism with DryIoc var bootstrapper = new AppBootstrapper(); bootstrapper.Run(); } + + protected override void OnExit(ExitEventArgs e) + { + // 关闭并刷新日志 + SerilogConfig.CloseAndFlush(); + base.OnExit(e); + } + + /// + /// 处理未捕获的异常 + /// + 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); + } + + /// + /// 处理 UI 线程未捕获的异常 + /// + 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 @@ -30,8 +79,14 @@ namespace XplorePlane protected override void RegisterTypes(IContainerRegistry containerRegistry) { + // 注册日志服务 + containerRegistry.RegisterSingleton(); + + // 注册视图和视图模型 containerRegistry.RegisterForNavigation(); containerRegistry.Register(); + + Log.Information("依赖注入容器配置完成"); } } } diff --git a/XplorePlane/Libs/Native/.gitkeep b/XplorePlane/Libs/Native/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/XplorePlane/MainWindow.xaml b/XplorePlane/MainWindow.xaml index 65907f3..4412790 100644 --- a/XplorePlane/MainWindow.xaml +++ b/XplorePlane/MainWindow.xaml @@ -1,10 +1,9 @@ - /// Interaction logic for MainWindow.xaml diff --git a/XplorePlane/README.md b/XplorePlane/README.md deleted file mode 100644 index 1acffe9..0000000 --- a/XplorePlane/README.md +++ /dev/null @@ -1,65 +0,0 @@ -## XplorePlane 平面CT软件 - - - -### 系统目标 -XplorePlane 系统用于控制平面 CT 设备的各个子系统(射线源、探测器、运动控制)并完成采集图像的处理与分析,为研发与调试提供统一的软件平台。 -总体架构风格 -- 客户端框架: WPF + Prism MVVM(项目 XplorePlane,目标框架 net8.0-windows)。 -- 图像处理内核: 独立类库 ImageProcessing.Core(算子基类与参数模型)和 ImageProcessing.Processors(具体算子实现),基于 EmguCV。 -- 基础设施: 日志使用 Serilog,序列化使用 Newtonsoft.Json,资源统一通过 WPF 资源系统管理。 - -开发目标 -- 在现有图像处理与 UI 基础上,引入并集成: -- 射线源子系统(X-Ray Source) -- 探测器子系统(Detector) - - 运动控制子系统(Motion Control) - - 通过统一的 CT 扫描工作流,在 UI 中实现一键式扫描、实时状态监控与图像后处理。 - -### 项目框架 - - -```css -XplorePlane/ -├── XplorePlane.csproj # .NET 8 WPF project file -│ -├── App.xaml # Application + global ResourceDictionary -├── App.xaml.cs -│ -├── Views/ -│ └── MainWindow.xaml # Main window (Grid + StackPanel layout) -│ └── MainWindow.xaml.cs # Code-behind (minimal – only TreeView event) -│ -├── ViewModels/ -│ └── MainViewModel.cs # Root VM: navigation, callouts, props, commands -│ └── NavGroupNode.cs # Tree group node VM -│ └── NavLeafNode.cs # Tree leaf node VM -│ └── InspectionCalloutVM.cs # Overlay callout card VM -│ └── CalloutRowVM.cs # Single callout data row VM -│ └── RelayCommand.cs # ICommand implementation -│ -├── Models/ -│ └── FeatureProperties.cs # Bindable domain model for right panel -│ -└── Assets/ - └── Icons/ # 28×28 toolbar icon PNGs - -``` - - -### XplorePlane.Hardware(硬件库) - - - -### XplorePlane.ImageProcessing (图像库) - - - - - - - -### TO-DO List - -[] 软件基于WPF + Prism 基础的框架, 主页面设计 -[] 打通与硬件 和图像的调用流程 diff --git a/XplorePlane/Services/ILoggerService.cs b/XplorePlane/Services/ILoggerService.cs new file mode 100644 index 0000000..4ed0693 --- /dev/null +++ b/XplorePlane/Services/ILoggerService.cs @@ -0,0 +1,90 @@ +using System; + +namespace XplorePlane.Services +{ + /// + /// 日志服务接口 + /// + public interface ILoggerService + { + /// + /// 为指定模块创建日志器(使用泛型自动推断类型名) + /// + ILoggerService ForModule(); + + /// + /// 为指定模块创建日志器(手动指定模块名) + /// + ILoggerService ForModule(string moduleName); + + /// + /// 记录调试信息 + /// + void Debug(string message); + + /// + /// 记录调试信息(带参数) + /// + void Debug(string messageTemplate, params object[] propertyValues); + + /// + /// 记录一般信息 + /// + void Info(string message); + + /// + /// 记录一般信息(带参数) + /// + void Info(string messageTemplate, params object[] propertyValues); + + /// + /// 记录警告信息 + /// + void Warn(string message); + + /// + /// 记录警告信息(带参数) + /// + void Warn(string messageTemplate, params object[] propertyValues); + + /// + /// 记录错误信息 + /// + void Error(string message); + + /// + /// 记录错误信息(带参数) + /// + void Error(string messageTemplate, params object[] propertyValues); + + /// + /// 记录错误信息(带异常) + /// + void Error(Exception exception, string message); + + /// + /// 记录错误信息(带异常和参数) + /// + void Error(Exception exception, string messageTemplate, params object[] propertyValues); + + /// + /// 记录致命错误 + /// + void Fatal(string message); + + /// + /// 记录致命错误(带参数) + /// + void Fatal(string messageTemplate, params object[] propertyValues); + + /// + /// 记录致命错误(带异常) + /// + void Fatal(Exception exception, string message); + + /// + /// 记录致命错误(带异常和参数) + /// + void Fatal(Exception exception, string messageTemplate, params object[] propertyValues); + } +} diff --git a/XplorePlane/Services/LoggerService.cs b/XplorePlane/Services/LoggerService.cs new file mode 100644 index 0000000..f2c7779 --- /dev/null +++ b/XplorePlane/Services/LoggerService.cs @@ -0,0 +1,150 @@ +using System; +using Serilog; +using Serilog.Core; + +namespace XplorePlane.Services +{ + /// + /// 日志服务实现 + /// + public class LoggerService : ILoggerService + { + private readonly ILogger _logger; + private readonly string _moduleName; + + /// + /// 构造函数 + /// + public LoggerService() : this(Log.Logger, null) + { + } + + /// + /// 构造函数(指定 Serilog Logger) + /// + public LoggerService(ILogger logger) : this(logger, null) + { + } + + /// + /// 私有构造函数(用于创建带模块名的实例) + /// + private LoggerService(ILogger logger, string moduleName) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _moduleName = moduleName; + } + + /// + /// 为指定模块创建日志器(使用泛型自动推断类型名) + /// + public ILoggerService ForModule() + { + var typeName = typeof(T).FullName ?? typeof(T).Name; + return new LoggerService(_logger, typeName); + } + + /// + /// 为指定模块创建日志器(手动指定模块名) + /// + public ILoggerService ForModule(string moduleName) + { + if (string.IsNullOrWhiteSpace(moduleName)) + throw new ArgumentException("模块名不能为空", nameof(moduleName)); + + return new LoggerService(_logger, moduleName); + } + + /// + /// 获取带模块名的日志器 + /// + private ILogger GetLogger() + { + if (string.IsNullOrEmpty(_moduleName)) + return _logger; + + return _logger.ForContext("Module", _moduleName); + } + + public void Debug(string message) + { + GetLogger().Debug(FormatMessage(message)); + } + + public void Debug(string messageTemplate, params object[] propertyValues) + { + GetLogger().Debug(FormatMessage(messageTemplate), propertyValues); + } + + public void Info(string message) + { + GetLogger().Information(FormatMessage(message)); + } + + public void Info(string messageTemplate, params object[] propertyValues) + { + GetLogger().Information(FormatMessage(messageTemplate), propertyValues); + } + + public void Warn(string message) + { + GetLogger().Warning(FormatMessage(message)); + } + + public void Warn(string messageTemplate, params object[] propertyValues) + { + GetLogger().Warning(FormatMessage(messageTemplate), propertyValues); + } + + public void Error(string message) + { + GetLogger().Error(FormatMessage(message)); + } + + public void Error(string messageTemplate, params object[] propertyValues) + { + GetLogger().Error(FormatMessage(messageTemplate), propertyValues); + } + + public void Error(Exception exception, string message) + { + GetLogger().Error(exception, FormatMessage(message)); + } + + public void Error(Exception exception, string messageTemplate, params object[] propertyValues) + { + GetLogger().Error(exception, FormatMessage(messageTemplate), propertyValues); + } + + public void Fatal(string message) + { + GetLogger().Fatal(FormatMessage(message)); + } + + public void Fatal(string messageTemplate, params object[] propertyValues) + { + GetLogger().Fatal(FormatMessage(messageTemplate), propertyValues); + } + + public void Fatal(Exception exception, string message) + { + GetLogger().Fatal(exception, FormatMessage(message)); + } + + public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues) + { + GetLogger().Fatal(exception, FormatMessage(messageTemplate), propertyValues); + } + + /// + /// 格式化消息(添加模块名前缀) + /// + private string FormatMessage(string message) + { + if (string.IsNullOrEmpty(_moduleName)) + return message; + + return $"[{_moduleName}] {message}"; + } + } +} diff --git a/XplorePlane/Services/LoggingExample.cs b/XplorePlane/Services/LoggingExample.cs new file mode 100644 index 0000000..9a72d3f --- /dev/null +++ b/XplorePlane/Services/LoggingExample.cs @@ -0,0 +1,139 @@ +using System; +using System.Threading.Tasks; + +namespace XplorePlane.Services +{ + /// + /// 日志服务使用示例 + /// + public class LoggingExample + { + private readonly ILoggerService _logger; + + public LoggingExample(ILoggerService logger) + { + // 推荐方式:使用泛型自动推断类型名 + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// 基本日志示例 + /// + public void BasicLoggingExample() + { + // 调试信息 + _logger.Debug("这是调试信息"); + + // 一般信息 + _logger.Info("应用程序启动成功"); + + // 警告信息 + _logger.Warn("连接不稳定,正在重试..."); + + // 错误信息 + _logger.Error("操作失败"); + + // 致命错误 + _logger.Fatal("系统崩溃"); + } + + /// + /// 结构化日志示例 + /// + public void StructuredLoggingExample() + { + var userId = 12345; + var action = "登录"; + var voltage = 150.5f; + var current = 500; + + // 使用占位符(推荐) + _logger.Info("用户 {UserId} 执行了操作 {Action}", userId, action); + _logger.Info("设置电压为 {Voltage} kV,电流为 {Current} μA", voltage, current); + + // 不推荐:字符串拼接 + // _logger.Info($"用户 {userId} 执行了操作 {action}"); + } + + /// + /// 异常日志示例 + /// + public async Task ExceptionLoggingExample() + { + try + { + // 模拟操作 + await Task.Delay(100); + throw new InvalidOperationException("模拟异常"); + } + catch (Exception ex) + { + // 记录异常(带上下文信息) + _logger.Error(ex, "操作失败:参数={Parameter}", "test"); + + // 或者简单记录 + _logger.Error(ex, "操作失败"); + + // 致命错误 + _logger.Fatal(ex, "系统发生致命错误"); + } + } + + /// + /// 不同模块名示例 + /// + public void DifferentModuleNameExample(ILoggerService logger) + { + // 方式 1:使用泛型(推荐) + var logger1 = logger.ForModule(); + logger1.Info("使用泛型推断的模块名"); + // 输出: [XplorePlane.Services.LoggingExample] 使用泛型推断的模块名 + + // 方式 2:手动指定模块名 + var logger2 = logger.ForModule("CustomModule"); + logger2.Info("使用自定义模块名"); + // 输出: [CustomModule] 使用自定义模块名 + + // 方式 3:不指定模块名 + logger.Info("没有模块名"); + // 输出: 没有模块名 + } + + /// + /// 实际业务场景示例 + /// + public async Task BusinessScenarioExample() + { + _logger.Info("开始初始化硬件..."); + + try + { + // 步骤 1 + _logger.Debug("步骤 1: 检查硬件连接"); + await Task.Delay(100); + + // 步骤 2 + _logger.Debug("步骤 2: 加载配置"); + var config = LoadConfig(); + _logger.Info("配置加载成功:{ConfigName}", config); + + // 步骤 3 + _logger.Debug("步骤 3: 建立连接"); + await Task.Delay(100); + + _logger.Info("硬件初始化成功"); + return true; + } + catch (Exception ex) + { + _logger.Error(ex, "硬件初始化失败"); + return false; + } + } + + private string LoadConfig() + { + return "DefaultConfig"; + } + } +} diff --git a/XplorePlane/Services/LoggingVerification.cs b/XplorePlane/Services/LoggingVerification.cs new file mode 100644 index 0000000..267363e --- /dev/null +++ b/XplorePlane/Services/LoggingVerification.cs @@ -0,0 +1,94 @@ +using System; +using System.Threading.Tasks; + +namespace XplorePlane.Services +{ + /// + /// 日志系统验证类 - 用于测试日志功能是否正常工作 + /// + public class LoggingVerification + { + private readonly ILoggerService _logger; + + public LoggingVerification(ILoggerService logger) + { + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// 运行完整的日志验证测试 + /// + public async Task RunVerificationAsync() + { + _logger.Info("========================================"); + _logger.Info("开始日志系统验证测试"); + _logger.Info("========================================"); + + // 测试 1: 基本日志级别 + TestBasicLogLevels(); + + // 测试 2: 结构化日志 + TestStructuredLogging(); + + // 测试 3: 异常日志 + await TestExceptionLoggingAsync(); + + // 测试 4: 模块名 + TestModuleNames(); + + _logger.Info("========================================"); + _logger.Info("日志系统验证测试完成"); + _logger.Info("========================================"); + } + + private void TestBasicLogLevels() + { + _logger.Info("测试 1: 基本日志级别"); + _logger.Debug("这是 Debug 级别日志"); + _logger.Info("这是 Info 级别日志"); + _logger.Warn("这是 Warn 级别日志"); + _logger.Error("这是 Error 级别日志"); + _logger.Info("✓ 基本日志级别测试完成"); + } + + private void TestStructuredLogging() + { + _logger.Info("测试 2: 结构化日志"); + + var userId = 12345; + var userName = "张三"; + var voltage = 150.5f; + var current = 500; + var timestamp = DateTime.Now; + + _logger.Info("用户登录: UserId={UserId}, UserName={UserName}", userId, userName); + _logger.Info("设置参数: Voltage={Voltage}kV, Current={Current}μA", voltage, current); + _logger.Info("操作时间: {Timestamp:yyyy-MM-dd HH:mm:ss}", timestamp); + + _logger.Info("✓ 结构化日志测试完成"); + } + + private async Task TestExceptionLoggingAsync() + { + _logger.Info("测试 3: 异常日志"); + + try + { + await Task.Delay(10); + throw new InvalidOperationException("这是一个测试异常"); + } + catch (Exception ex) + { + _logger.Error(ex, "捕获到异常: {Message}", ex.Message); + _logger.Info("✓ 异常日志测试完成"); + } + } + + private void TestModuleNames() + { + _logger.Info("测试 4: 模块名功能"); + _logger.Info("当前模块名应该是: XplorePlane.Services.LoggingVerification"); + _logger.Info("✓ 模块名测试完成"); + } + } +} diff --git a/XplorePlane/Services/README.md b/XplorePlane/Services/README.md new file mode 100644 index 0000000..314912e --- /dev/null +++ b/XplorePlane/Services/README.md @@ -0,0 +1,75 @@ +# Services 目录说明 + +此目录用于存放应用程序的服务层代码,负责封装硬件库的调用和业务逻辑。 + +## 目录结构 + +``` +Services/ +├── IHardwareService.cs # 硬件服务接口 +├── HardwareService.cs # 硬件服务实现 +├── IConfigurationService.cs # 配置服务接口 +└── ConfigurationService.cs # 配置服务实现 +``` + +## 服务说明 + +### 1. HardwareService + +统一管理所有硬件模块的服务适配器。 + +**功能**: +- 初始化和关闭所有硬件 +- 射线源控制(开关、参数设置) +- 探测器控制(图像采集、配置) +- PLC 通讯(变量读写) + +**使用示例**: +```csharp +// 在 ViewModel 中注入 +public class MainViewModel +{ + private readonly IHardwareService _hardwareService; + + public MainViewModel(IHardwareService hardwareService) + { + _hardwareService = hardwareService; + } + + public async Task InitializeAsync() + { + await _hardwareService.InitializeAllAsync(); + } +} +``` + +### 2. ConfigurationService + +管理应用程序配置的服务。 + +**功能**: +- 读取 App.config 配置 +- 类型安全的配置访问 +- 默认值支持 + +## 依赖注入配置 + +在 `App.xaml.cs` 中注册服务: + +```csharp +protected override void RegisterTypes(IContainerRegistry containerRegistry) +{ + // 注册硬件服务 + containerRegistry.RegisterSingleton(); + + // 注册配置服务 + containerRegistry.RegisterSingleton(); +} +``` + +## 注意事项 + +1. **服务生命周期**:硬件服务应注册为 Singleton,确保全局唯一 +2. **异步操作**:所有硬件操作都应使用异步方法 +3. **错误处理**:服务层应捕获并记录所有异常 +4. **资源释放**:实现 IDisposable 接口,确保资源正确释放 diff --git a/XplorePlane/Services/SerilogConfig.cs b/XplorePlane/Services/SerilogConfig.cs new file mode 100644 index 0000000..68399b2 --- /dev/null +++ b/XplorePlane/Services/SerilogConfig.cs @@ -0,0 +1,133 @@ +using System; +using System.Configuration; +using System.IO; +using Serilog; +using Serilog.Events; + +namespace XplorePlane.Services +{ + /// + /// Serilog 配置类 + /// + public static class SerilogConfig + { + /// + /// 配置 Serilog + /// + public static void Configure() + { + // 读取配置 + var logPath = GetConfigValue("Serilog:LogPath", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs")); + var minimumLevel = GetLogLevel(GetConfigValue("Serilog:MinimumLevel", "Information")); + var enableConsole = GetConfigValue("Serilog:EnableConsole", "true").ToLower() == "true"; + var rollingInterval = GetRollingInterval(GetConfigValue("Serilog:RollingInterval", "Day")); + var fileSizeLimitMB = int.Parse(GetConfigValue("Serilog:FileSizeLimitMB", "100")); + var retainedFileCountLimit = int.Parse(GetConfigValue("Serilog:RetainedFileCountLimit", "30")); + + // 确保日志目录存在 + if (!Directory.Exists(logPath)) + { + Directory.CreateDirectory(logPath); + } + + // 配置 Serilog + var loggerConfig = new LoggerConfiguration() + .MinimumLevel.Is(minimumLevel) + .Enrich.FromLogContext() + .Enrich.WithProperty("Application", "XplorePlane") + .Enrich.WithProperty("MachineName", Environment.MachineName) + .WriteTo.File( + path: Path.Combine(logPath, "xploreplane-.log"), + rollingInterval: rollingInterval, + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}", + fileSizeLimitBytes: fileSizeLimitMB * 1024 * 1024, + retainedFileCountLimit: retainedFileCountLimit, + shared: true + ); + + // 添加控制台输出 + if (enableConsole) + { + loggerConfig.WriteTo.Console( + outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}" + ); + } + + // 添加调试输出(仅在 Debug 模式) +#if DEBUG + loggerConfig.WriteTo.Debug( + outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}" + ); +#endif + + // 创建全局 Logger + Log.Logger = loggerConfig.CreateLogger(); + + Log.Information("========================================"); + Log.Information("XplorePlane 应用程序启动"); + Log.Information("日志路径: {LogPath}", logPath); + Log.Information("日志级别: {MinimumLevel}", minimumLevel); + Log.Information("========================================"); + } + + /// + /// 关闭并刷新日志 + /// + public static void CloseAndFlush() + { + Log.Information("========================================"); + Log.Information("XplorePlane 应用程序退出"); + Log.Information("========================================"); + Log.CloseAndFlush(); + } + + /// + /// 获取配置值 + /// + private static string GetConfigValue(string key, string defaultValue) + { + try + { + return ConfigurationManager.AppSettings[key] ?? defaultValue; + } + catch + { + return defaultValue; + } + } + + /// + /// 获取日志级别 + /// + private static LogEventLevel GetLogLevel(string level) + { + return level.ToLower() switch + { + "verbose" => LogEventLevel.Verbose, + "debug" => LogEventLevel.Debug, + "information" => LogEventLevel.Information, + "warning" => LogEventLevel.Warning, + "error" => LogEventLevel.Error, + "fatal" => LogEventLevel.Fatal, + _ => LogEventLevel.Information + }; + } + + /// + /// 获取滚动间隔 + /// + private static RollingInterval GetRollingInterval(string interval) + { + return interval.ToLower() switch + { + "infinite" => RollingInterval.Infinite, + "year" => RollingInterval.Year, + "month" => RollingInterval.Month, + "day" => RollingInterval.Day, + "hour" => RollingInterval.Hour, + "minute" => RollingInterval.Minute, + _ => RollingInterval.Day + }; + } + } +} diff --git a/XplorePlane/ViewModels/MainViewModel.cs b/XplorePlane/ViewModels/MainViewModel.cs index d0632a4..2c3f165 100644 --- a/XplorePlane/ViewModels/MainViewModel.cs +++ b/XplorePlane/ViewModels/MainViewModel.cs @@ -1,13 +1,20 @@ using Prism.Commands; using Prism.Mvvm; using System.Collections.ObjectModel; +using XplorePlane.Services; namespace XplorePlane.ViewModels { public class MainViewModel : BindableBase { - private string _licenseInfo = "ǰʱ"; - + private readonly ILoggerService _logger; + private string _licenseInfo = "当前时间"; + + public string LicenseInfo + { + get => _licenseInfo; + set => SetProperty(ref _licenseInfo, value); + } public ObservableCollection NavigationTree { get; set; } @@ -18,16 +25,56 @@ namespace XplorePlane.ViewModels public DelegateCommand ClearCommand { get; set; } public DelegateCommand EditPropertiesCommand { get; set; } - public MainViewModel() + public MainViewModel(ILoggerService logger) { + _logger = logger?.ForModule() ?? throw new System.ArgumentNullException(nameof(logger)); + NavigationTree = new ObservableCollection(); - NavigateHomeCommand = new DelegateCommand(() => { }); - NavigateInspectCommand = new DelegateCommand(() => { }); - OpenFileCommand = new DelegateCommand(() => { }); - ExportCommand = new DelegateCommand(() => { }); - ClearCommand = new DelegateCommand(() => { }); - EditPropertiesCommand = new DelegateCommand(() => { }); + NavigateHomeCommand = new DelegateCommand(OnNavigateHome); + NavigateInspectCommand = new DelegateCommand(OnNavigateInspect); + OpenFileCommand = new DelegateCommand(OnOpenFile); + ExportCommand = new DelegateCommand(OnExport); + ClearCommand = new DelegateCommand(OnClear); + EditPropertiesCommand = new DelegateCommand(OnEditProperties); + + _logger.Info("MainViewModel 已初始化"); + } + + private void OnNavigateHome() + { + _logger.Info("导航到主页"); + LicenseInfo = "主页"; + } + + private void OnNavigateInspect() + { + _logger.Info("导航到检测页面"); + LicenseInfo = "检测页面"; + } + + private void OnOpenFile() + { + _logger.Info("打开文件"); + LicenseInfo = "打开文件"; + } + + private void OnExport() + { + _logger.Info("导出数据"); + LicenseInfo = "导出数据"; + } + + private void OnClear() + { + _logger.Info("清除数据"); + LicenseInfo = "清除数据"; + } + + private void OnEditProperties() + { + _logger.Info("编辑属性"); + LicenseInfo = "编辑属性"; } } } diff --git a/XplorePlane/Views/MainWindow.xaml b/XplorePlane/Views/MainWindow.xaml index ee7cf48..8efb723 100644 --- a/XplorePlane/Views/MainWindow.xaml +++ b/XplorePlane/Views/MainWindow.xaml @@ -11,13 +11,10 @@ Background="#F5F5F5" WindowStartupLocation="CenterScreen"> - - - - - + + @@ -26,7 +23,7 @@ - + @@ -46,7 +43,7 @@ - + @@ -76,7 +73,7 @@ - + @@ -120,7 +117,7 @@ - + diff --git a/XplorePlane/Views/MainWindow.xaml.cs b/XplorePlane/Views/MainWindow.xaml.cs index e678fae..61ef10b 100644 --- a/XplorePlane/Views/MainWindow.xaml.cs +++ b/XplorePlane/Views/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using System.Windows; +using XplorePlane.ViewModels; namespace XplorePlane.Views { @@ -7,9 +8,10 @@ namespace XplorePlane.Views /// public partial class MainWindow : Window { - public MainWindow() + public MainWindow(MainViewModel viewModel) { InitializeComponent(); + DataContext = viewModel; } } } diff --git a/XplorePlane/XplorePlane.csproj b/XplorePlane/XplorePlane.csproj index 91d9b5f..38a6325 100644 --- a/XplorePlane/XplorePlane.csproj +++ b/XplorePlane/XplorePlane.csproj @@ -6,8 +6,33 @@ XplorePlane XplorePlane + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + \ No newline at end of file