using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using XP.Common.Logging.Interfaces;
using XP.Hardware.Detector.Abstractions;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Abstractions.Events;
using XP.Hardware.Detector.Services;
namespace XP.Hardware.Detector.ViewModels
{
///
/// 探测器操作 ViewModel | Detector operation ViewModel
/// 负责连接/断开、连续采集、单帧采集、图像显示及状态跟踪
///
public class DetectorImageWindowViewModel : BindableBase
{
private readonly IDetectorService _detectorService;
private readonly IEventAggregator _eventAggregator;
private readonly IImageService _imageSaveService;
private readonly ILoggerService _logger;
///
/// 帧处理节流标志,防止后台线程堆积 | Frame processing throttle flag to prevent thread pile-up
/// 0 = 空闲,1 = 正在处理
///
private int _isProcessingFrame;
#region 连接状态 | Connection status
private bool _isConnected;
///
/// 探测器是否已连接 | Whether detector is connected
///
public bool IsConnected
{
get => _isConnected;
private set
{
if (SetProperty(ref _isConnected, value))
{
RaiseAllCommandsCanExecuteChanged();
RaisePropertyChanged(nameof(ConnectionStatusText));
RaisePropertyChanged(nameof(ConnectionStatusColor));
}
}
}
///
/// 连接状态文本 | Connection status text
///
public string ConnectionStatusText => _isConnected ? "已连接 | Connected" : "未连接 | Disconnected";
///
/// 连接状态指示颜色 | Connection status indicator color
///
public Brush ConnectionStatusColor => _isConnected
? new SolidColorBrush(Color.FromRgb(0x4C, 0xAF, 0x50)) // 绿色 | Green
: new SolidColorBrush(Color.FromRgb(0xF4, 0x43, 0x36)); // 红色 | Red
#endregion
#region 探测器状态 | Detector status
private DetectorStatus _detectorStatus = DetectorStatus.Uninitialized;
///
/// 当前探测器状态 | Current detector status
///
public DetectorStatus DetectorStatus
{
get => _detectorStatus;
private set
{
if (SetProperty(ref _detectorStatus, value))
{
RaisePropertyChanged(nameof(DetectorStatusText));
RaiseAllCommandsCanExecuteChanged();
}
}
}
///
/// 探测器状态文本 | Detector status text
///
public string DetectorStatusText => _detectorStatus switch
{
Abstractions.Enums.DetectorStatus.Uninitialized => "未初始化 | Uninitialized",
Abstractions.Enums.DetectorStatus.Initializing => "初始化中 | Initializing",
Abstractions.Enums.DetectorStatus.Ready => "就绪 | Ready",
Abstractions.Enums.DetectorStatus.Acquiring => "采集中 | Acquiring",
Abstractions.Enums.DetectorStatus.Correcting => "校正中 | Correcting",
Abstractions.Enums.DetectorStatus.Error => "错误 | Error",
_ => "未知 | Unknown"
};
#endregion
#region 探测器信息 | Detector info
private string _detectorModel = "--";
///
/// 探测器型号 | Detector model
///
public string DetectorModel
{
get => _detectorModel;
private set => SetProperty(ref _detectorModel, value);
}
private string _detectorResolution = "--";
///
/// 探测器分辨率 | Detector resolution
///
public string DetectorResolution
{
get => _detectorResolution;
private set => SetProperty(ref _detectorResolution, value);
}
private string _detectorPixelSize = "--";
///
/// 探测器像素尺寸 | Detector pixel size
///
public string DetectorPixelSize
{
get => _detectorPixelSize;
private set => SetProperty(ref _detectorPixelSize, value);
}
private string _detectorType = "--";
///
/// 探测器类型 | Detector type
///
public string DetectorTypeText
{
get => _detectorType;
private set => SetProperty(ref _detectorType, value);
}
#endregion
#region 图像属性 | Image properties
private BitmapSource _imageSource;
///
/// 当前显示的图像 | Currently displayed image
///
public BitmapSource ImageSource
{
get => _imageSource;
private set => SetProperty(ref _imageSource, value);
}
private int _frameNumber;
///
/// 当前帧号 | Current frame number
///
public int FrameNumber
{
get => _frameNumber;
private set => SetProperty(ref _frameNumber, value);
}
private string _imageInfo = "等待采集... | Waiting for acquisition...";
///
/// 图像信息文本 | Image info text
///
public string ImageInfo
{
get => _imageInfo;
private set => SetProperty(ref _imageInfo, value);
}
private bool _isAcquiring;
///
/// 是否正在连续采集 | Whether continuous acquisition is in progress
///
public bool IsAcquiring
{
get => _isAcquiring;
private set
{
if (SetProperty(ref _isAcquiring, value))
RaiseAllCommandsCanExecuteChanged();
}
}
#endregion
#region 操作忙碌状态 | Operation busy state
private bool _isBusy;
///
/// 是否正在执行连接/断开操作 | Whether connect/disconnect is in progress
///
public bool IsBusy
{
get => _isBusy;
private set
{
if (SetProperty(ref _isBusy, value))
RaiseAllCommandsCanExecuteChanged();
}
}
private string _statusMessage = "";
///
/// 操作状态消息 | Operation status message
///
public string StatusMessage
{
get => _statusMessage;
private set => SetProperty(ref _statusMessage, value);
}
#endregion
#region 命令 | Commands
///
/// 连接探测器命令 | Connect detector command
///
public DelegateCommand ConnectCommand { get; }
///
/// 断开探测器命令 | Disconnect detector command
///
public DelegateCommand DisconnectCommand { get; }
///
/// 启动连续采集命令 | Start continuous acquisition command
///
public DelegateCommand StartAcquisitionCommand { get; }
///
/// 停止采集命令 | Stop acquisition command
///
public DelegateCommand StopAcquisitionCommand { get; }
///
/// 单帧采集命令 | Single frame acquisition command
///
public DelegateCommand SingleFrameCommand { get; }
///
/// 保存图像命令 | Save image command
///
public DelegateCommand SaveImageCommand { get; }
#endregion
#region 构造函数 | Constructor
public DetectorImageWindowViewModel(
IDetectorService detectorService,
IEventAggregator eventAggregator,
IImageService imageSaveService,
ILoggerService logger)
{
_detectorService = detectorService ?? throw new ArgumentNullException(nameof(detectorService));
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_imageSaveService = imageSaveService ?? throw new ArgumentNullException(nameof(imageSaveService));
_logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger));
ConnectCommand = new DelegateCommand(ExecuteConnectAsync, CanExecuteConnect);
DisconnectCommand = new DelegateCommand(ExecuteDisconnectAsync, CanExecuteDisconnect);
StartAcquisitionCommand = new DelegateCommand(ExecuteStartAcquisitionAsync, CanExecuteStartAcquisition);
StopAcquisitionCommand = new DelegateCommand(ExecuteStopAcquisitionAsync, CanExecuteStopAcquisition);
SingleFrameCommand = new DelegateCommand(ExecuteSingleFrameAsync, CanExecuteSingleFrame);
SaveImageCommand = new DelegateCommand(ExecuteSaveImageAsync, CanExecuteSaveImage);
// 订阅图像采集事件(后台线程)| Subscribe to image captured event (background thread)
// 图像转换在后台线程执行,避免阻塞 UI | Image conversion runs on background thread to avoid blocking UI
_eventAggregator.GetEvent()
.Subscribe(OnImageCaptured, ThreadOption.BackgroundThread);
// 订阅探测器状态变更事件 | Subscribe to detector status changed event
_eventAggregator.GetEvent()
.Subscribe(OnDetectorStatusChanged, ThreadOption.UIThread);
// 同步初始状态 | Sync initial state
SyncStatusFromService();
}
#endregion
#region CanExecute
private bool CanExecuteConnect() => !_isBusy && !_isConnected;
private bool CanExecuteDisconnect() => !_isBusy && _isConnected && !_isAcquiring;
private bool CanExecuteStartAcquisition() => !_isBusy && _isConnected &&
_detectorStatus == Abstractions.Enums.DetectorStatus.Ready &&
!_isAcquiring;
private bool CanExecuteStopAcquisition() => _isConnected && _isAcquiring;
private bool CanExecuteSingleFrame() => !_isBusy && _isConnected &&
_detectorStatus == Abstractions.Enums.DetectorStatus.Ready &&
!_isAcquiring;
private bool CanExecuteSaveImage() => _isConnected && _imageSaveService.LatestFrame != null;
private void RaiseAllCommandsCanExecuteChanged()
{
ConnectCommand.RaiseCanExecuteChanged();
DisconnectCommand.RaiseCanExecuteChanged();
StartAcquisitionCommand.RaiseCanExecuteChanged();
StopAcquisitionCommand.RaiseCanExecuteChanged();
SingleFrameCommand.RaiseCanExecuteChanged();
SaveImageCommand.RaiseCanExecuteChanged();
}
#endregion
#region 命令执行 | Command execution
///
/// 连接探测器 | Connect detector
///
private async void ExecuteConnectAsync()
{
_logger?.Info("开始连接探测器 | Connecting detector");
IsBusy = true;
StatusMessage = "正在连接探测器... | Connecting...";
try
{
var result = await _detectorService.InitializeAsync();
if (result.IsSuccess)
{
IsConnected = true;
StatusMessage = "探测器连接成功,正在应用参数... | Detector connected, applying parameters...";
_logger?.Info("探测器连接成功 | Detector connected successfully");
RefreshDetectorInfo();
// 连接成功后自动应用持久化的探测器参数 | Auto apply saved detector parameters after connection
var saved = Config.ConfigLoader.LoadSavedParameters();
if (saved.HasValue)
{
var (binIdx, pga, fr, _) = saved.Value;
var applyResult = await _detectorService.ApplyParametersAsync(binIdx, pga, fr);
if (applyResult.IsSuccess)
{
StatusMessage = "探测器就绪 | Detector ready";
_logger?.Info("探测器参数已自动应用,Binning={Binning},PGA={PGA},帧率={FrameRate} | Detector parameters auto-applied", binIdx, pga, fr);
}
else
{
StatusMessage = $"参数应用失败:{applyResult.ErrorMessage}";
_logger?.Warn("自动应用参数失败:{Message} | Auto apply parameters failed: {Message}", applyResult.ErrorMessage);
}
}
else
{
StatusMessage = "探测器就绪(使用默认参数)| Detector ready (default parameters)";
_logger?.Info("未找到持久化参数,使用默认配置 | No saved parameters found, using default config");
}
}
else
{
StatusMessage = $"连接失败:{result.ErrorMessage}";
_logger?.Error(result.Exception, "探测器连接失败:{Message} | Detector connection failed: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
StatusMessage = $"连接异常:{ex.Message}";
_logger?.Error(ex, "探测器连接异常:{Message} | Detector connection exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
}
}
///
/// 断开探测器 | Disconnect detector
///
private async void ExecuteDisconnectAsync()
{
_logger?.Info("开始断开探测器 | Disconnecting detector");
IsBusy = true;
StatusMessage = "正在断开探测器... | Disconnecting...";
try
{
var result = await _detectorService.DisconnectAsync();
IsConnected = false;
IsAcquiring = false;
ClearDetectorInfo();
ImageInfo = "等待采集... | Waiting for acquisition...";
if (result.IsSuccess)
{
StatusMessage = "探测器已断开 | Detector disconnected";
_logger?.Info("探测器已断开 | Detector disconnected");
}
else
{
StatusMessage = $"断开时发生错误:{result.ErrorMessage}";
_logger?.Warn("断开探测器时发生错误:{Message} | Error during disconnect: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
IsConnected = false;
IsAcquiring = false;
ClearDetectorInfo();
StatusMessage = $"断开异常:{ex.Message}";
_logger?.Error(ex, "断开探测器异常:{Message} | Disconnect exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
}
}
///
/// 启动连续采集 | Start continuous acquisition
///
private async void ExecuteStartAcquisitionAsync()
{
_logger?.Info("启动连续采集 | Starting continuous acquisition");
IsAcquiring = true;
StatusMessage = "采集中... | Acquiring...";
try
{
var result = await _detectorService.StartAcquisitionAsync();
if (!result.IsSuccess)
{
IsAcquiring = false;
StatusMessage = $"启动采集失败:{result.ErrorMessage}";
_logger?.Error(result.Exception, "启动采集失败:{Message} | Start acquisition failed: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
IsAcquiring = false;
StatusMessage = $"启动采集异常:{ex.Message}";
_logger?.Error(ex, "启动采集异常:{Message} | Start acquisition exception: {Message}", ex.Message);
}
}
///
/// 停止采集 | Stop acquisition
///
private async void ExecuteStopAcquisitionAsync()
{
_logger?.Info("停止采集 | Stopping acquisition");
StatusMessage = "正在停止采集... | Stopping...";
try
{
var result = await _detectorService.StopAcquisitionAsync();
IsAcquiring = false;
StatusMessage = result.IsSuccess ? "采集已停止 | Acquisition stopped" : $"停止采集失败:{result.ErrorMessage}";
if (!result.IsSuccess)
_logger?.Error(result.Exception, "停止采集失败:{Message} | Stop acquisition failed: {Message}", result.ErrorMessage);
}
catch (Exception ex)
{
IsAcquiring = false;
StatusMessage = $"停止采集异常:{ex.Message}";
_logger?.Error(ex, "停止采集异常:{Message} | Stop acquisition exception: {Message}", ex.Message);
}
}
///
/// 单帧采集 | Single frame acquisition
///
private async void ExecuteSingleFrameAsync()
{
_logger?.Info("单帧采集 | Single frame acquisition");
IsBusy = true;
StatusMessage = "单帧采集中... | Acquiring single frame...";
try
{
var result = await _detectorService.AcquireSingleFrameAsync();
StatusMessage = result.IsSuccess ? "单帧采集完成 | Single frame acquired" : $"单帧采集失败:{result.ErrorMessage}";
if (!result.IsSuccess)
_logger?.Error(result.Exception, "单帧采集失败:{Message} | Single frame failed: {Message}", result.ErrorMessage);
}
catch (Exception ex)
{
StatusMessage = $"单帧采集异常:{ex.Message}";
_logger?.Error(ex, "单帧采集异常:{Message} | Single frame exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
}
}
///
/// 保存当前图像为 16 位 TIFF | Save current image as 16-bit TIFF
///
private async void ExecuteSaveImageAsync()
{
var frame = _imageSaveService.LatestFrame;
if (frame?.ImageData == null)
{
StatusMessage = "无可保存的图像 | No image to save";
return;
}
_logger?.Info("保存图像,帧号:{FrameNumber} | Saving image, frame: {FrameNumber}", frame.FrameNumber);
StatusMessage = "正在保存图像... | Saving image...";
try
{
var saveDir = _imageSaveService.GetDefaultSaveDirectory();
var result = await _imageSaveService.SaveLatestFrameAsync(saveDir, "IMG");
StatusMessage = result.IsSuccess
? $"图像已保存 | Image saved"
: $"保存失败:{result.ErrorMessage}";
}
catch (Exception ex)
{
StatusMessage = $"保存异常:{ex.Message}";
_logger?.Error(ex, "保存图像异常:{Message} | Save image exception: {Message}", ex.Message);
}
}
#endregion
#region 事件回调 | Event callbacks
///
/// 图像采集回调,将 16 位原始数据归一化为 8 位灰度图 | Image captured callback
///
private void OnImageCaptured(ImageCapturedEventArgs args)
{
if (args?.ImageData == null || args.Width == 0 || args.Height == 0) return;
// 帧节流:上一帧尚未被 UI 线程消费完毕时,跳过当前帧
// Frame throttle: skip current frame if previous frame hasn't been consumed by UI thread yet
if (Interlocked.CompareExchange(ref _isProcessingFrame, 1, 0) != 0) return;
try
{
// 在后台线程执行图像转换 | Perform image conversion on background thread
var bitmap = ConvertToBitmapSource(args.ImageData, (int)args.Width, (int)args.Height);
bitmap.Freeze(); // Freeze 后可跨线程访问 | Freeze allows cross-thread access
var frameNumber = args.FrameNumber;
var imageInfo = $"{args.Width}×{args.Height} 帧#{args.FrameNumber} {args.CaptureTime:HH:mm:ss.fff}";
// 将属性赋值调度到 UI 线程,完成后才释放节流标志
// Dispatch property assignment to UI thread; release throttle flag only after UI completes
Application.Current?.Dispatcher?.BeginInvoke(new Action(() =>
{
try
{
ImageSource = bitmap;
FrameNumber = frameNumber;
ImageInfo = imageInfo;
}
finally
{
// UI 线程完成渲染后才允许下一帧进入 | Allow next frame only after UI thread finishes rendering
Interlocked.Exchange(ref _isProcessingFrame, 0);
}
}));
}
catch (Exception ex)
{
// 异常时释放标志,避免永久锁死 | Release flag on exception to avoid permanent lock
Interlocked.Exchange(ref _isProcessingFrame, 0);
_logger?.Error(ex, "图像转换失败:{Message} | Image conversion failed: {Message}", ex.Message);
}
}
///
/// 探测器状态变更回调 | Detector status changed callback
///
private void OnDetectorStatusChanged(DetectorStatus status)
{
DetectorStatus = status;
// 根据状态同步 IsAcquiring | Sync IsAcquiring from status
if (status == Abstractions.Enums.DetectorStatus.Acquiring)
IsAcquiring = true;
else if (status == Abstractions.Enums.DetectorStatus.Ready ||
status == Abstractions.Enums.DetectorStatus.Error)
IsAcquiring = false;
// 探测器错误时同步连接状态 | Sync connection state on error
if (status == Abstractions.Enums.DetectorStatus.Uninitialized)
{
IsConnected = false;
ClearDetectorInfo();
}
}
#endregion
#region 私有辅助方法 | Private helpers
///
/// 从服务层同步当前状态(ViewModel 初始化时调用)| Sync state from service (called on init)
///
private void SyncStatusFromService()
{
IsConnected = _detectorService.IsConnected;
DetectorStatus = _detectorService.Status;
if (IsConnected)
RefreshDetectorInfo();
}
///
/// 刷新探测器信息显示 | Refresh detector info display
///
private void RefreshDetectorInfo()
{
try
{
var info = _detectorService.GetInfo();
if (info == null) return;
DetectorModel = string.IsNullOrEmpty(info.Model) ? "--" : info.Model;
DetectorResolution = (info.MaxWidth > 0 && info.MaxHeight > 0)
? $"{info.MaxWidth}×{info.MaxHeight}"
: "--";
DetectorPixelSize = info.PixelSize > 0
? $"{info.PixelSize:F3} mm"
: "--";
DetectorTypeText = _detectorService.Type?.ToString() ?? "--";
}
catch (Exception ex)
{
_logger?.Warn("获取探测器信息失败:{Message} | Failed to get detector info: {Message}", ex.Message);
}
}
///
/// 清空探测器信息 | Clear detector info
///
private void ClearDetectorInfo()
{
DetectorModel = "--";
DetectorResolution = "--";
DetectorPixelSize = "--";
DetectorTypeText = "--";
}
///
/// 将 16 位灰度数据转换为 8 位 BitmapSource(委托给 XP.Common 通用转换器)
/// Convert 16-bit grayscale data to 8-bit BitmapSource (delegates to XP.Common common converter)
///
private static BitmapSource ConvertToBitmapSource(ushort[] data, int width, int height)
{
return XP.Common.Converters.ImageConverter.ConvertGray16ToBitmapSource(data, width, height);
}
#endregion
}
}