using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SSPCTester.Devices.Interfaces;
using SSPCTester.Devices.Config;
using Microsoft.Extensions.Options;
using SSPCTester.Logic.Models;
using SSPCTester.Logic.Storage;
using SSPCTester.UI.Services;
namespace SSPCTester.UI.ViewModels;
/// 主窗口 VM:持有项目集合、当前页面、应用状态。
public partial class MainViewModel : ObservableObject
{
private readonly IServiceProvider _sp;
private readonly ProjectStore _store;
private readonly ILogger _logger;
private readonly ISspc _sspc;
private readonly IPowerSupply _power;
private readonly ILoad _load;
private readonly IDaq _daq;
private readonly UiLogSink _logSink;
private readonly DeviceOptions _deviceOptions;
private readonly DispatcherTimer _deviceMonitorTimer;
private object? _viewBeforeSettings;
public MainViewModel(
IServiceProvider sp, ProjectStore store, ILogger logger,
ISspc sspc, IPowerSupply power, ILoad load, IDaq daq, UiLogSink logSink,
IOptions deviceOptions)
{
_sp = sp; _store = store; _logger = logger;
_sspc = sspc; _power = power; _load = load; _daq = daq;
_logSink = logSink;
_deviceOptions = deviceOptions.Value;
_sspc.ConnectionChanged += (_, _) => RefreshDeviceStatus();
_power.ConnectionChanged += (_, _) => RefreshDeviceStatus();
_load.ConnectionChanged += (_, _) => RefreshDeviceStatus();
_daq.ConnectionChanged += (_, _) => RefreshDeviceStatus();
_deviceMonitorTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_deviceMonitorTimer.Tick += (_, _) => RefreshDeviceStatus();
_deviceMonitorTimer.Start();
AllProjects = new ObservableCollection();
TreeRoots = new ObservableCollection();
}
/// 由 MainWindow.Loaded 调用,启动背景加载(避免在 DI 构造器中触发重入)。
public void Initialize()
{
if (_initialized) return;
_initialized = true;
_ = LoadProjectsAsync();
_ = ConnectConfiguredDevicesAsync();
}
private bool _initialized;
public ObservableCollection AllProjects { get; }
public ObservableCollection TreeRoots { get; }
[ObservableProperty] private AppState _state = AppState.Browsing;
[ObservableProperty] private Project? _currentProject;
[ObservableProperty] private object? _currentView;
[ObservableProperty] private bool _devicesConnected;
[ObservableProperty] private bool _devicePanelOpen;
[ObservableProperty] private bool _deviceOperationBusy;
[ObservableProperty] private string _deviceSummary = "设备状态检查中";
[ObservableProperty] private string _sspcConnectionStatus = "检查中";
[ObservableProperty] private string _powerConnectionStatus = "检查中";
[ObservableProperty] private string _loadConnectionStatus = "检查中";
[ObservableProperty] private string _daqConnectionStatus = "检查中";
[ObservableProperty] private string _testingMessage = "";
[ObservableProperty] private TestSessionViewModel? _currentSession;
partial void OnStateChanged(AppState value) => RebuildTree();
partial void OnCurrentProjectChanged(Project? value) => RebuildTree();
private async Task ConnectConfiguredDevicesAsync()
{
var jobs = new[]
{
ConnectOneAsync("SSPC", _sspc, _deviceOptions.Sspc.AutoConnect),
ConnectOneAsync("电源", _power, _deviceOptions.PowerSupply.AutoConnect),
ConnectOneAsync("负载", _load, _deviceOptions.Load.AutoConnect),
ConnectOneAsync("PCIe8586", _daq, _deviceOptions.Daq.AutoConnect)
};
await Task.WhenAll(jobs);
DevicesConnected =
(!_deviceOptions.Sspc.AutoConnect || _sspc.IsConnected) &&
(!_deviceOptions.PowerSupply.AutoConnect || _power.IsConnected) &&
(!_deviceOptions.Load.AutoConnect || _load.IsConnected) &&
(!_deviceOptions.Daq.AutoConnect || _daq.IsConnected);
RefreshDeviceStatus();
}
private async Task ConnectOneAsync(string name, IDeviceConnection device, bool autoConnect)
{
if (!autoConnect) return;
try
{
bool connected = await device.ConnectAsync();
if (connected && device.IsConnected)
{
_logSink.Add(UiLogLevel.Success, $"{name} 已自动连接。");
}
else
{
_logSink.Add(UiLogLevel.Warning, $"{name} 自动连接未成功,设备保持未连接。");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "{Device} auto connect failed.", name);
_logSink.Add(UiLogLevel.Error, $"{name} 自动连接失败:{ex.Message}");
}
}
private void RefreshDeviceStatus()
{
Application.Current?.Dispatcher.Invoke(() =>
{
SspcConnectionStatus = DeviceStatusText(_sspc);
PowerConnectionStatus = DeviceStatusText(_power);
LoadConnectionStatus = DeviceStatusText(_load);
DaqConnectionStatus = DeviceStatusText(_daq);
DevicesConnected =
(!_deviceOptions.Sspc.AutoConnect || _sspc.IsConnected) &&
(!_deviceOptions.PowerSupply.AutoConnect || _power.IsConnected) &&
(!_deviceOptions.Load.AutoConnect || _load.IsConnected) &&
(!_deviceOptions.Daq.AutoConnect || _daq.IsConnected);
int connected = new[] { _sspc.IsConnected, _power.IsConnected, _load.IsConnected, _daq.IsConnected }.Count(x => x);
DeviceSummary = connected == 4 ? "设备已连接" : $"设备 {connected}/4 已连接";
});
}
private static string DeviceStatusText(IDeviceConnection device) =>
device.IsConnected ? "已连接" : "未连接";
[RelayCommand]
private void ToggleDevicePanel()
{
RefreshDeviceStatus();
DevicePanelOpen = !DevicePanelOpen;
}
[RelayCommand]
private async Task ConnectAllDevices()
{
if (DeviceOperationBusy) return;
DeviceOperationBusy = true;
try
{
await Task.WhenAll(
ConnectOneAsync("SSPC", _sspc, true),
ConnectOneAsync("电源", _power, true),
ConnectOneAsync("负载", _load, true),
ConnectOneAsync("PCIe8586", _daq, true));
}
finally
{
DeviceOperationBusy = false;
RefreshDeviceStatus();
}
}
[RelayCommand]
private async Task DisconnectAllDevices()
{
if (DeviceOperationBusy) return;
DeviceOperationBusy = true;
try
{
await Task.WhenAll(
SafeDisconnectAsync("SSPC", _sspc),
SafeDisconnectAsync("电源", _power),
SafeDisconnectAsync("负载", _load),
SafeDisconnectAsync("PCIe8586", _daq));
}
finally
{
DeviceOperationBusy = false;
RefreshDeviceStatus();
}
}
[RelayCommand]
private void RefreshDevices() => RefreshDeviceStatus();
private async Task SafeDisconnectAsync(string name, IDeviceConnection device)
{
try
{
await device.DisconnectAsync();
_logSink.Add(UiLogLevel.Info, $"{name} 已断开。");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "{Device} disconnect failed.", name);
_logSink.Add(UiLogLevel.Error, $"{name} 断开失败:{ex.Message}");
}
}
public async Task LoadProjectsAsync()
{
AllProjects.Clear();
var list = await _store.LoadAllAsync();
foreach (var p in list) AllProjects.Add(p);
RebuildTree();
if (CurrentView == null) NavigateHome();
}
public void RebuildTree()
{
TreeRoots.Clear();
var src = State == AppState.Testing && CurrentProject != null
? new[] { CurrentProject }
: AllProjects.ToArray();
foreach (var p in src)
{
var node = new ProjectNode(p);
node.Children.Add(new ProjectNode(p, NodeKind.Info));
node.Children.Add(new ProjectNode(p, NodeKind.Test));
node.Children.Add(new ProjectNode(p, NodeKind.Report));
TreeRoots.Add(node);
}
}
[RelayCommand]
private void NavigateHome()
{
CurrentView = _sp.GetRequiredService();
}
[RelayCommand]
private async Task NewProject()
{
var dlg = new Views.NewProjectDialog { Owner = Application.Current?.MainWindow };
if (dlg.ShowDialog() != true) return;
var p = new Project
{
Name = dlg.ProjectName,
Dut = new DutInfo
{
ProductName = dlg.DeviceName,
SerialNumber = dlg.SerialNumber,
Model = dlg.Model,
},
Site = new TestSiteInfo
{
Tester = dlg.Tester,
TestDate = DateTime.Today,
}
};
await _store.SaveAsync(p);
AllProjects.Insert(0, p);
CurrentProject = p;
OpenProjectInfo(p);
}
public void OpenProjectInfo(Project p)
{
CurrentProject = p;
var vm = _sp.GetRequiredService();
vm.SetProject(p, StartTesting);
CurrentView = vm;
}
public void OpenTestHost(Project p)
{
CurrentProject = p;
var vm = _sp.GetRequiredService();
vm.SetProject(p);
CurrentView = vm;
}
public void OpenReport(Project p)
{
CurrentProject = p;
var vm = _sp.GetRequiredService();
vm.SetProject(p);
CurrentView = vm;
}
[RelayCommand]
private void OpenSettings()
{
if (CurrentView is SettingsViewModel)
{
CloseSettings();
return;
}
_viewBeforeSettings = CurrentView;
var vm = _sp.GetRequiredService();
vm.SetCloseAction(CloseSettings);
CurrentView = vm;
DevicePanelOpen = false;
}
[RelayCommand]
private void OpenSettingsFromDevicePanel()
{
DevicePanelOpen = false;
OpenSettings();
}
private void CloseSettings()
{
CurrentView = _viewBeforeSettings ?? _sp.GetRequiredService();
_viewBeforeSettings = null;
}
/// 从项目信息页点击"开始测试"调用。
public async void StartTesting(Project p)
{
CurrentProject = p;
State = AppState.Testing;
TestingMessage = $"测试进行中:{p.Name}";
var session = new TestSessionViewModel(_sp, p, _sspc, _power, _load, _daq, _logSink);
CurrentSession = session;
var host = _sp.GetRequiredService();
host.SetProject(p);
host.AttachSession(session);
CurrentView = host;
try
{
await session.RunAllAsync();
_logSink.Add(UiLogLevel.Success, "全部测试完成。");
}
catch (OperationCanceledException)
{
_logSink.Add(UiLogLevel.Warning, "测试已中止。");
}
catch (Exception ex)
{
_logger.LogError(ex, "Test failed.");
_logSink.Add(UiLogLevel.Error, "测试出错:" + ex.Message);
}
finally
{
await _store.SaveAsync(p);
State = AppState.Browsing;
CurrentSession = null;
}
}
[RelayCommand]
private void AbortTest()
{
CurrentSession?.Cancel();
}
[RelayCommand]
private void OpenProjectsFolder()
{
if (Directory.Exists(_store.ProjectsRoot))
System.Diagnostics.Process.Start("explorer.exe", _store.ProjectsRoot);
}
/// 由 View 双击树节点时调用。
public void HandleNodeActivated(ProjectNode? node)
{
if (node == null) return;
switch (node.Kind)
{
case NodeKind.Root:
case NodeKind.Info:
OpenProjectInfo(node.Project); break;
case NodeKind.Test:
OpenTestHost(node.Project); break;
case NodeKind.Report:
OpenReport(node.Project); break;
}
}
}
/// 侧栏 TreeView 用的节点。
public sealed class ProjectNode
{
public Project Project { get; }
public NodeKind Kind { get; }
public string Title { get; }
public ObservableCollection Children { get; } = new();
public ProjectNode(Project p, NodeKind kind = NodeKind.Root)
{
Project = p;
Kind = kind;
Title = kind switch
{
NodeKind.Root => p.Name,
NodeKind.Info => "项目信息",
NodeKind.Test => "测试",
NodeKind.Report => "报告",
_ => p.Name
};
}
}
public enum NodeKind { Root, Info, Test, Report }