529: XYScanAxis()类

_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();
        //}
    }
}
相关推荐
czhc11400756631 天前
528:Halcon图像控件 启动轴状态实时监控
mvvm·视觉·运控
czhc11400756632 天前
5.27 :工业设备检测模式与安全防护详解
安全·mvvm·视觉·运控
czhc11400756634 天前
525:检测完善
mvvm·视觉
学不懂飞行器4 天前
【电赛保姆级教程】电赛视觉怎么选?怎么调?从OpenMV到边缘计算硬核避坑指南(附高鲁棒通信源码)
人工智能·stm32·边缘计算·电赛·视觉
czhc11400756635 天前
2026.5.24 检测流程mvvm
mvvm
czhc11400756636 天前
523:MVVM 检测业务
视觉
czhc11400756637 天前
ICommand 视觉、运控522
视觉
czhc11400756638 天前
视觉521 halcon24.05 测试
视觉
时光追逐者20 天前
2026 年 .NET 客户端常用 MVVM 框架推荐
c#·.net·mvvm·.net core