交运行工位桩:三口契约、运行服务、六态 VM、跨线程切 UI。

采集/PLC/Inspect 均为桩,验收 1–8 可测。不部署现场。
This commit is contained in:
C# 开发
2026-08-19 01:16:32 +00:00
commit bcddcd0bed
36 changed files with 1432 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using VisionHost.Contracts;
namespace VisionHost.Runtime;
public sealed class RelayCommand : ICommand
{
private readonly Action _exec;
private readonly Func<bool>? _can;
public RelayCommand(Action exec, Func<bool>? can = null)
{
_exec = exec;
_can = can;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => _can?.Invoke() ?? true;
public void Execute(object? parameter) => _exec();
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public sealed class RunViewModel : INotifyPropertyChanged, IDisposable
{
private readonly RunService _run;
private readonly IUiDispatch _ui;
private StationSnapshot _s;
private int _disposed;
public RunViewModel(RunService run, IUiDispatch? ui = null)
{
_run = run;
_ui = ui ?? new SynchronizationContextDispatch();
_s = run.Snapshot();
StartCommand = new RelayCommand(() => _run.RequestStart(), () => CanStart);
StopCommand = new RelayCommand(() => _run.RequestStop(), () => CanStop);
_run.Changed += OnChanged;
}
public static RunViewModel CreateStub(IUserConfirm? confirm = null, IUiDispatch? ui = null)
{
var cam = new StubFrameSource();
var run = new RunService(
cam,
cam,
new StubInspectService(),
new StubPlcIo(),
confirm ?? new AlwaysConfirm(),
new RuntimeOptions());
return new RunViewModel(run, ui);
}
public event PropertyChangedEventHandler? PropertyChanged;
public ICommand StartCommand { get; }
public ICommand StopCommand { get; }
public RunState RunState => _s.RunState;
public string RecipeName => _s.RecipeName;
public string ConnectionText => _s.ConnectionText;
public object? LiveImage => _s.LiveImage;
public object? OverlayImage => _s.OverlayImage;
public string BadgeText => _s.BadgeText;
public string LastResultText => _s.LastResultText;
public string LastResultBrush => _s.LastResultBrush;
public int OkCount => _s.OkCount;
public int NgCount => _s.NgCount;
public int TotalCount => _s.TotalCount;
public string AlarmText => _s.AlarmText;
public string AlarmBrush => _s.AlarmBrush;
public bool CanStart => _s.CanStart;
public bool CanStop => _s.CanStop;
public RunService Service => _run;
private void OnChanged() => _ui.Post(ApplyOnUi);
private void ApplyOnUi()
{
if (Volatile.Read(ref _disposed) != 0) return;
_s = _run.Snapshot();
OnPropertyChanged(string.Empty);
((RelayCommand)StartCommand).RaiseCanExecuteChanged();
((RelayCommand)StopCommand).RaiseCanExecuteChanged();
}
private void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
_run.Changed -= OnChanged;
_run.Dispose();
}
}