diff --git a/XplorePlane/App.xaml.cs b/XplorePlane/App.xaml.cs index 53b18b7..edc49f9 100644 --- a/XplorePlane/App.xaml.cs +++ b/XplorePlane/App.xaml.cs @@ -150,6 +150,22 @@ namespace XplorePlane Log.Error(ex, "射线源资源释放失败"); } + // 先停止导航相机实时采集,再释放资源,避免回调死锁 + try + { + var bootstrapper = AppBootstrapper.Instance; + if (bootstrapper != null) + { + var cameraVm = bootstrapper.Container.Resolve(); + cameraVm?.Dispose(); + Log.Information("导航相机 ViewModel 已释放"); + } + } + catch (Exception ex) + { + Log.Error(ex, "导航相机 ViewModel 释放失败"); + } + // 释放导航相机服务资源 try { @@ -232,6 +248,8 @@ namespace XplorePlane private bool _modulesInitialized = false; + private string? _cameraError; + protected override Window CreateShell() { // 提前初始化模块,确保硬件服务在 MainWindow XAML 解析前已注册 @@ -241,46 +259,55 @@ namespace XplorePlane _modulesInitialized = true; } - // 在主窗口显示前完成导航相机连接,避免窗口弹出后再检索导致卡顿 - TryConnectCamera(); + var shell = Container.Resolve(); - return Container.Resolve(); + // 主窗口加载完成后再连接相机,确保所有模块和原生 DLL 已完成初始化 + shell.Loaded += (s, e) => + { + TryConnectCamera(); + + // 通知 ViewModel 相机状态已确定,启动实时预览或显示错误 + try + { + var cameraVm = Container.Resolve(); + cameraVm.OnCameraReady(); + } + catch (Exception ex) + { + Log.Error(ex, "通知相机 ViewModel 失败"); + } + + if (_cameraError != null) + { + HexMessageBox.Show(_cameraError, MessageBoxButton.OK, MessageBoxImage.Error); + } + }; + + return shell; } /// - /// 在独立 MTA 线程中检索并连接导航相机,带 10 秒超时。 - /// 连接成功后 NavigationPropertyPanelViewModel 构造时拿到的就是已连接的导航相机。 + /// 在主线程上检索并连接导航相机。 + /// pylon SDK 要求在主线程(STA)上操作,不能放到后台线程。 /// private void TryConnectCamera() { var camera = Container.Resolve(); - CameraInfo? info = null; - Exception? error = null; - var thread = new System.Threading.Thread(() => + try { - try { info = camera.Open(); } - catch (Exception ex) { error = ex; } - }); - thread.IsBackground = true; - thread.SetApartmentState(System.Threading.ApartmentState.MTA); - thread.Start(); - - bool completed = thread.Join(TimeSpan.FromSeconds(10)); - - if (!completed) - { - Log.Warning("导航相机检索超时 (10s),跳过自动连接"); - HexMessageBox.Show("导航相机检索超时(10秒),请检查导航相机连接后重启软件。", MessageBoxButton.OK, MessageBoxImage.Error); + var info = camera.Open(); + Log.Information("导航相机已连接: {ModelName} (SN: {SerialNumber})", info.ModelName, info.SerialNumber); } - else if (error != null) + catch (DeviceNotFoundException) { - Log.Warning(error, "导航相机自动连接失败: {Message}", error.Message); - HexMessageBox.Show($"导航相机连接失败: {error.Message}", MessageBoxButton.OK, MessageBoxImage.Error); + Log.Warning("未检测到导航相机"); + _cameraError = "未检测到导航相机,请检查连接后重启软件。"; } - else + catch (Exception ex) { - Log.Information("导航相机已连接: {ModelName} (SN: {SerialNumber})", info!.ModelName, info.SerialNumber); + Log.Warning(ex, "导航相机自动连接失败: {Message}", ex.Message); + _cameraError = $"导航相机连接失败: {ex.Message}"; } } diff --git a/XplorePlane/ViewModels/Main/NavigationPropertyPanelViewModel.cs b/XplorePlane/ViewModels/Main/NavigationPropertyPanelViewModel.cs index 62c1483..6253851 100644 --- a/XplorePlane/ViewModels/Main/NavigationPropertyPanelViewModel.cs +++ b/XplorePlane/ViewModels/Main/NavigationPropertyPanelViewModel.cs @@ -177,23 +177,29 @@ namespace XplorePlane.ViewModels RefreshCameraParamsCommand = new DelegateCommand(RefreshCameraParams, () => IsCameraConnected); OpenCameraSettingsCommand = new DelegateCommand(OpenCameraSettings, () => IsCameraConnected); - // 相机已在 App 启动阶段连接,直接检测状态并启动实时预览 - if (_camera.IsConnected) - { - _camera.ImageGrabbed += OnCameraImageGrabbed; - _camera.GrabError += OnCameraGrabError; - _camera.ConnectionLost += OnCameraConnectionLost; + CameraStatusText = "正在检索相机..."; + } - IsCameraConnected = true; - CameraStatusText = "已连接"; - RefreshCameraParams(); - StartGrab(); - IsLiveViewEnabled = true; - } - else + /// + /// 相机连接完成后由外部调用,启动实时预览。 + /// + public void OnCameraReady() + { + if (!_camera.IsConnected) { CameraStatusText = "未检测到相机"; + return; } + + _camera.ImageGrabbed += OnCameraImageGrabbed; + _camera.GrabError += OnCameraGrabError; + _camera.ConnectionLost += OnCameraConnectionLost; + + IsCameraConnected = true; + CameraStatusText = "已连接"; + RefreshCameraParams(); + StartGrab(); + IsLiveViewEnabled = true; } #region Camera Methods @@ -344,15 +350,18 @@ namespace XplorePlane.ViewModels private void OnCameraImageGrabbed(object? sender, ImageGrabbedEventArgs e) { + if (_disposed) return; + try { var bitmap = PixelConverter.ToBitmapSource(e.PixelData, e.Width, e.Height, e.PixelFormat); var app = Application.Current; if (app == null) return; - app.Dispatcher.Invoke(() => + app.Dispatcher.BeginInvoke(() => { - CameraImageSource = bitmap; + if (!_disposed) + CameraImageSource = bitmap; }); if (_liveViewRunning) @@ -362,7 +371,8 @@ namespace XplorePlane.ViewModels } catch (Exception ex) { - _logger.Error(ex, "Failed to process camera image"); + if (!_disposed) + _logger.Error(ex, "Failed to process camera image"); } } @@ -372,9 +382,10 @@ namespace XplorePlane.ViewModels var app = Application.Current; if (app == null) return; - app.Dispatcher.Invoke(() => + app.Dispatcher.BeginInvoke(() => { - CameraStatusText = $"采集错误: {e.ErrorDescription}"; + if (!_disposed) + CameraStatusText = $"采集错误: {e.ErrorDescription}"; }); } @@ -384,8 +395,9 @@ namespace XplorePlane.ViewModels var app = Application.Current; if (app == null) return; - app.Dispatcher.Invoke(() => + app.Dispatcher.BeginInvoke(() => { + if (_disposed) return; IsCameraConnected = false; IsCameraGrabbing = false; CameraStatusText = "连接已断开"; @@ -400,12 +412,19 @@ namespace XplorePlane.ViewModels public void Dispose() { if (_disposed) return; + _disposed = true; _liveViewRunning = false; - try { _camera.Dispose(); } - catch (Exception ex) { _logger.Error(ex, "Error disposing camera"); } + // 先取消事件订阅,防止回调继续触发 + _camera.ImageGrabbed -= OnCameraImageGrabbed; + _camera.GrabError -= OnCameraGrabError; + _camera.ConnectionLost -= OnCameraConnectionLost; - _disposed = true; + // 停止采集后再关闭连接 + try { if (_camera.IsGrabbing) _camera.StopGrabbing(); } catch { } + try { _camera.Close(); } catch { } + + _logger.Information("NavigationPropertyPanelViewModel disposed"); } #endregion IDisposable