_scanAxis = new XYScanAxis();
csharp
public XYScanAxis()
{
CurrentStatus = AxisWorkStatus.Idle; // 轴默认状态:空闲待机
Posx = 0; // X轴初始坐标归零
Posy = 0; // Y轴初始坐标归零
}
业务层面:五大核心功能载体
1. 封装所有运动动作
(ViewModel 只负责调用,不写运动逻辑)
你所有扫描功能,本质都是调用这个对象的方法:
点位扫描:_scanAxis.MoveToAsync(50,0,token) → 控制轴移动到目标坐标
连续扫描:_scanAxis.StartContinueScanAsync(token) → 控制轴匀速连续移动
回零 / 停止 / 急停:HomeAsync() / NormalStop() / EmergencyStop()
ViewModel 不需要关心运动底层怎么实现,只需要调用这个对象的接口,实现业务与底层解耦。
2. 存储轴的实时状态与坐标(UI 显示的数据源)
_scanAxis 里保存了轴的核心数据,是界面状态显示的唯一来源:
状态:CurrentStatus(Idle/Moving/Scanning/LimitAlarm 等)
位置:Posx、Posy(实时 XY 坐标)
界面上的 AxisStatusText 文本,就是读取这个对象的数据刷新而来。
3. 承载设备安全保护逻辑
限位判断、报警复位等安全规则全部封装在 XYScanAxis 内部:
CheckLimit():判断 X 轴是否超出行程,触发限位报警
ResetAlarm():故障 / 限位后,执行轴复位、坐标归零
StartAxisStatusRefresh() 监控线程,本质就是实时读取 _scanAxis 的状态,判断是否触发安全保护。
4. 保证全局唯一的运动状态
整个 ViewModel 生命周期内,只实例化一次 _scanAxis,所有按钮(点位扫描、连续扫描、停止)共用同一个轴对象。
避免出现「多个轴实例状态冲突」,比如一边扫描、一边回零的逻辑混乱。
5. 适配后续硬件对接(架构可扩展性)
这是 MVVM 架构的核心优势:
现在 XYScanAxis 是模拟运动逻辑(Task.Delay 模拟硬件运动延时)
后续对接真实运动控制卡(伺服 / 步进驱动器)时,只需要修改 XYScanAxis 内部的运动实现,ViewModel 里 _scanAxis 的调用代码完全不用改,上层业务零改动。
XYScanAxis 不是物理轴
XYScanAxis 不是坐标系上的一个点
XYScanAxis 自己不会动
它是 → 软件层面的「运动控制器 / 指挥官」
物理 XY 轴 = 真实的电机、导轨、工作台(会真正移动的硬件)
XYScanAxis = 专门给这个物理轴发命令的软件控制器
它的工作:发指令 → 记录轴的位置 → 反馈轴的状态
它不运动,是指挥轴运动
物理轴 = 一辆小车
XYScanAxis = 小车的遥控器 + 行车电脑
遥控器自己不会跑
遥控器不是小车本身
遥控器不是路上的一个点
遥控器指挥小车跑、记录小车位置、告诉我们小车在哪

await scanLogic(token);
MVVM
V
csharp
<Window x:Class="WpfApp6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp6"
xmlns:winforms="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
xmlns:halcon="clr-namespace:HalconDotNet;assembly=halcondotnet"
mc:Ignorable="d"
Title="x光检测系统" Height="550" Width="900" Closed="OnClosed">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal" Margin="10">
<TextBlock Text="{Binding Status}" FontSize="16" FontWeight="Bold" Foreground="blue"/>
<TextBlock Text="|" FontSize="16" Margin="5,0"/>
<TextBlock Text="{Binding AxisStatusText}" FontSize="14" Foreground="DarkGreen"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0" Margin="10" Orientation="Vertical">
<Border BorderBrush="Gray" BorderThickness="1" Padding="8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="曝光(ms):" VerticalAlignment="Center"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding ExposureTime,Mode=TwoWay}" IsEnabled="{Binding CanEditParam}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="扫描速度:" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding ScanSpeed,Mode=TwoWay,StringFormat=F2}"
IsEnabled="{Binding CanEditParam}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="目标X坐标:" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding TargetX,Mode=TwoWay,StringFormat=F1}"
IsEnabled="{Binding CanEditParam}"/>
</Grid>
</Border>
</StackPanel>
<ProgressBar Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Value="{Binding Rate}" Minimum="0" Maximum="1"
Height="20" Width="400" Margin="10" HorizontalAlignment="Center"/>
<ListBox Grid.Row="3" Grid.Column="0"
ItemsSource="{Binding LogList}" Height="auto" Margin="10">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="Black" FontSize="12"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--
<TextBlock Text="{Binding Status}" FontSize="16" FontWeight="Bold" Foreground="Blue" Margin="5"/>
<TextBlock Text="{Binding AxisStatusText}" FontSize="14" Margin="5" Height="auto" Foreground="DarkGreen"/>
<ProgressBar Value="{Binding Rate}"
Minimum="0"
Maximum="1"
VerticalAlignment="Top" Height="20" Width="300" Margin="0 60 0 0"/>
<ListBox ItemsSource="{Binding LogList}" Height="150" Width="350"
Margin="0 100 0 0" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="Black"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>-->
<winforms:WindowsFormsHost Grid.Row="3" Grid.Column="1"
Margin="10" Background="Black">
<halcon:HWindowControl x:Name="HalconWinFormsControl"/>
</winforms:WindowsFormsHost>
<StackPanel Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 10">
<Button Command="{Binding PointScanC}" Content="点位扫描"
Width="100" Height="35" Margin="5" FontSize="14"/>
<Button Command="{Binding ContinueScanC}" Content="连续扫描"
Width="100" Height="35" Margin="5" FontSize="14"/>
<Button Command="{Binding StopC}" Content="停止"
Width="100" Height="35" Margin="5" FontSize="14"/>
<Button Command="{Binding EmergencyStopC}" Content="急停"
Width="100" Height="35" Margin="5" Background="Red" Foreground="white" FontSize="14"/>
<Button Command="{Binding ResetC}" Content="复位"
Width="100" Height="35" Margin="5" FontSize="14"/>
<Button Command="{Binding TestImageC}" Content="测试图像"
Width="100" Height="35" Margin="5" Background="Orange" Foreground="White"/>
</StackPanel>
</Grid>
</Window>
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp6
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainViewModel(HalconWinFormsControl);
DataContext = _viewModel;
}
protected override void OnClosed( EventArgs e)
{
base.OnClosed( e );
_viewModel.ShutdownAllResources();
}
private void OnClosed(object sender, EventArgs e)
{
}
//private void MainWindow_Loaded(object sender, RoutedEventArgs e)
//{
// // throw new NotImplementedException();
// this.DataContext = new MainViewModel(HalconWinFormsControl);
//}
}
}
VM
csharp
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using HalconDotNet;
namespace WpfApp6
{
public class MainViewModel : Inotifybase
{
private readonly XYScanAxis _scanAxis;
// private readonly HWindowControlWPF _halconWpfWindow;
private readonly HWindowControl _halconWinFormsControl;
private CancellationTokenSource _cts;
private CancellationTokenSource _axisStatusCts;
private bool _isXrayOpen;
private int _exposureTime=200;
public int ExposureTime
{
get { return _exposureTime; }
set {
if (value >= 10 && value <= 1000)
{ _exposureTime = value;
OnPropertyChanged();
}
}
}
private double _scanSpeed=0.5;
public double ScanSpeed
{
get { return _scanSpeed; }
set {
if(value >= 0.1 && value <= 2.0)
{ _scanSpeed = value; OnPropertyChanged();}
}
}
private double _targetX=50;
public double TargetX
{
get { return _targetX ; }
set {
if(value>=0 && value <= XYScanAxis.X_MAX_LIMIT)
{ _targetX = value; OnPropertyChanged(); }
}
}
private double _targetY=0;
public double TargetY
{
get { return _targetY; }
set {
if (value >= 0) { _targetY = value; OnPropertyChanged(); }
}
}
public bool CanEditParam => Status == STATUS_READY || Status == STATUS_STOP || Status == STATUS_FAULT
|| Status == STATUS_LIMIT;
private string _axisStatusText;
public string AxisStatusText
{
get { return _axisStatusText; }
set
{
_axisStatusText = value;
OnPropertyChanged();
}
}
private string status;
public string Status
{
get { return status; }
set
{
status = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CanEditParam));
CommandManager.InvalidateRequerySuggested();
}
}
private double rate;
public double Rate
{
get { return rate; }
set
{
rate = value;
OnPropertyChanged();
}
}
public ObservableCollection<string> LogList { get; } = new ObservableCollection<string>();
// public RelayCommand StartC { get; }
// private HImage _xRayImage;
private const string STATUS_INIT = "初始化中...";
private const string STATUS_READY = "已就绪";
private const string STATUS_RUNNING = "运行中";
private const string STATUS_STOP = "已停止";
private const string STATUS_FAULT = "故障";
private const string STATUS_FINISH = "检测完成";
private const string STATUS_LIMIT = "限位报警";
private const string STATUS_ARRIVED = "点位已到位";
public RelayCommand PointScanC { get; }
public RelayCommand ContinueScanC { get; }
public RelayCommand StopC { get; }
//public RelayCommand StopC { get; }
public RelayCommand EmergencyStopC { get; }
public RelayCommand ResetC { get; }
public RelayCommand TestImageC { get; }
public RelayCommand SimulateXrayFaultC { get; }
public MainViewModel(HWindowControl halconControl)
{
_scanAxis = new XYScanAxis();
_halconWinFormsControl = halconControl ?? throw new ArgumentNullException(nameof(halconControl));
TestImageC = new RelayCommand(TestShowImage);
PointScanC = new RelayCommand(StartPointScan, CanStartScan);
ContinueScanC = new RelayCommand(StartContinueScan, CanStartScan);
//StartC = new RelayCommand(Start, CanStart);
StopC = new RelayCommand(NormalStop, CanStop);
EmergencyStopC = new RelayCommand(EmergencyStop);
ResetC = new RelayCommand(ResetAlarm, CanReset);
SimulateXrayFaultC = new RelayCommand(SimulateXrayHardwareFault);
Loading();
StartAxisStatusRefresh();
}
//{
// //throw new NotImplementedException();
//}
//{
// throw new NotImplementedException();
//}
//private async Task DeviceInitWork(CancellationToken token)
//{
// await _scanAxis.HomeAsync(token);
// if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.Fault)
// {
// Status = STATUS_FAULT;
// return;
// }
// Status = STATUS_READY;
// RefreshAxisStatus();
//}
//{
// //throw new NotImplementedException();
// return Status ==STATUS_RUNNING;
//}
//private async void Stop()
//{
// try
// {
// _cts?.Cancel();
// await Task.Delay(100);
// CloseXray();
// await Task.Delay(900);
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_STOP;
// Rate = 0;
// _halconWinFormsControl?.HalconWindow?.ClearWindow();
// });
// }
// catch (Exception ex)
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_FAULT;
// MessageBox.Show(ex.Message);
// });
// }
// finally
// {
// _cts?.Dispose();
// _cts = null;
// }
//}
//private bool CanStart()
//{
// //throw new NotImplementedException();
// return Status == STATUS_READY;
//}
//private void Start()
//{
// //throw new NotImplementedException();
// if (Status == STATUS_RUNNING)
// {
// MessageBox.Show("已在运行中");
// return;
// }
// _cts = new CancellationTokenSource();
// var token = _cts.Token;
// Task.Run(async () =>
// {
// try
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_RUNNING;
// Rate = 0;
// });
// _isXrayOpen = true;
// for (int i = 1; i <= 100; i++)
// {
// if (token.IsCancellationRequested) break;
// using (HImage xRayImage = new HImage())
// {
// xRayImage.GenImageConst("byte", 800, 500);
// //HRegion defectRegion = new HRegion(200.0, 300, 300, 400);
// //_xRayImage.SetGrayvalRegion(defectRegion, new HTuple(225));
// //HRegion fullImageRegion = new HRegion(0.0, 0, 800, 500);
// //_xRayImage.PaintRegion(fullImageRegion, new HImage(128, "byte", 1, 1), 0);
// HalconDetectAlgorithm(xRayImage);
// }
// double LRate = i * 0.01;
// Application.Current.Dispatcher.Invoke(new Action(() =>
// {
// Rate = LRate;
// }));
// await Task.Delay(500, token);// Thread.Sleep(500);//
// }
// if (!token.IsCancellationRequested)
// {
// Application.Current.Dispatcher.Invoke((() =>
// {
// Status = STATUS_FINISH;
// Rate = 1.0;
// }));
// }
// }
// catch (TaskCanceledException)
// {
// Application.Current.Dispatcher.Invoke(() => Status = STATUS_STOP);
// }
// catch (Exception ex)
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_FAULT;
// MessageBox.Show(STATUS_FAULT + ex.Message);
// });
// }
// finally
// {
// CloseXray();
// _cts?.Dispose();
// _cts = null;
// }
// });
//}
//private void HalconDetectAlgorithm(HImage image)
//{
// //throw new NotImplementedException();
// if (_halconWinFormsControl?.HalconWindow == null)
// {
// MessageBox.Show("Halcon窗口未初始化", STATUS_FAULT);
// return;
// }
// HImage imageFilter = null;
// HRegion region = null;
// try
// {
// imageFilter = image.MeanImage(3, 3);
// region = imageFilter.Threshold(100.0, 200.0).
// Connection().
// SelectShape("area", "and", 100, 99999);
// int defectCount = region.CountObj();
// Application.Current.Dispatcher.Invoke(() =>
// {
// _halconWinFormsControl.HalconWindow.ClearWindow();
// _halconWinFormsControl.HalconWindow.DispObj(image);
// _halconWinFormsControl.HalconWindow.DispObj(region);
// _halconWinFormsControl.HalconWindow.SetColor("white");
// _halconWinFormsControl.HalconWindow.DispRectangle1(100.0, 100, 400, 700);
// if (defectCount > 0)
// {
// Status = $"检测中:发现{defectCount}个缺陷";
// }
// });
// }
// catch (Exception ex)
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_FAULT;
// MessageBox.Show($"图像处理失败: {ex.Message}", STATUS_FAULT);
// });
// }
// finally
// {
// region?.Dispose();
// imageFilter?.Dispose();
// }
//}
private void ShowXrayImage()
{
if (new Random().Next(1, 80) == 1)
{
AddLog("【硬件故障】图像探测器连接断开");
Status = STATUS_FAULT;
EmergencyStop();
return;
}
if (_halconWinFormsControl?.HalconWindow == null)
{
AddLog("Halcon窗口未初始化,无法显示图像");
return;
}
//HImage rawImage = null;
//HImage smoothImage= null;
//HImage enhanceImage= null;
//HImage xrayImage = null;
try
{
Application.Current.Dispatcher.Invoke(() =>
{
_halconWinFormsControl.HalconWindow.ClearWindow();
using (HImage rawImage = new HImage())
{
int grayBase = 80 + ExposureTime / 10;
rawImage.GenImageConst("byte", 800, 500);
using (HImage brightImage = rawImage.ScaleImage(1.0, grayBase - 128))
{
using (HImage smoothImage = brightImage.MeanImage(3, 3))
{
using (HImage enhanceImage = smoothImage.ScaleImage(1.5, 0))
{
_halconWinFormsControl.HalconWindow.DispObj(enhanceImage);
_halconWinFormsControl.HalconWindow.SetColor("green");
_halconWinFormsControl.HalconWindow.SetLineWidth(2);
_halconWinFormsControl.HalconWindow.DispRectangle1(120.0, 120, 380, 680);
}
}
}
}
AddLog($"X光图像采集成功|曝光:{ExposureTime}ms");
});
}
catch (Exception ex)
{
Application.Current.Dispatcher.Invoke(() =>
{
AddLog($"图像显示异常:{ex.Message}");
Status = STATUS_FAULT;
EmergencyStop();
});
}
//finally
//{
// rawImage?.Dispose();
// smoothImage?.Dispose();
// enhanceImage?.Dispose();
//}
}
public void TestShowImage()
{
AddLog("===单独测试 x光图像显示 ===");
ShowXrayImage();
}
private async void StartPointScan()
{
// throw new NotImplementedException();
await RunScan(async token =>
{
double tarX = TargetX;
double tarY = TargetY;
AddLog($"点位模式:移动到坐标X:{tarX:F1} Y:{tarY:F1}");
await _scanAxis.MoveToAsync(tarX, tarY, token);
if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.Arrived)
{
Status = STATUS_ARRIVED;
AddLog("点位到位,等待采图");
ShowXrayImage();
Application.Current.Dispatcher.Invoke(() => Rate = 1.0);
}
});
}
private async void StartContinueScan()
{
//throw new NotImplementedException();
await RunScan(async token =>
{
double step = ScanSpeed;
AddLog($"连续扫描启动|扫描步长:{step:F2}");
var scanTask = _scanAxis.StartContinueScanAsync(step,token);
while (!token.IsCancellationRequested &&
_scanAxis.CurrentStatus != XYScanAxis.AxisWorkStatus.LimitAlarm)
{
await Task.Delay(200, token);
//await _scanAxis.StartContinueScanAsync(token);
double progress = _scanAxis.Posx / XYScanAxis.X_MAX_LIMIT;
Application.Current.Dispatcher.Invoke(() => Rate = progress);
ShowXrayImage();
}
await scanTask;
Application.Current.Dispatcher.Invoke(() =>
{
Rate = 1.0;
Status = STATUS_FINISH;
AddLog("连续扫描完成");
});
});
}
private async Task StartAxisStatusRefresh()
{
_axisStatusCts = new CancellationTokenSource();
var token = _axisStatusCts.Token;
//throw new NotImplementedException();
try
{
while (!token.IsCancellationRequested)
{
await Task.Delay(200, token);
//RefreshAxisStatus();
Application.Current.Dispatcher.Invoke(RefreshAxisStatus);
if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.LimitAlarm && Status != STATUS_LIMIT)
{
Application.Current.Dispatcher.Invoke(() =>
{
Status = STATUS_LIMIT;
AddLog($"[限位报警]X坐标超出阈值({_scanAxis.Posx:F1})");
EmergencyStop();
});
}
}
}
catch (TaskCanceledException)
{
AddLog("轴状态监控已停止");
}
catch (Exception ex)
{
Application.Current.Dispatcher.Invoke(() =>
{
AddLog($"轴状态刷新异常:{ex.Message}");
Status = STATUS_FAULT;
});
}
finally
{
_axisStatusCts?.Dispose();
_axisStatusCts = null;
}
}
private void StopAxisStatusRefresh()
{
_axisStatusCts?.Cancel();
}
private void RefreshAxisStatus()
{
AxisStatusText = $"轴状态:{_scanAxis.CurrentStatus}|X:{_scanAxis.Posx:F1} Y:{_scanAxis.Posy:F1}";
//OnPropertyChanged();
}
private bool CanStartScan() => Status == STATUS_READY;
private bool CanStop() => Status == STATUS_RUNNING || Status == STATUS_ARRIVED;
private bool CanReset() => Status == STATUS_FAULT || Status == STATUS_LIMIT;
private async void Loading()
{
try
{
Status = STATUS_INIT;
AddLog("设备初始化...");
await Task.Delay(3000);
Application.Current.Dispatcher.Invoke(() =>
{
if (_halconWinFormsControl?.HalconWindow == null)
{
AddLog("Halcon窗口未初始化");
Status = STATUS_FAULT;
return;
}
//_halconWinFormsControl.HalconWindow.OpenWindow(0, 0, 800, 500, "visible", "");
Status = STATUS_READY;
Rate = 0;
AddLog("初始化完成,设备就绪");
//Rate = 0;
});
//_halconWinFormsControl.HalconID.OpenWindow(0, 0, 800, 500, "visible", "");
//throw new NotImplementedException();
//Application.Current.Dispatcher.Invoke(() =>
//{
// Status = "设备初始化中...";
// //_halconWinFormsControl.HalconWindow.OpenWindow(
// // 0, 0, 800, 500,
// // "visible", "");
// _xRayImage = new HImage();
// });
//Task.Delay(1000).Wait();
//Task.Delay(1000).Wait();
//Task.Delay(1000).Wait();
// Thread.Sleep(3000);
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = "已就绪";
// Rate = 0;
// });
//}}
}
catch (Exception ex)
{
Status = STATUS_FAULT;
AddLog($"初始化异常: {ex.Message}");
// MessageBox.Show(ex.Message + STATUS_FAULT);
}
}
private void ResetAlarm()
{
// throw new NotImplementedException();
StopAxisStatusRefresh();
_scanAxis.ResetAlarm();
Status = STATUS_READY;
Rate = 0;
AddLog("报警已复位,轴已回零");
StartAxisStatusRefresh();
}
private void EmergencyStop()
{
//throw new NotImplementedException();
StopAxisStatusRefresh();
_cts?.Cancel();
_scanAxis.EmergencyStop();
CloseXray();
Status = STATUS_FAULT;
AddLog("【紧急停止】整机已停机");
}
private async void NormalStop()
{
//throw new NotImplementedException();
StopAxisStatusRefresh();
_cts?.Cancel();
await Task.Delay(100);
_scanAxis.NormalStop();
CloseXray();
Status = STATUS_STOP;
Rate = 0;
AddLog("正常停止");
CleanupCts();
StartAxisStatusRefresh();
}
private void SimulateXrayHardwareFault()
{
if (Status != STATUS_FAULT)
{
AddLog("请先启动扫描,再模拟故障");
return;
}
AddLog("[硬件故障]x光发射单元异常,停止发射!");
EmergencyStop();
}
private async Task RunScan(Func<CancellationToken, Task> scanLogic)
{
//throw new NotImplementedException();
if (_cts != null) return;
_cts = new CancellationTokenSource();
var token = _cts.Token;
try
{
Application.Current.Dispatcher.Invoke(() =>
{
Status = STATUS_RUNNING;
Rate = 0;
_isXrayOpen = true;
});
await scanLogic(token);
}
catch (TaskCanceledException)
{
AddLog("任务已取消");
}
catch (Exception ex)
{
Status = STATUS_FAULT;
AddLog($"运行异常:{ex.Message}");
EmergencyStop();
}
finally
{
CloseXray();
CleanupCts();
}
}
private void AddLog(string msg)
{
// throw new NotImplementedException();
Application.Current.Dispatcher.Invoke(() =>
LogList.Insert(0, $"[{DateTime.Now:HH:mm:ss}]{msg}")
);
}
private void CleanupCts()
{
// throw new NotImplementedException();
_cts?.Dispose();
_cts = null;
}
private void CloseXray()
{
// throw new NotImplementedException();
_isXrayOpen = false;
}
public void ShutdownAllResources()
{
AddLog("程序退出,开始释放所有资源...");
EmergencyStop();
StopAxisStatusRefresh();
CleanupCts();
Application.Current.Dispatcher.Invoke(() =>
{
_halconWinFormsControl?.HalconWindow?.ClearWindow();
});
AddLog("所有资源已释放,安全退出!");
}
}
}
BASE
csharp
using HalconDotNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WpfApp6
{
public class XYScanAxis
{
public enum AxisWorkStatus
{
Idle,
Homing,
Moving,
Scanning,
Arrived,
LimitAlarm,
Fault
}
public const double X_MAX_LIMIT = 100.0;
public AxisWorkStatus CurrentStatus { get;private set; }
public double Posx { get; private set; }
public double Posy { get; private set; }
public XYScanAxis()
{
CurrentStatus = AxisWorkStatus.Idle;
Posx = 0;
Posy = 0;
}
public async Task HomeAsync(CancellationToken token)
{
if (CurrentStatus != AxisWorkStatus.Idle) return;
CurrentStatus = AxisWorkStatus.Homing;
await Task.Delay(1500, token);
if (token.IsCancellationRequested || CheckLimit())
{
CurrentStatus = CheckLimit()?AxisWorkStatus.LimitAlarm: AxisWorkStatus.Fault;
return;
}
Posx = 0;
Posy = 0;
CurrentStatus = AxisWorkStatus.Idle;
}
public async Task MoveToAsync(double targetX, double targetY, CancellationToken token)
{
if (CurrentStatus != AxisWorkStatus.Idle) return;
CurrentStatus = AxisWorkStatus.Moving;
await Task.Delay(1000,token);
if (token.IsCancellationRequested||CheckLimit()) {
CurrentStatus=CheckLimit()?AxisWorkStatus.LimitAlarm: AxisWorkStatus.Fault;
return;
}
Posx = targetX;
Posy = targetY;
CurrentStatus = AxisWorkStatus.Arrived;
}
public async Task StartContinueScanAsync(double scanStep,CancellationToken token)
{
if (CurrentStatus != AxisWorkStatus.Idle) return;
CurrentStatus = AxisWorkStatus.Scanning;
while (!token.IsCancellationRequested)
{
if (CheckLimit()) { CurrentStatus = AxisWorkStatus.LimitAlarm;
break;
}
Posx += scanStep;
await Task.Delay(200,token);
}
if(CurrentStatus != AxisWorkStatus.LimitAlarm)
CurrentStatus = AxisWorkStatus.Idle;
}
public void NormalStop()
{
if(CurrentStatus == AxisWorkStatus.Moving || CurrentStatus==AxisWorkStatus.Scanning||CurrentStatus==AxisWorkStatus.Homing)
{
CurrentStatus = AxisWorkStatus.Idle;
}
}
public void EmergencyStop()
{
CurrentStatus = AxisWorkStatus.Fault;
}
public bool CheckLimit() => Posx > X_MAX_LIMIT;
public void ResetAlarm()
{
if(CurrentStatus==AxisWorkStatus.LimitAlarm || CurrentStatus == AxisWorkStatus.Fault)
{
Posx = 0;
CurrentStatus = AxisWorkStatus.Idle;
}
}
}
}
csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp6
{
public class Inotifybase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName=null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WpfApp6
{
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute)) ;
_canExecute = canExecute ?? (() => true);
}
public RelayCommand(Action execute)
{
_execute = execute ?? throw new ArgumentNullException( nameof(execute)) ;
_canExecute =(() => true);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value;}
}
public bool CanExecute(object parameter) => _canExecute();
//{
// throw new NotImplementedException();
//}
public void Execute(object parameter) => _execute();
//{
// throw new NotImplementedException();
//}
}
}
