树形节点图标直接复用项目现有图标资源;调整CNC编辑界面大小
This commit is contained in:
@@ -205,6 +205,32 @@ namespace XplorePlane.Services.Cnc
|
||||
return updated;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CncProgram UpdateNode(CncProgram program, int index, CncNode node)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(program);
|
||||
ArgumentNullException.ThrowIfNull(node);
|
||||
|
||||
if (index < 0 || index >= program.Nodes.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index),
|
||||
$"Index out of range: {index}, Count={program.Nodes.Count}");
|
||||
|
||||
var nodes = new List<CncNode>(program.Nodes)
|
||||
{
|
||||
[index] = node with { Index = index }
|
||||
};
|
||||
|
||||
var updated = program with
|
||||
{
|
||||
Nodes = nodes.AsReadOnly(),
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_logger.Info("Updated node: Index={Index}, Type={NodeType}, Program={ProgramName}",
|
||||
index, node.NodeType, program.Name);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveAsync(CncProgram program, string filePath)
|
||||
{
|
||||
|
||||
@@ -4,36 +4,28 @@ using XplorePlane.Models;
|
||||
namespace XplorePlane.Services.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// CNC 程序管理服务接口,负责程序的创建、节点编辑、序列化/反序列化和文件读写
|
||||
/// CNC program management service interface for creation, node editing, serialization and file I/O
|
||||
/// CNC program management service interface.
|
||||
/// </summary>
|
||||
public interface ICncProgramService
|
||||
{
|
||||
/// <summary>创建空的 CNC 程序 | Create an empty CNC program</summary>
|
||||
CncProgram CreateProgram(string name);
|
||||
|
||||
/// <summary>根据节点类型创建节点(从 IAppStateService 捕获设备状态)| Create a node by type (captures device state from IAppStateService)</summary>
|
||||
CncNode CreateNode(CncNodeType type);
|
||||
|
||||
/// <summary>在指定索引之后插入节点并重新编号 | Insert a node after the given index and renumber</summary>
|
||||
CncProgram InsertNode(CncProgram program, int afterIndex, CncNode node);
|
||||
|
||||
/// <summary>移除指定索引的节点并重新编号 | Remove the node at the given index and renumber</summary>
|
||||
CncProgram RemoveNode(CncProgram program, int index);
|
||||
|
||||
/// <summary>将节点从旧索引移动到新索引并重新编号 | Move a node from old index to new index and renumber</summary>
|
||||
CncProgram MoveNode(CncProgram program, int oldIndex, int newIndex);
|
||||
|
||||
/// <summary>将 CNC 程序保存到 .xp 文件 | Save CNC program to .xp file</summary>
|
||||
CncProgram UpdateNode(CncProgram program, int index, CncNode node);
|
||||
|
||||
Task SaveAsync(CncProgram program, string filePath);
|
||||
|
||||
/// <summary>从 .xp 文件加载 CNC 程序 | Load CNC program from .xp file</summary>
|
||||
Task<CncProgram> LoadAsync(string filePath);
|
||||
|
||||
/// <summary>将 CNC 程序序列化为 JSON 字符串 | Serialize CNC program to JSON string</summary>
|
||||
string Serialize(CncProgram program);
|
||||
|
||||
/// <summary>从 JSON 字符串反序列化 CNC 程序 | Deserialize CNC program from JSON string</summary>
|
||||
CncProgram Deserialize(string json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -18,27 +19,21 @@ using XplorePlane.Services.Cnc;
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// CNC 编辑器 ViewModel,管理 CNC 程序的节点列表、编辑操作和文件操作
|
||||
/// CNC editor ViewModel that manages the node list, editing operations and file operations
|
||||
/// CNC editor ViewModel that manages the node tree, editing operations and file operations.
|
||||
/// </summary>
|
||||
public class CncEditorViewModel : BindableBase
|
||||
{
|
||||
private readonly ICncProgramService _cncProgramService;
|
||||
private readonly IAppStateService _appStateService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
// 当前 CNC 程序 | Current CNC program
|
||||
private CncProgram _currentProgram;
|
||||
|
||||
private ObservableCollection<CncNodeViewModel> _nodes;
|
||||
private ObservableCollection<CncNodeViewModel> _treeNodes;
|
||||
private CncNodeViewModel _selectedNode;
|
||||
private bool _isModified;
|
||||
private string _programName;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public CncEditorViewModel(
|
||||
ICncProgramService cncProgramService,
|
||||
IAppStateService appStateService,
|
||||
@@ -46,13 +41,13 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
ILoggerService logger)
|
||||
{
|
||||
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
|
||||
_appStateService = appStateService ?? throw new ArgumentNullException(nameof(appStateService));
|
||||
ArgumentNullException.ThrowIfNull(appStateService);
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<CncEditorViewModel>();
|
||||
|
||||
_nodes = new ObservableCollection<CncNodeViewModel>();
|
||||
_treeNodes = new ObservableCollection<CncNodeViewModel>();
|
||||
|
||||
// ── 节点插入命令 | Node insertion commands ──
|
||||
InsertReferencePointCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.ReferencePoint));
|
||||
InsertSaveNodeWithImageCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.SaveNodeWithImage));
|
||||
InsertSaveNodeCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.SaveNode));
|
||||
@@ -63,116 +58,78 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
InsertWaitDelayCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.WaitDelay));
|
||||
InsertCompleteProgramCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.CompleteProgram));
|
||||
|
||||
// ── 节点编辑命令 | Node editing commands ──
|
||||
DeleteNodeCommand = new DelegateCommand(ExecuteDeleteNode, CanExecuteDeleteNode)
|
||||
.ObservesProperty(() => SelectedNode);
|
||||
MoveNodeUpCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeUp);
|
||||
MoveNodeDownCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeDown);
|
||||
|
||||
// ── 文件操作命令 | File operation commands ──
|
||||
SaveProgramCommand = new DelegateCommand(async () => await ExecuteSaveProgramAsync());
|
||||
LoadProgramCommand = new DelegateCommand(async () => await ExecuteLoadProgramAsync());
|
||||
NewProgramCommand = new DelegateCommand(ExecuteNewProgram);
|
||||
ExportCsvCommand = new DelegateCommand(ExecuteExportCsv);
|
||||
|
||||
_logger.Info("CncEditorViewModel 已初始化 | CncEditorViewModel initialized");
|
||||
_logger.Info("CncEditorViewModel initialized");
|
||||
}
|
||||
|
||||
// ── 属性 | Properties ──────────────────────────────────────────
|
||||
|
||||
/// <summary>节点列表 | Node list</summary>
|
||||
public ObservableCollection<CncNodeViewModel> Nodes
|
||||
{
|
||||
get => _nodes;
|
||||
set => SetProperty(ref _nodes, value);
|
||||
private set => SetProperty(ref _nodes, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<CncNodeViewModel> TreeNodes
|
||||
{
|
||||
get => _treeNodes;
|
||||
private set => SetProperty(ref _treeNodes, value);
|
||||
}
|
||||
|
||||
/// <summary>当前选中的节点 | Currently selected node</summary>
|
||||
public CncNodeViewModel SelectedNode
|
||||
{
|
||||
get => _selectedNode;
|
||||
set => SetProperty(ref _selectedNode, value);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedNode, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(HasSelection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>程序是否已修改 | Whether the program has been modified</summary>
|
||||
public bool HasSelection => SelectedNode != null;
|
||||
|
||||
public bool IsModified
|
||||
{
|
||||
get => _isModified;
|
||||
set => SetProperty(ref _isModified, value);
|
||||
}
|
||||
|
||||
/// <summary>当前程序名称 | Current program name</summary>
|
||||
public string ProgramName
|
||||
{
|
||||
get => _programName;
|
||||
set => SetProperty(ref _programName, value);
|
||||
}
|
||||
|
||||
// ── 节点插入命令 | Node insertion commands ──────────────────────
|
||||
|
||||
/// <summary>插入参考点命令 | Insert reference point command</summary>
|
||||
public DelegateCommand InsertReferencePointCommand { get; }
|
||||
|
||||
/// <summary>插入保存节点(含图像)命令 | Insert save node with image command</summary>
|
||||
public DelegateCommand InsertSaveNodeWithImageCommand { get; }
|
||||
|
||||
/// <summary>插入保存节点命令 | Insert save node command</summary>
|
||||
public DelegateCommand InsertSaveNodeCommand { get; }
|
||||
|
||||
/// <summary>插入保存位置命令 | Insert save position command</summary>
|
||||
public DelegateCommand InsertSavePositionCommand { get; }
|
||||
|
||||
/// <summary>插入检测模块命令 | Insert inspection module command</summary>
|
||||
public DelegateCommand InsertInspectionModuleCommand { get; }
|
||||
|
||||
/// <summary>插入检测标记命令 | Insert inspection marker command</summary>
|
||||
public DelegateCommand InsertInspectionMarkerCommand { get; }
|
||||
|
||||
/// <summary>插入停顿对话框命令 | Insert pause dialog command</summary>
|
||||
public DelegateCommand InsertPauseDialogCommand { get; }
|
||||
|
||||
/// <summary>插入等待延时命令 | Insert wait delay command</summary>
|
||||
public DelegateCommand InsertWaitDelayCommand { get; }
|
||||
|
||||
/// <summary>插入完成程序命令 | Insert complete program command</summary>
|
||||
public DelegateCommand InsertCompleteProgramCommand { get; }
|
||||
|
||||
// ── 节点编辑命令 | Node editing commands ────────────────────────
|
||||
|
||||
/// <summary>删除选中节点命令 | Delete selected node command</summary>
|
||||
public DelegateCommand DeleteNodeCommand { get; }
|
||||
|
||||
/// <summary>上移节点命令 | Move node up command</summary>
|
||||
public DelegateCommand<CncNodeViewModel> MoveNodeUpCommand { get; }
|
||||
|
||||
/// <summary>下移节点命令 | Move node down command</summary>
|
||||
public DelegateCommand<CncNodeViewModel> MoveNodeDownCommand { get; }
|
||||
|
||||
// ── 文件操作命令 | File operation commands ──────────────────────
|
||||
|
||||
/// <summary>保存程序命令 | Save program command</summary>
|
||||
public DelegateCommand SaveProgramCommand { get; }
|
||||
|
||||
/// <summary>加载程序命令 | Load program command</summary>
|
||||
public DelegateCommand LoadProgramCommand { get; }
|
||||
|
||||
/// <summary>新建程序命令 | New program command</summary>
|
||||
public DelegateCommand NewProgramCommand { get; }
|
||||
|
||||
/// <summary>导出 CSV 命令 | Export CSV command</summary>
|
||||
public DelegateCommand ExportCsvCommand { get; }
|
||||
|
||||
// ── 命令执行方法 | Command execution methods ────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 插入指定类型的节点到选中节点之后
|
||||
/// Insert a node of the specified type after the selected node
|
||||
/// </summary>
|
||||
private void ExecuteInsertNode(CncNodeType nodeType)
|
||||
{
|
||||
if (_currentProgram == null)
|
||||
{
|
||||
_logger.Warn("无法插入节点:当前无程序 | Cannot insert node: no current program");
|
||||
ExecuteNewProgram();
|
||||
}
|
||||
|
||||
@@ -183,18 +140,14 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node);
|
||||
|
||||
OnProgramEdited();
|
||||
_logger.Info("已插入节点 | Inserted node: Type={NodeType}", nodeType);
|
||||
_logger.Info("Inserted node: Type={NodeType}", nodeType);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
// 重复插入 CompleteProgram 等业务规则异常 | Business rule exceptions like duplicate CompleteProgram
|
||||
_logger.Warn("插入节点被阻止 | Node insertion blocked: {Message}", ex.Message);
|
||||
_logger.Warn("Node insertion blocked: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除选中节点 | Delete the selected node
|
||||
/// </summary>
|
||||
private void ExecuteDeleteNode()
|
||||
{
|
||||
if (_currentProgram == null || SelectedNode == null)
|
||||
@@ -204,18 +157,14 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, SelectedNode.Index);
|
||||
OnProgramEdited();
|
||||
_logger.Info("已删除节点 | Deleted node at index: {Index}", SelectedNode.Index);
|
||||
_logger.Info("Deleted node at index: {Index}", SelectedNode.Index);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
_logger.Warn("删除节点失败 | Delete node failed: {Message}", ex.Message);
|
||||
_logger.Warn("Delete node failed: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以删除节点(至少保留 1 个节点)
|
||||
/// Determines whether delete is allowed (at least 1 node must remain)
|
||||
/// </summary>
|
||||
private bool CanExecuteDeleteNode()
|
||||
{
|
||||
return SelectedNode != null
|
||||
@@ -223,9 +172,6 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
&& _currentProgram.Nodes.Count > 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上移节点 | Move node up
|
||||
/// </summary>
|
||||
private void ExecuteMoveNodeUp(CncNodeViewModel nodeVm)
|
||||
{
|
||||
if (_currentProgram == null || nodeVm == null || nodeVm.Index <= 0)
|
||||
@@ -235,17 +181,13 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index - 1);
|
||||
OnProgramEdited();
|
||||
_logger.Info("已上移节点 | Moved node up: {OldIndex} -> {NewIndex}", nodeVm.Index, nodeVm.Index - 1);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
_logger.Warn("上移节点失败 | Move node up failed: {Message}", ex.Message);
|
||||
_logger.Warn("Move node up failed: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下移节点 | Move node down
|
||||
/// </summary>
|
||||
private void ExecuteMoveNodeDown(CncNodeViewModel nodeVm)
|
||||
{
|
||||
if (_currentProgram == null || nodeVm == null || nodeVm.Index >= _currentProgram.Nodes.Count - 1)
|
||||
@@ -255,22 +197,18 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index + 1);
|
||||
OnProgramEdited();
|
||||
_logger.Info("已下移节点 | Moved node down: {OldIndex} -> {NewIndex}", nodeVm.Index, nodeVm.Index + 1);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
_logger.Warn("下移节点失败 | Move node down failed: {Message}", ex.Message);
|
||||
_logger.Warn("Move node down failed: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前程序到文件 | Save current program to file
|
||||
/// </summary>
|
||||
private async Task ExecuteSaveProgramAsync()
|
||||
{
|
||||
if (_currentProgram == null)
|
||||
{
|
||||
_logger.Warn("无法保存:当前无程序 | Cannot save: no current program");
|
||||
_logger.Warn("Cannot save: no current program");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -289,17 +227,13 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
|
||||
await _cncProgramService.SaveAsync(_currentProgram, dlg.FileName);
|
||||
IsModified = false;
|
||||
_logger.Info("程序已保存 | Program saved: {FilePath}", dlg.FileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "保存程序失败 | Failed to save program");
|
||||
_logger.Error(ex, "Failed to save program");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载程序 | Load program from file
|
||||
/// </summary>
|
||||
private async Task ExecuteLoadProgramAsync()
|
||||
{
|
||||
try
|
||||
@@ -318,35 +252,27 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
ProgramName = _currentProgram.Name;
|
||||
IsModified = false;
|
||||
RefreshNodes();
|
||||
_logger.Info("程序已加载 | Program loaded: {ProgramName}", _currentProgram.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "加载程序失败 | Failed to load program");
|
||||
_logger.Error(ex, "Failed to load program");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建新程序 | Create a new program
|
||||
/// </summary>
|
||||
private void ExecuteNewProgram()
|
||||
{
|
||||
var name = string.IsNullOrWhiteSpace(ProgramName) ? "新程序" : ProgramName;
|
||||
var name = string.IsNullOrWhiteSpace(ProgramName) ? "NewCncProgram" : ProgramName;
|
||||
_currentProgram = _cncProgramService.CreateProgram(name);
|
||||
ProgramName = _currentProgram.Name;
|
||||
IsModified = false;
|
||||
RefreshNodes();
|
||||
_logger.Info("已创建新程序 | Created new program: {ProgramName}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出当前程序为 CSV 文件 | Export current program to CSV file
|
||||
/// </summary>
|
||||
private void ExecuteExportCsv()
|
||||
{
|
||||
if (_currentProgram == null || _currentProgram.Nodes.Count == 0)
|
||||
{
|
||||
_logger.Warn("无法导出 CSV:当前无程序或节点为空 | Cannot export CSV: no program or empty nodes");
|
||||
_logger.Warn("Cannot export CSV: no program or empty nodes");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -364,7 +290,6 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
// CSV 表头 | CSV header
|
||||
sb.AppendLine("Index,NodeType,Name,XM,YM,ZT,ZD,TiltD,Dist,Voltage_kV,Current_uA,Power_W,RayOn,DetectorConnected,FrameRate,Resolution,ImageFile,MarkerType,MarkerX,MarkerY,DialogTitle,DialogMessage,DelayMs,Pipeline");
|
||||
|
||||
var inv = CultureInfo.InvariantCulture;
|
||||
@@ -374,23 +299,14 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
var row = node switch
|
||||
{
|
||||
ReferencePointNode rp => $"{rp.Index},{rp.NodeType},{Esc(rp.Name)},{rp.XM.ToString(inv)},{rp.YM.ToString(inv)},{rp.ZT.ToString(inv)},{rp.ZD.ToString(inv)},{rp.TiltD.ToString(inv)},{rp.Dist.ToString(inv)},{rp.Voltage.ToString(inv)},{rp.Current.ToString(inv)},,{rp.IsRayOn},,,,,,,,,,",
|
||||
|
||||
SaveNodeWithImageNode sni => $"{sni.Index},{sni.NodeType},{Esc(sni.Name)},{sni.MotionState.XM.ToString(inv)},{sni.MotionState.YM.ToString(inv)},{sni.MotionState.ZT.ToString(inv)},{sni.MotionState.ZD.ToString(inv)},{sni.MotionState.TiltD.ToString(inv)},{sni.MotionState.Dist.ToString(inv)},{sni.RaySourceState.Voltage.ToString(inv)},,{sni.RaySourceState.Power.ToString(inv)},{sni.RaySourceState.IsOn},{sni.DetectorState.IsConnected},{sni.DetectorState.FrameRate.ToString(inv)},{Esc(sni.DetectorState.Resolution)},{Esc(sni.ImageFileName)},,,,,,",
|
||||
|
||||
SaveNodeNode sn => $"{sn.Index},{sn.NodeType},{Esc(sn.Name)},{sn.MotionState.XM.ToString(inv)},{sn.MotionState.YM.ToString(inv)},{sn.MotionState.ZT.ToString(inv)},{sn.MotionState.ZD.ToString(inv)},{sn.MotionState.TiltD.ToString(inv)},{sn.MotionState.Dist.ToString(inv)},{sn.RaySourceState.Voltage.ToString(inv)},,{sn.RaySourceState.Power.ToString(inv)},{sn.RaySourceState.IsOn},{sn.DetectorState.IsConnected},{sn.DetectorState.FrameRate.ToString(inv)},{Esc(sn.DetectorState.Resolution)},,,,,,,",
|
||||
|
||||
SavePositionNode sp => $"{sp.Index},{sp.NodeType},{Esc(sp.Name)},{sp.MotionState.XM.ToString(inv)},{sp.MotionState.YM.ToString(inv)},{sp.MotionState.ZT.ToString(inv)},{sp.MotionState.ZD.ToString(inv)},{sp.MotionState.TiltD.ToString(inv)},{sp.MotionState.Dist.ToString(inv)},,,,,,,,,,,,,,",
|
||||
|
||||
InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}",
|
||||
|
||||
InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,",
|
||||
|
||||
PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
|
||||
|
||||
WaitDelayNode wd => $"{wd.Index},{wd.NodeType},{Esc(wd.Name)},,,,,,,,,,,,,,,,,,,{wd.DelayMilliseconds},",
|
||||
|
||||
CompleteProgramNode cp => $"{cp.Index},{cp.NodeType},{Esc(cp.Name)},,,,,,,,,,,,,,,,,,,,",
|
||||
|
||||
_ => $"{node.Index},{node.NodeType},{Esc(node.Name)},,,,,,,,,,,,,,,,,,,,"
|
||||
};
|
||||
|
||||
@@ -398,18 +314,13 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
}
|
||||
|
||||
File.WriteAllText(dlg.FileName, sb.ToString(), Encoding.UTF8);
|
||||
_logger.Info("CSV 已导出 | CSV exported: {FilePath}, 节点数={Count}", dlg.FileName, _currentProgram.Nodes.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "导出 CSV 失败 | Failed to export CSV");
|
||||
_logger.Error(ex, "Failed to export CSV");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CSV 字段转义:含逗号、引号或换行时用双引号包裹
|
||||
/// Escape CSV field: wrap with double quotes if it contains comma, quote, or newline
|
||||
/// </summary>
|
||||
private static string Esc(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return string.Empty;
|
||||
@@ -418,12 +329,6 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
return value;
|
||||
}
|
||||
|
||||
// ── 辅助方法 | Helper methods ───────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 编辑操作后的统一处理:刷新节点、标记已修改、发布变更事件
|
||||
/// Unified post-edit handling: refresh nodes, mark modified, publish change event
|
||||
/// </summary>
|
||||
private void OnProgramEdited()
|
||||
{
|
||||
IsModified = true;
|
||||
@@ -431,34 +336,70 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
PublishProgramChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 _currentProgram.Nodes 重建 Nodes 集合
|
||||
/// Rebuild the Nodes collection from _currentProgram.Nodes
|
||||
/// </summary>
|
||||
private void RefreshNodes()
|
||||
private void HandleNodeModelChanged(CncNodeViewModel nodeVm, CncNode updatedNode)
|
||||
{
|
||||
Nodes.Clear();
|
||||
|
||||
if (_currentProgram?.Nodes == null)
|
||||
if (_currentProgram == null)
|
||||
return;
|
||||
|
||||
foreach (var node in _currentProgram.Nodes)
|
||||
{
|
||||
Nodes.Add(new CncNodeViewModel(node));
|
||||
}
|
||||
|
||||
// 尝试保持选中状态 | Try to preserve selection
|
||||
if (SelectedNode != null)
|
||||
{
|
||||
var match = Nodes.FirstOrDefault(n => n.Index == SelectedNode.Index);
|
||||
SelectedNode = match ?? Nodes.LastOrDefault();
|
||||
}
|
||||
_currentProgram = _cncProgramService.UpdateNode(_currentProgram, nodeVm.Index, updatedNode);
|
||||
IsModified = true;
|
||||
ProgramName = _currentProgram.Name;
|
||||
PublishProgramChanged();
|
||||
}
|
||||
|
||||
private void RefreshNodes()
|
||||
{
|
||||
var selectedId = SelectedNode?.Id;
|
||||
var expansionState = Nodes.ToDictionary(node => node.Id, node => node.IsExpanded);
|
||||
|
||||
var flatNodes = new List<CncNodeViewModel>();
|
||||
var rootNodes = new List<CncNodeViewModel>();
|
||||
CncNodeViewModel currentModule = null;
|
||||
|
||||
if (_currentProgram?.Nodes != null)
|
||||
{
|
||||
foreach (var node in _currentProgram.Nodes)
|
||||
{
|
||||
var vm = new CncNodeViewModel(node, HandleNodeModelChanged)
|
||||
{
|
||||
IsExpanded = expansionState.TryGetValue(node.Id, out var isExpanded) ? isExpanded : true
|
||||
};
|
||||
|
||||
flatNodes.Add(vm);
|
||||
|
||||
if (vm.IsInspectionModule)
|
||||
{
|
||||
rootNodes.Add(vm);
|
||||
currentModule = vm;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentModule != null && IsModuleChild(vm.NodeType))
|
||||
{
|
||||
currentModule.Children.Add(vm);
|
||||
continue;
|
||||
}
|
||||
|
||||
rootNodes.Add(vm);
|
||||
currentModule = null;
|
||||
}
|
||||
}
|
||||
|
||||
Nodes = new ObservableCollection<CncNodeViewModel>(flatNodes);
|
||||
TreeNodes = new ObservableCollection<CncNodeViewModel>(rootNodes);
|
||||
|
||||
SelectedNode = selectedId.HasValue
|
||||
? Nodes.FirstOrDefault(node => node.Id == selectedId.Value) ?? Nodes.LastOrDefault()
|
||||
: Nodes.LastOrDefault();
|
||||
}
|
||||
|
||||
private static bool IsModuleChild(CncNodeType type)
|
||||
{
|
||||
return type is CncNodeType.InspectionMarker
|
||||
or CncNodeType.PauseDialog
|
||||
or CncNodeType.WaitDelay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 IEventAggregator 发布 CNC 程序变更事件
|
||||
/// Publish CNC program changed event via IEventAggregator
|
||||
/// </summary>
|
||||
private void PublishProgramChanged()
|
||||
{
|
||||
_eventAggregator
|
||||
|
||||
@@ -1,89 +1,530 @@
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using XplorePlane.Models;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// CNC 节点 ViewModel,将 CncNode 模型封装为可绑定的 WPF ViewModel
|
||||
/// CNC node ViewModel that wraps a CncNode model into a bindable WPF ViewModel
|
||||
/// CNC node ViewModel with editable properties and tree children.
|
||||
/// </summary>
|
||||
public class CncNodeViewModel : BindableBase
|
||||
{
|
||||
private int _index;
|
||||
private string _name;
|
||||
private CncNodeType _nodeType;
|
||||
private readonly Action<CncNodeViewModel, CncNode> _modelChangedCallback;
|
||||
private CncNode _model;
|
||||
private string _icon;
|
||||
private bool _isExpanded = true;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,从 CncNode 模型初始化 ViewModel
|
||||
/// Constructor that initializes the ViewModel from a CncNode model
|
||||
/// </summary>
|
||||
public CncNodeViewModel(CncNode model)
|
||||
public CncNodeViewModel(CncNode model, Action<CncNodeViewModel, CncNode> modelChangedCallback)
|
||||
{
|
||||
Model = model;
|
||||
_index = model.Index;
|
||||
_name = model.Name;
|
||||
_nodeType = model.NodeType;
|
||||
_model = model ?? throw new ArgumentNullException(nameof(model));
|
||||
_modelChangedCallback = modelChangedCallback ?? throw new ArgumentNullException(nameof(modelChangedCallback));
|
||||
_icon = GetIconForNodeType(model.NodeType);
|
||||
Children = new ObservableCollection<CncNodeViewModel>();
|
||||
}
|
||||
|
||||
/// <summary>底层 CNC 节点模型(只读)| Underlying CNC node model (read-only)</summary>
|
||||
public CncNode Model { get; }
|
||||
public ObservableCollection<CncNodeViewModel> Children { get; }
|
||||
|
||||
/// <summary>节点在程序中的索引 | Node index in the program</summary>
|
||||
public int Index
|
||||
{
|
||||
get => _index;
|
||||
set => SetProperty(ref _index, value);
|
||||
}
|
||||
public CncNode Model => _model;
|
||||
|
||||
public Guid Id => _model.Id;
|
||||
|
||||
public int Index => _model.Index;
|
||||
|
||||
/// <summary>节点显示名称 | Node display name</summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, value);
|
||||
get => _model.Name;
|
||||
set => UpdateModel(_model with { Name = value ?? string.Empty });
|
||||
}
|
||||
|
||||
/// <summary>节点类型 | Node type</summary>
|
||||
public CncNodeType NodeType
|
||||
public CncNodeType NodeType => _model.NodeType;
|
||||
|
||||
public string NodeTypeDisplay => NodeType.ToString();
|
||||
|
||||
public string Icon
|
||||
{
|
||||
get => _nodeType;
|
||||
get => _icon;
|
||||
private set => SetProperty(ref _icon, value);
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public bool HasChildren => Children.Count > 0;
|
||||
|
||||
public bool IsReferencePoint => _model is ReferencePointNode;
|
||||
public bool IsSaveNode => _model is SaveNodeNode;
|
||||
public bool IsSaveNodeWithImage => _model is SaveNodeWithImageNode;
|
||||
public bool IsSavePosition => _model is SavePositionNode;
|
||||
public bool IsInspectionModule => _model is InspectionModuleNode;
|
||||
public bool IsInspectionMarker => _model is InspectionMarkerNode;
|
||||
public bool IsPauseDialog => _model is PauseDialogNode;
|
||||
public bool IsWaitDelay => _model is WaitDelayNode;
|
||||
public bool IsCompleteProgram => _model is CompleteProgramNode;
|
||||
public bool IsMotionSnapshotNode => _model is ReferencePointNode or SaveNodeNode or SaveNodeWithImageNode or SavePositionNode;
|
||||
|
||||
public double XM
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
ReferencePointNode rp => rp.XM,
|
||||
SaveNodeNode sn => sn.MotionState.XM,
|
||||
SaveNodeWithImageNode sni => sni.MotionState.XM,
|
||||
SavePositionNode sp => sp.MotionState.XM,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateMotion(value, MotionAxis.XM);
|
||||
}
|
||||
|
||||
public double YM
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
ReferencePointNode rp => rp.YM,
|
||||
SaveNodeNode sn => sn.MotionState.YM,
|
||||
SaveNodeWithImageNode sni => sni.MotionState.YM,
|
||||
SavePositionNode sp => sp.MotionState.YM,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateMotion(value, MotionAxis.YM);
|
||||
}
|
||||
|
||||
public double ZT
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
ReferencePointNode rp => rp.ZT,
|
||||
SaveNodeNode sn => sn.MotionState.ZT,
|
||||
SaveNodeWithImageNode sni => sni.MotionState.ZT,
|
||||
SavePositionNode sp => sp.MotionState.ZT,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateMotion(value, MotionAxis.ZT);
|
||||
}
|
||||
|
||||
public double ZD
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
ReferencePointNode rp => rp.ZD,
|
||||
SaveNodeNode sn => sn.MotionState.ZD,
|
||||
SaveNodeWithImageNode sni => sni.MotionState.ZD,
|
||||
SavePositionNode sp => sp.MotionState.ZD,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateMotion(value, MotionAxis.ZD);
|
||||
}
|
||||
|
||||
public double TiltD
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
ReferencePointNode rp => rp.TiltD,
|
||||
SaveNodeNode sn => sn.MotionState.TiltD,
|
||||
SaveNodeWithImageNode sni => sni.MotionState.TiltD,
|
||||
SavePositionNode sp => sp.MotionState.TiltD,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateMotion(value, MotionAxis.TiltD);
|
||||
}
|
||||
|
||||
public double Dist
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
ReferencePointNode rp => rp.Dist,
|
||||
SaveNodeNode sn => sn.MotionState.Dist,
|
||||
SaveNodeWithImageNode sni => sni.MotionState.Dist,
|
||||
SavePositionNode sp => sp.MotionState.Dist,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateMotion(value, MotionAxis.Dist);
|
||||
}
|
||||
|
||||
public bool IsRayOn
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
ReferencePointNode rp => rp.IsRayOn,
|
||||
SaveNodeNode sn => sn.RaySourceState.IsOn,
|
||||
SaveNodeWithImageNode sni => sni.RaySourceState.IsOn,
|
||||
_ => false
|
||||
};
|
||||
set => UpdateRaySource(isOn: value);
|
||||
}
|
||||
|
||||
public double Voltage
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
ReferencePointNode rp => rp.Voltage,
|
||||
SaveNodeNode sn => sn.RaySourceState.Voltage,
|
||||
SaveNodeWithImageNode sni => sni.RaySourceState.Voltage,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateRaySource(voltage: value);
|
||||
}
|
||||
|
||||
public double Current
|
||||
{
|
||||
get => _model is ReferencePointNode rp ? rp.Current : 0d;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _nodeType, value))
|
||||
if (_model is ReferencePointNode rp)
|
||||
{
|
||||
// 类型变更时自动更新图标 | Auto-update icon when type changes
|
||||
Icon = GetIconForNodeType(value);
|
||||
UpdateModel(rp with { Current = value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>节点图标路径 | Node icon path</summary>
|
||||
public string Icon
|
||||
public double Power
|
||||
{
|
||||
get => _icon;
|
||||
set => SetProperty(ref _icon, value);
|
||||
get => _model switch
|
||||
{
|
||||
SaveNodeNode sn => sn.RaySourceState.Power,
|
||||
SaveNodeWithImageNode sni => sni.RaySourceState.Power,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateRaySource(power: value);
|
||||
}
|
||||
|
||||
public bool DetectorConnected
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
SaveNodeNode sn => sn.DetectorState.IsConnected,
|
||||
SaveNodeWithImageNode sni => sni.DetectorState.IsConnected,
|
||||
_ => false
|
||||
};
|
||||
set => UpdateDetector(isConnected: value);
|
||||
}
|
||||
|
||||
public bool DetectorAcquiring
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
SaveNodeNode sn => sn.DetectorState.IsAcquiring,
|
||||
SaveNodeWithImageNode sni => sni.DetectorState.IsAcquiring,
|
||||
_ => false
|
||||
};
|
||||
set => UpdateDetector(isAcquiring: value);
|
||||
}
|
||||
|
||||
public double FrameRate
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
SaveNodeNode sn => sn.DetectorState.FrameRate,
|
||||
SaveNodeWithImageNode sni => sni.DetectorState.FrameRate,
|
||||
_ => 0d
|
||||
};
|
||||
set => UpdateDetector(frameRate: value);
|
||||
}
|
||||
|
||||
public string Resolution
|
||||
{
|
||||
get => _model switch
|
||||
{
|
||||
SaveNodeNode sn => sn.DetectorState.Resolution,
|
||||
SaveNodeWithImageNode sni => sni.DetectorState.Resolution,
|
||||
_ => string.Empty
|
||||
};
|
||||
set => UpdateDetector(resolution: value ?? string.Empty);
|
||||
}
|
||||
|
||||
public string ImageFileName
|
||||
{
|
||||
get => _model is SaveNodeWithImageNode sni ? sni.ImageFileName : string.Empty;
|
||||
set
|
||||
{
|
||||
if (_model is SaveNodeWithImageNode sni)
|
||||
{
|
||||
UpdateModel(sni with { ImageFileName = value ?? string.Empty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string PipelineName
|
||||
{
|
||||
get => _model is InspectionModuleNode im ? im.Pipeline?.Name ?? string.Empty : string.Empty;
|
||||
set
|
||||
{
|
||||
if (_model is InspectionModuleNode im)
|
||||
{
|
||||
var pipeline = im.Pipeline ?? new PipelineModel();
|
||||
pipeline.Name = value ?? string.Empty;
|
||||
UpdateModel(im with { Pipeline = pipeline });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string MarkerType
|
||||
{
|
||||
get => _model is InspectionMarkerNode mk ? mk.MarkerType : string.Empty;
|
||||
set
|
||||
{
|
||||
if (_model is InspectionMarkerNode mk)
|
||||
{
|
||||
UpdateModel(mk with { MarkerType = value ?? string.Empty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double MarkerX
|
||||
{
|
||||
get => _model is InspectionMarkerNode mk ? mk.MarkerX : 0d;
|
||||
set
|
||||
{
|
||||
if (_model is InspectionMarkerNode mk)
|
||||
{
|
||||
UpdateModel(mk with { MarkerX = value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double MarkerY
|
||||
{
|
||||
get => _model is InspectionMarkerNode mk ? mk.MarkerY : 0d;
|
||||
set
|
||||
{
|
||||
if (_model is InspectionMarkerNode mk)
|
||||
{
|
||||
UpdateModel(mk with { MarkerY = value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DialogTitle
|
||||
{
|
||||
get => _model is PauseDialogNode pd ? pd.DialogTitle : string.Empty;
|
||||
set
|
||||
{
|
||||
if (_model is PauseDialogNode pd)
|
||||
{
|
||||
UpdateModel(pd with { DialogTitle = value ?? string.Empty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DialogMessage
|
||||
{
|
||||
get => _model is PauseDialogNode pd ? pd.DialogMessage : string.Empty;
|
||||
set
|
||||
{
|
||||
if (_model is PauseDialogNode pd)
|
||||
{
|
||||
UpdateModel(pd with { DialogMessage = value ?? string.Empty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int DelayMilliseconds
|
||||
{
|
||||
get => _model is WaitDelayNode wd ? wd.DelayMilliseconds : 0;
|
||||
set
|
||||
{
|
||||
if (_model is WaitDelayNode wd)
|
||||
{
|
||||
UpdateModel(wd with { DelayMilliseconds = value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReplaceModel(CncNode model)
|
||||
{
|
||||
_model = model ?? throw new ArgumentNullException(nameof(model));
|
||||
Icon = GetIconForNodeType(model.NodeType);
|
||||
RaiseAllPropertiesChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据节点类型返回对应的图标路径
|
||||
/// Returns the icon path for the given node type
|
||||
/// </summary>
|
||||
public static string GetIconForNodeType(CncNodeType nodeType)
|
||||
{
|
||||
return nodeType switch
|
||||
{
|
||||
CncNodeType.ReferencePoint => "/Resources/Icons/cnc_reference_point.png",
|
||||
CncNodeType.SaveNodeWithImage => "/Resources/Icons/cnc_save_with_image.png",
|
||||
CncNodeType.SaveNode => "/Resources/Icons/cnc_save_node.png",
|
||||
CncNodeType.SavePosition => "/Resources/Icons/cnc_save_position.png",
|
||||
CncNodeType.InspectionModule => "/Resources/Icons/cnc_inspection_module.png",
|
||||
CncNodeType.InspectionMarker => "/Resources/Icons/cnc_inspection_marker.png",
|
||||
CncNodeType.PauseDialog => "/Resources/Icons/cnc_pause_dialog.png",
|
||||
CncNodeType.WaitDelay => "/Resources/Icons/cnc_wait_delay.png",
|
||||
CncNodeType.CompleteProgram => "/Resources/Icons/cnc_complete_program.png",
|
||||
_ => "/Resources/Icons/cnc_default.png",
|
||||
CncNodeType.ReferencePoint => "/Assets/Icons/reference.png",
|
||||
CncNodeType.SaveNodeWithImage => "/Assets/Icons/saveall.png",
|
||||
CncNodeType.SaveNode => "/Assets/Icons/save.png",
|
||||
CncNodeType.SavePosition => "/Assets/Icons/add-pos.png",
|
||||
CncNodeType.InspectionModule => "/Assets/Icons/Module.png",
|
||||
CncNodeType.InspectionMarker => "/Assets/Icons/mark.png",
|
||||
CncNodeType.PauseDialog => "/Assets/Icons/message.png",
|
||||
CncNodeType.WaitDelay => "/Assets/Icons/wait.png",
|
||||
CncNodeType.CompleteProgram => "/Assets/Icons/finish.png",
|
||||
_ => "/Assets/Icons/cnc.png",
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateMotion(double value, MotionAxis axis)
|
||||
{
|
||||
switch (_model)
|
||||
{
|
||||
case ReferencePointNode rp:
|
||||
UpdateModel(axis switch
|
||||
{
|
||||
MotionAxis.XM => rp with { XM = value },
|
||||
MotionAxis.YM => rp with { YM = value },
|
||||
MotionAxis.ZT => rp with { ZT = value },
|
||||
MotionAxis.ZD => rp with { ZD = value },
|
||||
MotionAxis.TiltD => rp with { TiltD = value },
|
||||
MotionAxis.Dist => rp with { Dist = value },
|
||||
_ => rp
|
||||
});
|
||||
break;
|
||||
case SaveNodeNode sn:
|
||||
UpdateModel(sn with { MotionState = UpdateMotionState(sn.MotionState, axis, value) });
|
||||
break;
|
||||
case SaveNodeWithImageNode sni:
|
||||
UpdateModel(sni with { MotionState = UpdateMotionState(sni.MotionState, axis, value) });
|
||||
break;
|
||||
case SavePositionNode sp:
|
||||
UpdateModel(sp with { MotionState = UpdateMotionState(sp.MotionState, axis, value) });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRaySource(bool? isOn = null, double? voltage = null, double? current = null, double? power = null)
|
||||
{
|
||||
switch (_model)
|
||||
{
|
||||
case ReferencePointNode rp:
|
||||
UpdateModel(rp with
|
||||
{
|
||||
IsRayOn = isOn ?? rp.IsRayOn,
|
||||
Voltage = voltage ?? rp.Voltage,
|
||||
Current = current ?? rp.Current
|
||||
});
|
||||
break;
|
||||
case SaveNodeNode sn:
|
||||
UpdateModel(sn with
|
||||
{
|
||||
RaySourceState = sn.RaySourceState with
|
||||
{
|
||||
IsOn = isOn ?? sn.RaySourceState.IsOn,
|
||||
Voltage = voltage ?? sn.RaySourceState.Voltage,
|
||||
Power = power ?? sn.RaySourceState.Power
|
||||
}
|
||||
});
|
||||
break;
|
||||
case SaveNodeWithImageNode sni:
|
||||
UpdateModel(sni with
|
||||
{
|
||||
RaySourceState = sni.RaySourceState with
|
||||
{
|
||||
IsOn = isOn ?? sni.RaySourceState.IsOn,
|
||||
Voltage = voltage ?? sni.RaySourceState.Voltage,
|
||||
Power = power ?? sni.RaySourceState.Power
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDetector(bool? isConnected = null, bool? isAcquiring = null, double? frameRate = null, string resolution = null)
|
||||
{
|
||||
switch (_model)
|
||||
{
|
||||
case SaveNodeNode sn:
|
||||
UpdateModel(sn with
|
||||
{
|
||||
DetectorState = sn.DetectorState with
|
||||
{
|
||||
IsConnected = isConnected ?? sn.DetectorState.IsConnected,
|
||||
IsAcquiring = isAcquiring ?? sn.DetectorState.IsAcquiring,
|
||||
FrameRate = frameRate ?? sn.DetectorState.FrameRate,
|
||||
Resolution = resolution ?? sn.DetectorState.Resolution
|
||||
}
|
||||
});
|
||||
break;
|
||||
case SaveNodeWithImageNode sni:
|
||||
UpdateModel(sni with
|
||||
{
|
||||
DetectorState = sni.DetectorState with
|
||||
{
|
||||
IsConnected = isConnected ?? sni.DetectorState.IsConnected,
|
||||
IsAcquiring = isAcquiring ?? sni.DetectorState.IsAcquiring,
|
||||
FrameRate = frameRate ?? sni.DetectorState.FrameRate,
|
||||
Resolution = resolution ?? sni.DetectorState.Resolution
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static MotionState UpdateMotionState(MotionState state, MotionAxis axis, double value)
|
||||
{
|
||||
return axis switch
|
||||
{
|
||||
MotionAxis.XM => state with { XM = value },
|
||||
MotionAxis.YM => state with { YM = value },
|
||||
MotionAxis.ZT => state with { ZT = value },
|
||||
MotionAxis.ZD => state with { ZD = value },
|
||||
MotionAxis.TiltD => state with { TiltD = value },
|
||||
MotionAxis.Dist => state with { Dist = value },
|
||||
_ => state
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateModel(CncNode updatedModel)
|
||||
{
|
||||
_model = updatedModel ?? throw new ArgumentNullException(nameof(updatedModel));
|
||||
RaiseAllPropertiesChanged();
|
||||
_modelChangedCallback(this, updatedModel);
|
||||
}
|
||||
|
||||
private void RaiseAllPropertiesChanged()
|
||||
{
|
||||
RaisePropertyChanged(nameof(Model));
|
||||
RaisePropertyChanged(nameof(Id));
|
||||
RaisePropertyChanged(nameof(Index));
|
||||
RaisePropertyChanged(nameof(Name));
|
||||
RaisePropertyChanged(nameof(NodeType));
|
||||
RaisePropertyChanged(nameof(NodeTypeDisplay));
|
||||
RaisePropertyChanged(nameof(Icon));
|
||||
RaisePropertyChanged(nameof(IsReferencePoint));
|
||||
RaisePropertyChanged(nameof(IsSaveNode));
|
||||
RaisePropertyChanged(nameof(IsSaveNodeWithImage));
|
||||
RaisePropertyChanged(nameof(IsSavePosition));
|
||||
RaisePropertyChanged(nameof(IsInspectionModule));
|
||||
RaisePropertyChanged(nameof(IsInspectionMarker));
|
||||
RaisePropertyChanged(nameof(IsPauseDialog));
|
||||
RaisePropertyChanged(nameof(IsWaitDelay));
|
||||
RaisePropertyChanged(nameof(IsCompleteProgram));
|
||||
RaisePropertyChanged(nameof(IsMotionSnapshotNode));
|
||||
RaisePropertyChanged(nameof(XM));
|
||||
RaisePropertyChanged(nameof(YM));
|
||||
RaisePropertyChanged(nameof(ZT));
|
||||
RaisePropertyChanged(nameof(ZD));
|
||||
RaisePropertyChanged(nameof(TiltD));
|
||||
RaisePropertyChanged(nameof(Dist));
|
||||
RaisePropertyChanged(nameof(IsRayOn));
|
||||
RaisePropertyChanged(nameof(Voltage));
|
||||
RaisePropertyChanged(nameof(Current));
|
||||
RaisePropertyChanged(nameof(Power));
|
||||
RaisePropertyChanged(nameof(DetectorConnected));
|
||||
RaisePropertyChanged(nameof(DetectorAcquiring));
|
||||
RaisePropertyChanged(nameof(FrameRate));
|
||||
RaisePropertyChanged(nameof(Resolution));
|
||||
RaisePropertyChanged(nameof(ImageFileName));
|
||||
RaisePropertyChanged(nameof(PipelineName));
|
||||
RaisePropertyChanged(nameof(MarkerType));
|
||||
RaisePropertyChanged(nameof(MarkerX));
|
||||
RaisePropertyChanged(nameof(MarkerY));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(DialogMessage));
|
||||
RaisePropertyChanged(nameof(DelayMilliseconds));
|
||||
}
|
||||
|
||||
private enum MotionAxis
|
||||
{
|
||||
XM,
|
||||
YM,
|
||||
ZT,
|
||||
ZD,
|
||||
TiltD,
|
||||
Dist
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace XplorePlane.ViewModels
|
||||
private void ShowCncEditor()
|
||||
{
|
||||
ImagePanelContent = _cncPageView;
|
||||
ImagePanelWidth = new GridLength(430);
|
||||
ImagePanelWidth = new GridLength(720);
|
||||
_isCncEditorMode = true;
|
||||
_logger.Info("CNC 编辑器已切换到主界面图像区域");
|
||||
}
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
<UserControl
|
||||
<UserControl
|
||||
x:Class="XplorePlane.Views.Cnc.CncPageView"
|
||||
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:local="clr-namespace:XplorePlane.Views.Cnc"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:views="clr-namespace:XplorePlane.Views"
|
||||
d:DesignHeight="700"
|
||||
d:DesignWidth="544"
|
||||
xmlns:vm="clr-namespace:XplorePlane.ViewModels.Cnc"
|
||||
d:DesignHeight="760"
|
||||
d:DesignWidth="1040"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
|
||||
<SolidColorBrush x:Key="PanelBg" Color="White" />
|
||||
<SolidColorBrush x:Key="PanelBorder" Color="#cdcbcb" />
|
||||
<SolidColorBrush x:Key="SeparatorBrush" Color="#E0E0E0" />
|
||||
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
|
||||
|
||||
<Style x:Key="CncNodeItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="TreeItemHover" Color="#F5F9FF" />
|
||||
<SolidColorBrush x:Key="TreeItemSelected" Color="#E8F0FE" />
|
||||
|
||||
<Style x:Key="ToolbarBtn" TargetType="Button">
|
||||
<Setter Property="Height" Value="28" />
|
||||
@@ -34,57 +33,84 @@
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="TreeItemStyle" TargetType="TreeViewItem">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border
|
||||
MinWidth="550"
|
||||
Background="{StaticResource PanelBg}"
|
||||
BorderBrush="{StaticResource PanelBorder}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="280" MinWidth="240" />
|
||||
<ColumnDefinition Width="1" />
|
||||
<ColumnDefinition Width="*" MinWidth="300" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="250" MinWidth="230" />
|
||||
<ColumnDefinition Width="1" />
|
||||
<ColumnDefinition Width="*" MinWidth="260" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!--树行节点显示-->
|
||||
<ListBox
|
||||
x:Name="CncNodeListBox"
|
||||
Grid.Column="0"
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Padding="10,8"
|
||||
BorderBrush="{StaticResource SeparatorBrush}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding ProgramName, TargetNullValue=CNC Program}" />
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="11"
|
||||
Foreground="#666"
|
||||
Text="模块节点下会自动归类显示标记/等待/消息节点" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<TreeView
|
||||
x:Name="CncTreeView"
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
ItemContainerStyle="{StaticResource CncNodeItemStyle}"
|
||||
ItemsSource="{Binding Nodes}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid x:Name="NodeRoot" MinHeight="40">
|
||||
ItemsSource="{Binding TreeNodes}"
|
||||
SelectedItemChanged="CncTreeView_SelectedItemChanged">
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type vm:CncNodeViewModel}" ItemsSource="{Binding Children}">
|
||||
<Grid x:Name="NodeRoot" MinHeight="42">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40" />
|
||||
<ColumnDefinition Width="34" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Width="28"
|
||||
Height="28"
|
||||
Width="26"
|
||||
Height="26"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#E8F0FE"
|
||||
BorderBrush="#5B9BD5"
|
||||
BorderThickness="1.5"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="4">
|
||||
<Image
|
||||
Width="20"
|
||||
Height="20"
|
||||
Width="18"
|
||||
Height="18"
|
||||
Source="{Binding Icon}"
|
||||
Stretch="Uniform" />
|
||||
</Border>
|
||||
@@ -121,7 +147,7 @@
|
||||
Background="Transparent"
|
||||
BorderBrush="#cdcbcb"
|
||||
BorderThickness="1"
|
||||
Command="{Binding DataContext.MoveNodeUpCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||||
Command="{Binding DataContext.MoveNodeUpCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="▲"
|
||||
Cursor="Hand"
|
||||
@@ -134,7 +160,7 @@
|
||||
Background="Transparent"
|
||||
BorderBrush="#cdcbcb"
|
||||
BorderThickness="1"
|
||||
Command="{Binding DataContext.MoveNodeDownCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||||
Command="{Binding DataContext.MoveNodeDownCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="▼"
|
||||
Cursor="Hand"
|
||||
@@ -147,7 +173,7 @@
|
||||
Background="Transparent"
|
||||
BorderBrush="#E05050"
|
||||
BorderThickness="1"
|
||||
Command="{Binding DataContext.DeleteNodeCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||||
Command="{Binding DataContext.DeleteNodeCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"
|
||||
Content="✕"
|
||||
Cursor="Hand"
|
||||
FontSize="10"
|
||||
@@ -159,17 +185,225 @@
|
||||
<Setter TargetName="NodeActions" Property="Visibility" Value="Visible" />
|
||||
</Trigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<Rectangle
|
||||
Grid.Column="1"
|
||||
Width="1"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<views:PipelineEditorView Grid.Column="2" />
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.Resources>
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource TreeItemStyle}" TargetType="TreeViewItem" />
|
||||
</TreeView.ItemContainerStyle>
|
||||
</TreeView>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
Grid.Column="1"
|
||||
Width="1"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto">
|
||||
<Grid Margin="18,18,22,18">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="EditorTitle" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Margin" Value="0,0,0,12" />
|
||||
</Style>
|
||||
<Style x:Key="LabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="#666" />
|
||||
<Setter Property="Margin" Value="0,0,0,4" />
|
||||
</Style>
|
||||
<Style x:Key="EditorBox" TargetType="TextBox">
|
||||
<Setter Property="Height" Value="30" />
|
||||
<Setter Property="Padding" Value="8,4" />
|
||||
<Setter Property="Margin" Value="0,0,0,12" />
|
||||
<Setter Property="BorderBrush" Value="#CFCFCF" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
||||
</Style>
|
||||
<Style x:Key="EditorCheck" TargetType="CheckBox">
|
||||
<Setter Property="Margin" Value="0,4,0,12" />
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<StackPanel Visibility="{Binding SelectedNode, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<TextBlock Style="{StaticResource EditorTitle}" Text="节点属性" />
|
||||
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="名称" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Name, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<UniformGrid Columns="2" Margin="0,0,0,12">
|
||||
<StackPanel Margin="0,0,12,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="索引" />
|
||||
<TextBox Style="{StaticResource EditorBox}" IsReadOnly="True" Text="{Binding SelectedNode.Index, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="类型" />
|
||||
<TextBox Style="{StaticResource EditorBox}" IsReadOnly="True" Text="{Binding SelectedNode.NodeTypeDisplay, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</UniformGrid>
|
||||
|
||||
<GroupBox
|
||||
Margin="0,0,0,12"
|
||||
Header="运动参数"
|
||||
Visibility="{Binding SelectedNode.IsMotionSnapshotNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<UniformGrid Margin="12" Columns="2">
|
||||
<StackPanel Margin="0,0,12,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="XM" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.XM, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="YM" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.YM, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,12,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="ZT" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZT, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="ZD" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZD, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,12,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="TiltD" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.TiltD, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="Dist" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Dist, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</UniformGrid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Margin="0,0,0,12"
|
||||
Header="射线源"
|
||||
Visibility="{Binding SelectedNode.IsReferencePoint, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="12">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电流 (uA)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Current, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Margin="0,0,0,12"
|
||||
Header="采集参数"
|
||||
Visibility="{Binding SelectedNode.IsSaveNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="12">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="功率 (W)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Power, UpdateSourceTrigger=LostFocus}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器已连接" IsChecked="{Binding SelectedNode.DetectorConnected}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器正在采集" IsChecked="{Binding SelectedNode.DetectorAcquiring}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="帧率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FrameRate, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="分辨率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Resolution, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Margin="0,0,0,12"
|
||||
Header="采集参数"
|
||||
Visibility="{Binding SelectedNode.IsSaveNodeWithImage, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="12">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="功率 (W)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Power, UpdateSourceTrigger=LostFocus}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器已连接" IsChecked="{Binding SelectedNode.DetectorConnected}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器正在采集" IsChecked="{Binding SelectedNode.DetectorAcquiring}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="帧率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FrameRate, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="分辨率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Resolution, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="图像文件" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ImageFileName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Margin="0,0,0,12"
|
||||
Header="检测模块"
|
||||
Visibility="{Binding SelectedNode.IsInspectionModule, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="12">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="Pipeline 名称" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.PipelineName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Margin="0,0,0,12"
|
||||
Header="标记参数"
|
||||
Visibility="{Binding SelectedNode.IsInspectionMarker, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="12">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="标记类型" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerType, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="MarkerX" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerX, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="MarkerY" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerY, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Margin="0,0,0,12"
|
||||
Header="消息弹窗"
|
||||
Visibility="{Binding SelectedNode.IsPauseDialog, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="12">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="标题" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DialogTitle, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="消息内容" />
|
||||
<TextBox
|
||||
MinHeight="80"
|
||||
Margin="0,0,0,12"
|
||||
Padding="8,6"
|
||||
AcceptsReturn="True"
|
||||
Text="{Binding SelectedNode.DialogMessage, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Margin="0,0,0,12"
|
||||
Header="等待参数"
|
||||
Visibility="{Binding SelectedNode.IsWaitDelay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="12">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="延时 (ms)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DelayMilliseconds, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<Border
|
||||
Padding="24"
|
||||
Background="#FAFAFA"
|
||||
BorderBrush="#E6E6E6"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6"
|
||||
Visibility="{Binding SelectedNode, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Invert}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="15"
|
||||
FontWeight="SemiBold"
|
||||
Text="未选择节点" />
|
||||
<TextBlock
|
||||
Margin="0,8,0,0"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
Foreground="#666"
|
||||
Text="从左侧树中选择一个节点后,这里会显示并允许编辑该节点的参数。" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Prism.Ioc;
|
||||
using System.Windows.Data;
|
||||
using XplorePlane.ViewModels.Cnc;
|
||||
|
||||
namespace XplorePlane.Views.Cnc
|
||||
@@ -31,5 +33,33 @@ namespace XplorePlane.Views.Cnc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CncTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
if (DataContext is CncEditorViewModel viewModel)
|
||||
{
|
||||
viewModel.SelectedNode = e.NewValue as CncNodeViewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NullToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var invert = string.Equals(parameter as string, "Invert", StringComparison.OrdinalIgnoreCase);
|
||||
var isVisible = value != null;
|
||||
if (invert)
|
||||
{
|
||||
isVisible = !isVisible;
|
||||
}
|
||||
|
||||
return isVisible ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user