矩阵编排允许用户通过界面设定矩阵参数(行数、列数、行间距、列间距),将一个已编写好的 CNC 模板程序(.xp 文件)自动扩展为覆盖所有矩阵位置的完整检测序列,并按行优先顺序依次完成移轴→采图→检测的闭环执行
This commit is contained in:
@@ -632,6 +632,8 @@ namespace XplorePlane
|
||||
containerRegistry.RegisterSingleton<IInspectionResultStore, InspectionResultStore>();
|
||||
containerRegistry.RegisterSingleton<IImagePersistenceService, ImagePersistenceService>();
|
||||
containerRegistry.RegisterSingleton<ICncExecutionService, CncExecutionService>();
|
||||
containerRegistry.RegisterSingleton<IMatrixOrchestrationService, MatrixOrchestrationService>();
|
||||
containerRegistry.RegisterSingleton<MatrixSummaryWriter>();
|
||||
|
||||
// ── 主界面实时图像 / 探测器双队列服务(单例)──
|
||||
containerRegistry.RegisterSingleton<IMainViewportService, MainViewportService>();
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace XplorePlane.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 布尔值取反转换器,用于将 true 转为 false、false 转为 true
|
||||
/// Inverse boolean converter: converts true to false and false to true
|
||||
/// </summary>
|
||||
[ValueConversion(typeof(bool), typeof(bool))]
|
||||
public class InverseBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is bool b ? !b : true;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is bool b ? !b : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,4 +42,39 @@ namespace XplorePlane.Models
|
||||
string CncProgramPath,
|
||||
IReadOnlyList<MatrixCell> Cells
|
||||
);
|
||||
|
||||
// ── 矩阵执行摘要模型(用于 matrix_summary.json 序列化)────────────
|
||||
|
||||
/// <summary>矩阵执行摘要文件根对象 | Matrix execution summary file root object</summary>
|
||||
public class MatrixSummaryFile
|
||||
{
|
||||
public MatrixSummaryConfig Config { get; set; }
|
||||
public string ProgramName { get; set; }
|
||||
public string StartTime { get; set; } // ISO 8601
|
||||
public double DurationSeconds { get; set; }
|
||||
public int TotalCells { get; set; }
|
||||
public int EnabledCells { get; set; }
|
||||
public int CompletedCells { get; set; }
|
||||
public int FailedCells { get; set; }
|
||||
public List<MatrixCellSummaryEntry> Cells { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>矩阵配置信息 | Matrix configuration</summary>
|
||||
public class MatrixSummaryConfig
|
||||
{
|
||||
public int Rows { get; set; }
|
||||
public int Columns { get; set; }
|
||||
public double RowSpacing { get; set; }
|
||||
public double ColumnSpacing { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>矩阵单元格摘要条目 | Matrix cell summary entry</summary>
|
||||
public class MatrixCellSummaryEntry
|
||||
{
|
||||
public string RunId { get; set; }
|
||||
public int Row { get; set; }
|
||||
public int Column { get; set; }
|
||||
public string Status { get; set; }
|
||||
public bool? Pass { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using XplorePlane.Models;
|
||||
|
||||
namespace XplorePlane.Services.Matrix
|
||||
{
|
||||
/// <summary>
|
||||
/// 矩阵编排执行服务接口。
|
||||
/// 按行优先顺序展开矩阵并执行每个启用单元格。
|
||||
/// 对每个单元格:深度克隆模板程序 → 叠加偏移量 → 调用 ICncExecutionService。
|
||||
/// </summary>
|
||||
public interface IMatrixOrchestrationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 按行优先顺序展开矩阵并执行每个启用单元格。
|
||||
/// 对每个单元格:深度克隆模板程序 → 叠加偏移量 → 调用 ICncExecutionService。
|
||||
/// </summary>
|
||||
Task ExecuteAsync(
|
||||
MatrixLayout layout,
|
||||
CncProgram templateProgram,
|
||||
IProgress<MatrixCellExecutionProgress> progress,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using XplorePlane.Models;
|
||||
|
||||
namespace XplorePlane.Services.Matrix
|
||||
{
|
||||
/// <summary>
|
||||
/// 矩阵单元格执行进度报告。
|
||||
/// </summary>
|
||||
public record MatrixCellExecutionProgress(
|
||||
int Row,
|
||||
int Column,
|
||||
MatrixCellStatus Status,
|
||||
int CurrentIndex, // 当前启用单元格序号(从 1 开始)
|
||||
int TotalEnabled, // 总启用单元格数
|
||||
string ErrorMessage = null
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.Cnc;
|
||||
using XplorePlane.Services.InspectionResults;
|
||||
using XplorePlane.Services.Storage;
|
||||
|
||||
namespace XplorePlane.Services.Matrix
|
||||
{
|
||||
/// <summary>
|
||||
/// 矩阵编排执行服务实现。
|
||||
/// 按行优先顺序展开矩阵并执行每个启用单元格:深度克隆模板程序 → 叠加偏移量 → 调用 ICncExecutionService。
|
||||
/// Matrix orchestration execution service implementation.
|
||||
/// Expands matrix in row-major order and executes each enabled cell: deep clone template → apply offset → call ICncExecutionService.
|
||||
/// </summary>
|
||||
public class MatrixOrchestrationService : IMatrixOrchestrationService
|
||||
{
|
||||
private readonly ICncExecutionService _cncExecutionService;
|
||||
private readonly IInspectionResultStore _inspectionResultStore;
|
||||
private readonly MatrixSummaryWriter _matrixSummaryWriter;
|
||||
private readonly IXpDataPathService _dataPathService;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public MatrixOrchestrationService(
|
||||
ICncExecutionService cncExecutionService,
|
||||
IInspectionResultStore inspectionResultStore,
|
||||
MatrixSummaryWriter matrixSummaryWriter,
|
||||
IXpDataPathService dataPathService,
|
||||
ILoggerService logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(cncExecutionService);
|
||||
ArgumentNullException.ThrowIfNull(inspectionResultStore);
|
||||
ArgumentNullException.ThrowIfNull(matrixSummaryWriter);
|
||||
ArgumentNullException.ThrowIfNull(dataPathService);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_cncExecutionService = cncExecutionService;
|
||||
_inspectionResultStore = inspectionResultStore;
|
||||
_matrixSummaryWriter = matrixSummaryWriter;
|
||||
_dataPathService = dataPathService;
|
||||
_logger = logger.ForModule<MatrixOrchestrationService>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteAsync(
|
||||
MatrixLayout layout,
|
||||
CncProgram templateProgram,
|
||||
IProgress<MatrixCellExecutionProgress> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 筛选启用的单元格,按行优先(Row 升序,Column 升序)排列
|
||||
var enabledCells = layout.Cells
|
||||
.Where(c => c.IsEnabled)
|
||||
.OrderBy(c => c.Row)
|
||||
.ThenBy(c => c.Column)
|
||||
.ToList();
|
||||
|
||||
if (enabledCells.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("没有已启用的位置,请至少启用一个单元格");
|
||||
}
|
||||
|
||||
var totalEnabled = enabledCells.Count;
|
||||
var startTime = DateTime.UtcNow;
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
int completedCount = 0;
|
||||
int failedCount = 0;
|
||||
var cellEntries = new List<MatrixCellSummaryEntry>();
|
||||
|
||||
_logger.Info(
|
||||
"矩阵执行开始 | Matrix execution started: Program={ProgramName}, EnabledCells={EnabledCount}, TotalCells={TotalCells}",
|
||||
templateProgram.Name, totalEnabled, layout.Rows * layout.Columns);
|
||||
|
||||
for (int i = 0; i < enabledCells.Count; i++)
|
||||
{
|
||||
var cell = enabledCells[i];
|
||||
var currentIndex = i + 1;
|
||||
|
||||
// 检查取消请求
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 报告 Executing 状态
|
||||
progress?.Report(new MatrixCellExecutionProgress(
|
||||
cell.Row,
|
||||
cell.Column,
|
||||
MatrixCellStatus.Executing,
|
||||
currentIndex,
|
||||
totalEnabled));
|
||||
|
||||
// 生成单元格专用程序
|
||||
var cellProgram = ApplyOffset(templateProgram, cell);
|
||||
|
||||
try
|
||||
{
|
||||
// 调用 CNC 执行服务(内部已处理 BeginRunAsync 归档)
|
||||
await _cncExecutionService.ExecuteAsync(
|
||||
cellProgram,
|
||||
new Progress<CncNodeExecutionProgress>(),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 成功完成
|
||||
completedCount++;
|
||||
progress?.Report(new MatrixCellExecutionProgress(
|
||||
cell.Row,
|
||||
cell.Column,
|
||||
MatrixCellStatus.Completed,
|
||||
currentIndex,
|
||||
totalEnabled));
|
||||
|
||||
cellEntries.Add(new MatrixCellSummaryEntry
|
||||
{
|
||||
RunId = cellProgram.Id.ToString(),
|
||||
Row = cell.Row,
|
||||
Column = cell.Column,
|
||||
Status = nameof(MatrixCellStatus.Completed),
|
||||
Pass = true
|
||||
});
|
||||
|
||||
_logger.Info(
|
||||
"单元格执行完成 | Cell completed: R{Row}C{Col} ({Current}/{Total})",
|
||||
cell.Row, cell.Column, currentIndex, totalEnabled);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 探测器断连或用户取消 — 标记当前单元格为 Error,传播异常退出循环
|
||||
failedCount++;
|
||||
progress?.Report(new MatrixCellExecutionProgress(
|
||||
cell.Row,
|
||||
cell.Column,
|
||||
MatrixCellStatus.Error,
|
||||
currentIndex,
|
||||
totalEnabled,
|
||||
"操作已取消(探测器断连或用户停止)"));
|
||||
|
||||
cellEntries.Add(new MatrixCellSummaryEntry
|
||||
{
|
||||
RunId = cellProgram.Id.ToString(),
|
||||
Row = cell.Row,
|
||||
Column = cell.Column,
|
||||
Status = nameof(MatrixCellStatus.Error),
|
||||
Pass = false
|
||||
});
|
||||
|
||||
_logger.Info(
|
||||
"矩阵执行被取消 | Matrix execution cancelled at R{Row}C{Col} ({Current}/{Total})",
|
||||
cell.Row, cell.Column, currentIndex, totalEnabled);
|
||||
|
||||
// 写入摘要(即使被取消也尝试写入已有结果)
|
||||
stopwatch.Stop();
|
||||
await WriteSummaryAsync(layout, templateProgram.Name, startTime, cellEntries, cancellationToken: default).ConfigureAwait(false);
|
||||
LogSummary(templateProgram.Name, totalEnabled, completedCount, failedCount, stopwatch.Elapsed);
|
||||
|
||||
throw; // 传播 OperationCanceledException
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 普通异常 — 标记 Error,继续下一个单元格
|
||||
failedCount++;
|
||||
var errorMessage = ex.Message;
|
||||
|
||||
progress?.Report(new MatrixCellExecutionProgress(
|
||||
cell.Row,
|
||||
cell.Column,
|
||||
MatrixCellStatus.Error,
|
||||
currentIndex,
|
||||
totalEnabled,
|
||||
errorMessage));
|
||||
|
||||
cellEntries.Add(new MatrixCellSummaryEntry
|
||||
{
|
||||
RunId = cellProgram.Id.ToString(),
|
||||
Row = cell.Row,
|
||||
Column = cell.Column,
|
||||
Status = nameof(MatrixCellStatus.Error),
|
||||
Pass = false
|
||||
});
|
||||
|
||||
_logger.Error(ex,
|
||||
"单元格执行失败 | Cell execution failed: R{Row}C{Col} ({Current}/{Total})",
|
||||
cell.Row, cell.Column, currentIndex, totalEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
// 全部完成后写入摘要
|
||||
stopwatch.Stop();
|
||||
await WriteSummaryAsync(layout, templateProgram.Name, startTime, cellEntries, cancellationToken: default).ConfigureAwait(false);
|
||||
LogSummary(templateProgram.Name, totalEnabled, completedCount, failedCount, stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入矩阵执行摘要文件。
|
||||
/// </summary>
|
||||
private async Task WriteSummaryAsync(
|
||||
MatrixLayout layout,
|
||||
string templateProgramName,
|
||||
DateTime startTime,
|
||||
IReadOnlyList<MatrixCellSummaryEntry> cellEntries,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var outputDirectory = System.IO.Path.Combine(_dataPathService.DataPath, "MatrixResults");
|
||||
await _matrixSummaryWriter.WriteAsync(
|
||||
layout,
|
||||
templateProgramName,
|
||||
startTime,
|
||||
cellEntries,
|
||||
outputDirectory,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录汇总日志。
|
||||
/// </summary>
|
||||
private void LogSummary(string programName, int totalCells, int completed, int failed, TimeSpan duration)
|
||||
{
|
||||
_logger.Info(
|
||||
"矩阵执行汇总 | Matrix execution summary: Program={ProgramName}, TotalCells={TotalCells}, Completed={Completed}, Failed={Failed}, Duration={Duration:F1}s",
|
||||
programName, totalCells, completed, failed, duration.TotalSeconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将模板程序按单元格偏移量生成该单元格专用的 CNC 程序副本。
|
||||
/// 使用 record with 表达式深度克隆,对每个 SavePositionNode 叠加偏移量(μm),
|
||||
/// 生成新 Id 和新程序名,原模板对象不被修改。
|
||||
///
|
||||
/// Generates a cell-specific CNC program copy by applying cell offset to the template.
|
||||
/// Uses record with expression for deep clone, adds offset (μm) to each SavePositionNode,
|
||||
/// generates new Id and program name. Original template is NOT modified.
|
||||
/// </summary>
|
||||
internal static CncProgram ApplyOffset(CncProgram template, MatrixCell cell)
|
||||
{
|
||||
var offsetXum = cell.OffsetX * 1000.0;
|
||||
var offsetYum = cell.OffsetY * 1000.0;
|
||||
|
||||
var newNodes = template.Nodes
|
||||
.Select(node => node is SavePositionNode sp
|
||||
? sp with
|
||||
{
|
||||
MotionState = sp.MotionState with
|
||||
{
|
||||
StageX = sp.MotionState.StageX + offsetXum,
|
||||
StageY = sp.MotionState.StageY + offsetYum
|
||||
}
|
||||
}
|
||||
: node)
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
|
||||
return template with
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = $"{template.Name}_R{cell.Row}C{cell.Column}",
|
||||
Nodes = newNodes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
@@ -24,6 +25,7 @@ namespace XplorePlane.Services.Matrix
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Converters = { new JsonStringEnumConverter() }
|
||||
};
|
||||
|
||||
@@ -40,6 +42,8 @@ namespace XplorePlane.Services.Matrix
|
||||
public MatrixLayout CreateLayout(int rows, int columns, double rowSpacing, double columnSpacing)
|
||||
{
|
||||
ValidateDimensions(rows, columns);
|
||||
ValidateSpacing(rowSpacing);
|
||||
ValidateSpacing(columnSpacing);
|
||||
|
||||
var layout = new MatrixLayout(
|
||||
Id: Guid.NewGuid(),
|
||||
@@ -62,6 +66,8 @@ namespace XplorePlane.Services.Matrix
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(layout);
|
||||
ValidateDimensions(rows, columns);
|
||||
ValidateSpacing(rowSpacing);
|
||||
ValidateSpacing(columnSpacing);
|
||||
|
||||
var updated = layout with
|
||||
{
|
||||
@@ -249,8 +255,24 @@ namespace XplorePlane.Services.Matrix
|
||||
return cells.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证间距参数 | Validate spacing parameter
|
||||
/// Spacing must be in [0.0, 1000.0].
|
||||
/// </summary>
|
||||
private static void ValidateSpacing(double spacing)
|
||||
{
|
||||
if (spacing < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(spacing),
|
||||
$"间距不能为负数 | Spacing must not be negative: {spacing}");
|
||||
|
||||
if (spacing > 1000.0)
|
||||
throw new ArgumentOutOfRangeException(nameof(spacing),
|
||||
$"间距不能超过 1000mm | Spacing must not exceed 1000mm: {spacing}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证行列数参数 | Validate row and column dimensions
|
||||
/// Rows and Columns must be in [1, 50].
|
||||
/// </summary>
|
||||
private static void ValidateDimensions(int rows, int columns)
|
||||
{
|
||||
@@ -258,9 +280,17 @@ namespace XplorePlane.Services.Matrix
|
||||
throw new ArgumentOutOfRangeException(nameof(rows),
|
||||
$"行数必须大于 0 | Rows must be greater than 0: {rows}");
|
||||
|
||||
if (rows > 50)
|
||||
throw new ArgumentOutOfRangeException(nameof(rows),
|
||||
$"行数不能超过 50 | Rows must not exceed 50: {rows}");
|
||||
|
||||
if (columns <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(columns),
|
||||
$"列数必须大于 0 | Columns must be greater than 0: {columns}");
|
||||
|
||||
if (columns > 50)
|
||||
throw new ArgumentOutOfRangeException(nameof(columns),
|
||||
$"列数不能超过 50 | Columns must not exceed 50: {columns}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
|
||||
namespace XplorePlane.Services.Matrix
|
||||
{
|
||||
/// <summary>
|
||||
/// 矩阵执行摘要写入器,负责将矩阵执行结果序列化为 matrix_summary.json 文件。
|
||||
/// Matrix execution summary writer, responsible for serializing matrix execution results to matrix_summary.json.
|
||||
/// </summary>
|
||||
public class MatrixSummaryWriter
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
private static readonly JsonSerializerOptions SummaryJsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Converters = { new JsonStringEnumConverter() }
|
||||
};
|
||||
|
||||
public MatrixSummaryWriter(ILoggerService logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
_logger = logger.ForModule<MatrixSummaryWriter>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将矩阵执行摘要写入 JSON 文件。写入失败时记录错误日志,不重新抛出异常。
|
||||
/// Write matrix execution summary to JSON file. Logs error on failure without rethrowing.
|
||||
/// </summary>
|
||||
/// <param name="layout">矩阵布局 | Matrix layout</param>
|
||||
/// <param name="templateProgramName">模板程序名称 | Template program name</param>
|
||||
/// <param name="startTime">执行开始时间 | Execution start time</param>
|
||||
/// <param name="cellEntries">各单元格摘要条目 | Cell summary entries</param>
|
||||
/// <param name="outputDirectory">输出目录 | Output directory</param>
|
||||
/// <param name="ct">取消令牌 | Cancellation token</param>
|
||||
public async Task WriteAsync(
|
||||
MatrixLayout layout,
|
||||
string templateProgramName,
|
||||
DateTime startTime,
|
||||
IReadOnlyList<MatrixCellSummaryEntry> cellEntries,
|
||||
string outputDirectory,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var duration = DateTime.UtcNow - startTime;
|
||||
|
||||
var summaryFile = new MatrixSummaryFile
|
||||
{
|
||||
Config = new MatrixSummaryConfig
|
||||
{
|
||||
Rows = layout.Rows,
|
||||
Columns = layout.Columns,
|
||||
RowSpacing = layout.RowSpacing,
|
||||
ColumnSpacing = layout.ColumnSpacing
|
||||
},
|
||||
ProgramName = templateProgramName,
|
||||
StartTime = startTime.ToString("o"),
|
||||
DurationSeconds = Math.Round(duration.TotalSeconds, 2),
|
||||
TotalCells = layout.Rows * layout.Columns,
|
||||
EnabledCells = layout.Cells.Count(c => c.IsEnabled),
|
||||
CompletedCells = cellEntries.Count(e => e.Status == nameof(MatrixCellStatus.Completed)),
|
||||
FailedCells = cellEntries.Count(e => e.Status == nameof(MatrixCellStatus.Error)),
|
||||
Cells = cellEntries.ToList()
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(summaryFile, SummaryJsonOptions);
|
||||
var filePath = Path.Combine(outputDirectory, "matrix_summary.json");
|
||||
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
await File.WriteAllTextAsync(filePath, json, ct).ConfigureAwait(false);
|
||||
|
||||
_logger.Info("已写入矩阵执行摘要 | Matrix summary written: {FilePath}", filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "写入矩阵执行摘要失败 | Failed to write matrix summary to {OutputDirectory}", outputDirectory);
|
||||
// 不重新抛出,确保不影响已完成的单元格检测结果归档
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
using Microsoft.Win32;
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.Cnc;
|
||||
@@ -20,12 +24,16 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
private readonly IMatrixService _matrixService;
|
||||
private readonly ICncProgramService _cncProgramService;
|
||||
private readonly IMatrixOrchestrationService _orchestrationService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
// 当前矩阵布局 | Current matrix layout
|
||||
private MatrixLayout _currentLayout;
|
||||
|
||||
// 执行取消令牌源 | Execution cancellation token source
|
||||
private CancellationTokenSource _executionCts;
|
||||
|
||||
private int _rows = 1;
|
||||
private int _columns = 1;
|
||||
private double _rowSpacing;
|
||||
@@ -34,29 +42,51 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
private MatrixCellViewModel _selectedCell;
|
||||
private string _associatedProgramPath;
|
||||
|
||||
// ── 验证错误属性 | Validation error properties ──
|
||||
private string _rowsError;
|
||||
private string _columnsError;
|
||||
private string _rowSpacingError;
|
||||
private string _colSpacingError;
|
||||
private bool _canUpdateLayout = true;
|
||||
|
||||
// ── 显示与状态属性 | Display and state properties ──
|
||||
private string _associatedProgramName;
|
||||
private int _enabledCount;
|
||||
private int _totalCount;
|
||||
private bool _isExecuting;
|
||||
private string _statusText;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public MatrixEditorViewModel(
|
||||
IMatrixService matrixService,
|
||||
ICncProgramService cncProgramService,
|
||||
IMatrixOrchestrationService orchestrationService,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_matrixService = matrixService ?? throw new ArgumentNullException(nameof(matrixService));
|
||||
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
|
||||
_orchestrationService = orchestrationService ?? throw new ArgumentNullException(nameof(orchestrationService));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<MatrixEditorViewModel>();
|
||||
|
||||
_cells = new ObservableCollection<MatrixCellViewModel>();
|
||||
|
||||
// ── 命令初始化 | Command initialization ──
|
||||
UpdateLayoutCommand = new DelegateCommand(ExecuteUpdateLayout);
|
||||
UpdateLayoutCommand = new DelegateCommand(ExecuteUpdateLayout, () => CanUpdateLayout)
|
||||
.ObservesProperty(() => CanUpdateLayout);
|
||||
ToggleCellCommand = new DelegateCommand<MatrixCellViewModel>(ExecuteToggleCell);
|
||||
SelectCellCommand = new DelegateCommand<MatrixCellViewModel>(ExecuteSelectCell);
|
||||
AssociateProgramCommand = new DelegateCommand(async () => await ExecuteAssociateProgramAsync());
|
||||
SaveLayoutCommand = new DelegateCommand(async () => await ExecuteSaveLayoutAsync());
|
||||
LoadLayoutCommand = new DelegateCommand(async () => await ExecuteLoadLayoutAsync());
|
||||
RunMatrixCommand = new DelegateCommand(async () => await ExecuteRunMatrixAsync(), CanExecuteRunMatrix)
|
||||
.ObservesProperty(() => IsExecuting)
|
||||
.ObservesProperty(() => AssociatedProgramPath);
|
||||
StopCommand = new DelegateCommand(ExecuteStop, () => IsExecuting)
|
||||
.ObservesProperty(() => IsExecuting);
|
||||
|
||||
_logger.Info("MatrixEditorViewModel 已初始化 | MatrixEditorViewModel initialized");
|
||||
}
|
||||
@@ -109,7 +139,89 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
public string AssociatedProgramPath
|
||||
{
|
||||
get => _associatedProgramPath;
|
||||
set => SetProperty(ref _associatedProgramPath, value);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _associatedProgramPath, value))
|
||||
{
|
||||
AssociatedProgramName = string.IsNullOrWhiteSpace(value)
|
||||
? null
|
||||
: Path.GetFileName(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 验证错误属性 | Validation error properties ──────────────────
|
||||
|
||||
/// <summary>行数输入错误提示 | Rows input error message</summary>
|
||||
public string RowsError
|
||||
{
|
||||
get => _rowsError;
|
||||
set => SetProperty(ref _rowsError, value);
|
||||
}
|
||||
|
||||
/// <summary>列数输入错误提示 | Columns input error message</summary>
|
||||
public string ColumnsError
|
||||
{
|
||||
get => _columnsError;
|
||||
set => SetProperty(ref _columnsError, value);
|
||||
}
|
||||
|
||||
/// <summary>行间距输入错误提示 | Row spacing input error message</summary>
|
||||
public string RowSpacingError
|
||||
{
|
||||
get => _rowSpacingError;
|
||||
set => SetProperty(ref _rowSpacingError, value);
|
||||
}
|
||||
|
||||
/// <summary>列间距输入错误提示 | Column spacing input error message</summary>
|
||||
public string ColSpacingError
|
||||
{
|
||||
get => _colSpacingError;
|
||||
set => SetProperty(ref _colSpacingError, value);
|
||||
}
|
||||
|
||||
/// <summary>是否可以更新布局(输入合法时为 true)| Whether layout can be updated (true when inputs are valid)</summary>
|
||||
public bool CanUpdateLayout
|
||||
{
|
||||
get => _canUpdateLayout;
|
||||
set => SetProperty(ref _canUpdateLayout, value);
|
||||
}
|
||||
|
||||
// ── 显示与状态属性 | Display and state properties ────────────────
|
||||
|
||||
/// <summary>关联程序的文件名(不含路径)| Associated program filename (without path)</summary>
|
||||
public string AssociatedProgramName
|
||||
{
|
||||
get => _associatedProgramName;
|
||||
set => SetProperty(ref _associatedProgramName, value);
|
||||
}
|
||||
|
||||
/// <summary>已启用的单元格数量 | Number of enabled cells</summary>
|
||||
public int EnabledCount
|
||||
{
|
||||
get => _enabledCount;
|
||||
set => SetProperty(ref _enabledCount, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格总数 | Total number of cells</summary>
|
||||
public int TotalCount
|
||||
{
|
||||
get => _totalCount;
|
||||
set => SetProperty(ref _totalCount, value);
|
||||
}
|
||||
|
||||
/// <summary>是否正在执行矩阵 | Whether matrix execution is in progress</summary>
|
||||
public bool IsExecuting
|
||||
{
|
||||
get => _isExecuting;
|
||||
set => SetProperty(ref _isExecuting, value);
|
||||
}
|
||||
|
||||
/// <summary>底部状态栏文本 | Bottom status bar text</summary>
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
set => SetProperty(ref _statusText, value);
|
||||
}
|
||||
|
||||
// ── 命令 | Commands ────────────────────────────────────────────
|
||||
@@ -132,6 +244,12 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
/// <summary>加载矩阵布局命令 | Load matrix layout command</summary>
|
||||
public DelegateCommand LoadLayoutCommand { get; }
|
||||
|
||||
/// <summary>运行矩阵执行命令 | Run matrix execution command</summary>
|
||||
public DelegateCommand RunMatrixCommand { get; }
|
||||
|
||||
/// <summary>停止执行命令 | Stop execution command</summary>
|
||||
public DelegateCommand StopCommand { get; }
|
||||
|
||||
// ── 命令执行方法 | Command execution methods ────────────────────
|
||||
|
||||
/// <summary>
|
||||
@@ -140,6 +258,13 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
/// </summary>
|
||||
private void ExecuteUpdateLayout()
|
||||
{
|
||||
// 清除之前的错误 | Clear previous errors
|
||||
RowsError = null;
|
||||
ColumnsError = null;
|
||||
RowSpacingError = null;
|
||||
ColSpacingError = null;
|
||||
CanUpdateLayout = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (_currentLayout == null)
|
||||
@@ -154,12 +279,56 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
RefreshCells();
|
||||
_logger.Info("已更新矩阵布局 | Updated matrix layout: Rows={Rows}, Columns={Columns}", Rows, Columns);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
HandleValidationError(ex);
|
||||
_logger.Error(ex, "更新矩阵布局失败 | Failed to update matrix layout");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "更新矩阵布局失败 | Failed to update matrix layout");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理参数验证异常,设置对应的错误属性
|
||||
/// Handle parameter validation exception and set corresponding error properties
|
||||
/// </summary>
|
||||
private void HandleValidationError(ArgumentOutOfRangeException ex)
|
||||
{
|
||||
CanUpdateLayout = false;
|
||||
var paramName = ex.ParamName?.ToLowerInvariant() ?? string.Empty;
|
||||
|
||||
if (paramName.Contains("row") && paramName.Contains("spacing"))
|
||||
{
|
||||
RowSpacingError = "间距不能为负数";
|
||||
}
|
||||
else if (paramName.Contains("col") && paramName.Contains("spacing"))
|
||||
{
|
||||
ColSpacingError = "间距不能为负数";
|
||||
}
|
||||
else if (paramName.Contains("spacing"))
|
||||
{
|
||||
// Generic spacing error — set both
|
||||
RowSpacingError = "间距不能为负数";
|
||||
ColSpacingError = "间距不能为负数";
|
||||
}
|
||||
else if (paramName.Contains("row"))
|
||||
{
|
||||
RowsError = "行数/列数必须在 1 到 50 之间";
|
||||
}
|
||||
else if (paramName.Contains("col"))
|
||||
{
|
||||
ColumnsError = "行数/列数必须在 1 到 50 之间";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: try to determine from message or set both
|
||||
RowsError = "行数/列数必须在 1 到 50 之间";
|
||||
ColumnsError = "行数/列数必须在 1 到 50 之间";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中指定单元格,更新高亮状态
|
||||
/// Select the specified cell and update highlight state
|
||||
@@ -199,8 +368,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关联 CNC 程序到当前矩阵布局(占位:从 AssociatedProgramPath 加载)
|
||||
/// Associate a CNC program with the current layout (placeholder: loads from AssociatedProgramPath)
|
||||
/// 关联 CNC 程序:使用 OpenFileDialog 选择 .xp 文件,验证文件有效性
|
||||
/// Associate CNC program: use OpenFileDialog to select .xp file, validate file
|
||||
/// </summary>
|
||||
private async Task ExecuteAssociateProgramAsync()
|
||||
{
|
||||
@@ -210,26 +379,50 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(AssociatedProgramPath))
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
_logger.Warn("无法关联程序:程序路径为空 | Cannot associate program: program path is empty");
|
||||
Title = "选择 CNC 模板程序",
|
||||
Filter = "CNC 程序文件 (*.xp)|*.xp",
|
||||
CheckFileExists = true
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() != true)
|
||||
return;
|
||||
}
|
||||
|
||||
var filePath = dialog.FileName;
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
try
|
||||
{
|
||||
var program = await _cncProgramService.LoadAsync(AssociatedProgramPath);
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
MessageBox.Show($"无法加载程序文件:{fileName}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var program = await _cncProgramService.LoadAsync(filePath);
|
||||
|
||||
// 验证程序包含至少一个 SavePositionNode
|
||||
if (!program.Nodes.OfType<SavePositionNode>().Any())
|
||||
{
|
||||
MessageBox.Show("所选程序不包含位置节点,无法用作矩阵模板", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_currentLayout = _matrixService.AssociateCncProgram(_currentLayout, program);
|
||||
_logger.Info("已关联 CNC 程序 | Associated CNC program: {ProgramPath}", AssociatedProgramPath);
|
||||
AssociatedProgramPath = filePath;
|
||||
_logger.Info("已关联 CNC 程序 | Associated CNC program: {ProgramPath}", filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "关联 CNC 程序失败 | Failed to associate CNC program");
|
||||
MessageBox.Show($"无法加载程序文件:{fileName}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
_logger.Error(ex, "关联 CNC 程序失败 | Failed to associate CNC program: {FileName}", fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前矩阵布局到文件 | Save current matrix layout to file
|
||||
/// 保存当前矩阵布局:使用 SaveFileDialog 选择保存路径
|
||||
/// Save current matrix layout: use SaveFileDialog to select save path
|
||||
/// </summary>
|
||||
private async Task ExecuteSaveLayoutAsync()
|
||||
{
|
||||
@@ -239,12 +432,21 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new SaveFileDialog
|
||||
{
|
||||
Title = "保存矩阵方案",
|
||||
Filter = "JSON 文件 (*.json)|*.json",
|
||||
DefaultExt = ".json",
|
||||
FileName = $"matrix_{_currentLayout.Id}"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() != true)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 占位:实际应通过文件对话框选择路径 | Placeholder: should use file dialog to select path
|
||||
var filePath = $"matrix_{_currentLayout.Id}.json";
|
||||
await _matrixService.SaveAsync(_currentLayout, filePath);
|
||||
_logger.Info("矩阵布局已保存 | Matrix layout saved: {FilePath}", filePath);
|
||||
await _matrixService.SaveAsync(_currentLayout, dialog.FileName);
|
||||
_logger.Info("矩阵布局已保存 | Matrix layout saved: {FilePath}", dialog.FileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -253,15 +455,24 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载矩阵布局 | Load matrix layout from file
|
||||
/// 从文件加载矩阵布局:使用 OpenFileDialog 选择文件
|
||||
/// Load matrix layout from file: use OpenFileDialog to select file
|
||||
/// </summary>
|
||||
private async Task ExecuteLoadLayoutAsync()
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "加载矩阵方案",
|
||||
Filter = "JSON 文件 (*.json)|*.json",
|
||||
CheckFileExists = true
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() != true)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 占位:实际应通过文件对话框选择路径 | Placeholder: should use file dialog to select path
|
||||
var filePath = $"matrix_layout.json";
|
||||
_currentLayout = await _matrixService.LoadAsync(filePath);
|
||||
_currentLayout = await _matrixService.LoadAsync(dialog.FileName);
|
||||
|
||||
// 同步属性到 ViewModel | Sync properties to ViewModel
|
||||
Rows = _currentLayout.Rows;
|
||||
@@ -271,14 +482,132 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
AssociatedProgramPath = _currentLayout.CncProgramPath;
|
||||
|
||||
RefreshCells();
|
||||
|
||||
// 检查关联的 CNC 程序文件是否存在 | Check if associated CNC program file exists
|
||||
if (!string.IsNullOrWhiteSpace(_currentLayout.CncProgramPath)
|
||||
&& !File.Exists(_currentLayout.CncProgramPath))
|
||||
{
|
||||
MessageBox.Show("关联的 CNC 程序文件已移动或删除,请重新关联", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
_logger.Info("矩阵布局已加载 | Matrix layout loaded: Rows={Rows}, Columns={Columns}", Rows, Columns);
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
MessageBox.Show($"方案文件格式无效:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
_logger.Error(ex, "加载矩阵布局失败:格式无效 | Failed to load matrix layout: invalid format");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "加载矩阵布局失败 | Failed to load matrix layout");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断 RunMatrixCommand 是否可执行
|
||||
/// Determine if RunMatrixCommand can execute
|
||||
/// </summary>
|
||||
private bool CanExecuteRunMatrix()
|
||||
{
|
||||
return !IsExecuting && !string.IsNullOrWhiteSpace(AssociatedProgramPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行矩阵运行:加载模板程序 → 重置状态 → 执行 → 更新 UI
|
||||
/// Execute matrix run: load template → reset states → execute → update UI
|
||||
/// </summary>
|
||||
private async Task ExecuteRunMatrixAsync()
|
||||
{
|
||||
if (_currentLayout == null || string.IsNullOrWhiteSpace(AssociatedProgramPath))
|
||||
return;
|
||||
|
||||
CncProgram templateProgram;
|
||||
try
|
||||
{
|
||||
templateProgram = await _cncProgramService.LoadAsync(AssociatedProgramPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"无法加载程序文件:{AssociatedProgramName}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
_logger.Error(ex, "运行矩阵失败:无法加载模板程序 | Run matrix failed: cannot load template program");
|
||||
return;
|
||||
}
|
||||
|
||||
IsExecuting = true;
|
||||
|
||||
// 重置所有单元格状态为 NotExecuted | Reset all cell statuses to NotExecuted
|
||||
foreach (var cell in Cells)
|
||||
{
|
||||
cell.Status = MatrixCellStatus.NotExecuted;
|
||||
cell.ErrorMessage = null;
|
||||
}
|
||||
|
||||
StatusText = null;
|
||||
_executionCts = new CancellationTokenSource();
|
||||
|
||||
var progress = new Progress<MatrixCellExecutionProgress>(p =>
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 更新对应单元格的状态 | Update the corresponding cell's status
|
||||
var cellVm = Cells.FirstOrDefault(c => c.Row == p.Row && c.Column == p.Column);
|
||||
if (cellVm != null)
|
||||
{
|
||||
cellVm.Status = p.Status;
|
||||
if (!string.IsNullOrEmpty(p.ErrorMessage))
|
||||
cellVm.ErrorMessage = p.ErrorMessage;
|
||||
}
|
||||
|
||||
// 更新状态文本 | Update status text
|
||||
if (p.Status == MatrixCellStatus.Executing)
|
||||
{
|
||||
StatusText = $"正在执行:{AssociatedProgramName} | 当前位置:R{p.Row}C{p.Column}({p.CurrentIndex}/{p.TotalEnabled})";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await _orchestrationService.ExecuteAsync(_currentLayout, templateProgram, progress, _executionCts.Token);
|
||||
|
||||
// 执行完成 | Execution completed
|
||||
var completedCount = Cells.Count(c => c.Status == MatrixCellStatus.Completed);
|
||||
var failedCount = Cells.Count(c => c.Status == MatrixCellStatus.Error);
|
||||
var totalEnabled = Cells.Count(c => c.IsEnabled);
|
||||
StatusText = $"执行完成:共 {totalEnabled} 个位置,{completedCount} 成功,{failedCount} 失败";
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 用户停止或外部取消 | User stopped or external cancellation
|
||||
var completedCount = Cells.Count(c => c.Status == MatrixCellStatus.Completed || c.Status == MatrixCellStatus.Error);
|
||||
var notExecutedCount = Cells.Count(c => c.IsEnabled && c.Status == MatrixCellStatus.NotExecuted);
|
||||
StatusText = $"执行已停止:已完成 {completedCount} 个位置,{notExecutedCount} 个位置未执行";
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
_logger.Error(ex, "运行矩阵失败 | Run matrix failed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "矩阵执行异常 | Matrix execution exception");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsExecuting = false;
|
||||
_executionCts?.Dispose();
|
||||
_executionCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止矩阵执行 | Stop matrix execution
|
||||
/// </summary>
|
||||
private void ExecuteStop()
|
||||
{
|
||||
_executionCts?.Cancel();
|
||||
}
|
||||
|
||||
// ── 辅助方法 | Helper methods ───────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
@@ -290,13 +619,21 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
Cells.Clear();
|
||||
|
||||
if (_currentLayout?.Cells == null)
|
||||
{
|
||||
EnabledCount = 0;
|
||||
TotalCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var cell in _currentLayout.Cells)
|
||||
{
|
||||
Cells.Add(new MatrixCellViewModel(cell));
|
||||
}
|
||||
|
||||
// 更新统计 | Update statistics
|
||||
TotalCount = Cells.Count;
|
||||
EnabledCount = Cells.Count(c => c.IsEnabled);
|
||||
|
||||
// 尝试保持选中状态 | Try to preserve selection
|
||||
if (SelectedCell != null)
|
||||
{
|
||||
@@ -305,4 +642,4 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:models="clr-namespace:XplorePlane.Models"
|
||||
xmlns:converters="clr-namespace:XplorePlane.Converters"
|
||||
d:DesignHeight="700"
|
||||
d:DesignWidth="900"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
@@ -19,6 +21,10 @@
|
||||
<SolidColorBrush x:Key="AccentBlue" Color="#E3F0FF" />
|
||||
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
|
||||
|
||||
<!-- 转换器 | Converters -->
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||
|
||||
<!-- 配置面板标签样式 | Configuration panel label style -->
|
||||
<Style x:Key="ConfigLabel" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
||||
@@ -38,6 +44,15 @@
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- 错误提示文本样式 | Error message text style -->
|
||||
<Style x:Key="ErrorText" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
||||
<Setter Property="FontSize" Value="10" />
|
||||
<Setter Property="Foreground" Value="#E53935" />
|
||||
<Setter Property="Margin" Value="0,0,0,4" />
|
||||
<Setter Property="TextWrapping" Value="Wrap" />
|
||||
</Style>
|
||||
|
||||
<!-- 工具栏按钮样式(与 CncPageView 一致)| Toolbar button style (consistent with CncPageView) -->
|
||||
<Style x:Key="ToolbarBtn" TargetType="Button">
|
||||
<Setter Property="Height" Value="28" />
|
||||
@@ -58,236 +73,401 @@
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<!-- 左侧:配置面板 | Left: configuration panel -->
|
||||
<ColumnDefinition Width="220" />
|
||||
<!-- 分隔线 | Splitter -->
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<!-- 右侧:矩阵网格 | Right: matrix grid -->
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<!-- 主内容区域 | Main content area -->
|
||||
<RowDefinition Height="*" />
|
||||
<!-- 底部状态栏 | Bottom status bar -->
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ═══ 左侧:矩阵配置面板 | Left: matrix configuration panel ═══ -->
|
||||
<ScrollViewer
|
||||
Grid.Column="0"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="10,8">
|
||||
<!-- 面板标题 | Panel title -->
|
||||
<TextBlock
|
||||
Margin="0,0,0,10"
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333"
|
||||
Text="矩阵配置 | Matrix Config" />
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<!-- 左侧:配置面板 | Left: configuration panel -->
|
||||
<ColumnDefinition Width="220" />
|
||||
<!-- 分隔线 | Splitter -->
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<!-- 右侧:矩阵网格 | Right: matrix grid -->
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 行数 | Rows -->
|
||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="行数 | Rows" />
|
||||
<TextBox
|
||||
Margin="0,2,0,8"
|
||||
Style="{StaticResource ConfigInput}"
|
||||
Text="{Binding Rows, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="矩阵行数 | Number of rows" />
|
||||
<!-- ═══ 左侧:矩阵配置面板 | Left: matrix configuration panel ═══ -->
|
||||
<ScrollViewer
|
||||
Grid.Column="0"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="10,8">
|
||||
<!-- 面板标题 | Panel title -->
|
||||
<TextBlock
|
||||
Margin="0,0,0,10"
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333"
|
||||
Text="矩阵配置 | Matrix Config" />
|
||||
|
||||
<!-- 列数 | Columns -->
|
||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="列数 | Columns" />
|
||||
<TextBox
|
||||
Margin="0,2,0,8"
|
||||
Style="{StaticResource ConfigInput}"
|
||||
Text="{Binding Columns, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="矩阵列数 | Number of columns" />
|
||||
<!-- 行数 | Rows -->
|
||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="行数 | Rows" />
|
||||
<TextBox
|
||||
Margin="0,2,0,2"
|
||||
Style="{StaticResource ConfigInput}"
|
||||
Text="{Binding Rows, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="矩阵行数 | Number of rows" />
|
||||
<!-- 行数错误提示 | Rows error message -->
|
||||
<TextBlock
|
||||
Style="{StaticResource ErrorText}"
|
||||
Text="{Binding RowsError}" />
|
||||
|
||||
<!-- 行间距 | Row Spacing -->
|
||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="行间距 (mm) | Row Spacing" />
|
||||
<TextBox
|
||||
Margin="0,2,0,8"
|
||||
Style="{StaticResource ConfigInput}"
|
||||
Text="{Binding RowSpacing, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="行间距(毫米)| Row spacing in mm" />
|
||||
<!-- 列数 | Columns -->
|
||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="列数 | Columns" />
|
||||
<TextBox
|
||||
Margin="0,2,0,2"
|
||||
Style="{StaticResource ConfigInput}"
|
||||
Text="{Binding Columns, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="矩阵列数 | Number of columns" />
|
||||
<!-- 列数错误提示 | Columns error message -->
|
||||
<TextBlock
|
||||
Style="{StaticResource ErrorText}"
|
||||
Text="{Binding ColumnsError}" />
|
||||
|
||||
<!-- 列间距 | Column Spacing -->
|
||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="列间距 (mm) | Col Spacing" />
|
||||
<TextBox
|
||||
Margin="0,2,0,8"
|
||||
Style="{StaticResource ConfigInput}"
|
||||
Text="{Binding ColumnSpacing, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="列间距(毫米)| Column spacing in mm" />
|
||||
<!-- 行间距 | Row Spacing -->
|
||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="行间距 (mm) | Row Spacing" />
|
||||
<TextBox
|
||||
Margin="0,2,0,2"
|
||||
Style="{StaticResource ConfigInput}"
|
||||
Text="{Binding RowSpacing, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="行间距(毫米)| Row spacing in mm" />
|
||||
<!-- 行间距错误提示 | Row spacing error message -->
|
||||
<TextBlock
|
||||
Style="{StaticResource ErrorText}"
|
||||
Text="{Binding RowSpacingError}" />
|
||||
|
||||
<!-- 分隔线 | Separator -->
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,4,0,8"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
<!-- 列间距 | Column Spacing -->
|
||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="列间距 (mm) | Col Spacing" />
|
||||
<TextBox
|
||||
Margin="0,2,0,2"
|
||||
Style="{StaticResource ConfigInput}"
|
||||
Text="{Binding ColumnSpacing, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="列间距(毫米)| Column spacing in mm" />
|
||||
<!-- 列间距错误提示 | Column spacing error message -->
|
||||
<TextBlock
|
||||
Style="{StaticResource ErrorText}"
|
||||
Text="{Binding ColSpacingError}" />
|
||||
|
||||
<!-- 操作按钮 | Action buttons -->
|
||||
<Button
|
||||
Command="{Binding UpdateLayoutCommand}"
|
||||
Content="更新布局 | Update"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="根据当前参数更新矩阵网格 | Update matrix grid with current parameters" />
|
||||
<Button
|
||||
Command="{Binding AssociateProgramCommand}"
|
||||
Content="关联程序 | Associate"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="关联 CNC 程序到矩阵 | Associate CNC program to matrix" />
|
||||
<Button
|
||||
Command="{Binding SaveLayoutCommand}"
|
||||
Content="保存方案 | Save"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="保存矩阵方案 | Save matrix layout" />
|
||||
<Button
|
||||
Command="{Binding LoadLayoutCommand}"
|
||||
Content="加载方案 | Load"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="加载矩阵方案 | Load matrix layout" />
|
||||
<!-- 分隔线 | Separator -->
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,4,0,8"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<!-- 分隔线 | Separator -->
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,8,0,8"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
<!-- 操作按钮 | Action buttons -->
|
||||
<Button
|
||||
Command="{Binding UpdateLayoutCommand}"
|
||||
Content="更新布局 | Update"
|
||||
IsEnabled="{Binding IsExecuting, Converter={StaticResource InverseBoolConverter}}"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="根据当前参数更新矩阵网格 | Update matrix grid with current parameters" />
|
||||
<Button
|
||||
Command="{Binding AssociateProgramCommand}"
|
||||
Content="关联程序 | Associate"
|
||||
IsEnabled="{Binding IsExecuting, Converter={StaticResource InverseBoolConverter}}"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="关联 CNC 程序到矩阵 | Associate CNC program to matrix" />
|
||||
<Button
|
||||
Command="{Binding SaveLayoutCommand}"
|
||||
Content="保存方案 | Save"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="保存矩阵方案 | Save matrix layout" />
|
||||
<Button
|
||||
Command="{Binding LoadLayoutCommand}"
|
||||
Content="加载方案 | Load"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="加载矩阵方案 | Load matrix layout" />
|
||||
|
||||
<!-- 选中单元格详情 | Selected cell details -->
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="11"
|
||||
FontWeight="Bold"
|
||||
Foreground="#555"
|
||||
Text="选中单元格 | Selected Cell" />
|
||||
<StackPanel DataContext="{Binding SelectedCell}">
|
||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
||||
<Run Foreground="#888" Text="位置 | Position: " />
|
||||
<Run Text="{Binding Row, Mode=OneWay, StringFormat='R{0}'}" />
|
||||
<Run Text=", " />
|
||||
<Run Text="{Binding Column, Mode=OneWay, StringFormat='C{0}'}" />
|
||||
<!-- 分隔线 | Separator -->
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,4,0,8"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<!-- 运行/停止按钮 | Run/Stop buttons -->
|
||||
<Button
|
||||
Command="{Binding RunMatrixCommand}"
|
||||
Content="▶ 运行矩阵 | Run"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="开始执行矩阵检测 | Start matrix execution" />
|
||||
<Button
|
||||
Command="{Binding StopCommand}"
|
||||
Content="■ 停止执行 | Stop"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="停止当前矩阵执行 | Stop current matrix execution"
|
||||
Visibility="{Binding IsExecuting, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<!-- 分隔线 | Separator -->
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,8,0,8"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<!-- 关联程序显示区域 | Associated program display area -->
|
||||
<TextBlock
|
||||
Margin="0,0,0,4"
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="11"
|
||||
FontWeight="Bold"
|
||||
Foreground="#555"
|
||||
Text="关联程序 | Program" />
|
||||
<TextBlock
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="11"
|
||||
Margin="0,0,0,8">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="{Binding AssociatedProgramName}" />
|
||||
<Setter Property="Foreground" Value="#333" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding AssociatedProgramName}" Value="{x:Null}">
|
||||
<Setter Property="Text" Value="请先关联 CNC 程序" />
|
||||
<Setter Property="Foreground" Value="#999" />
|
||||
<Setter Property="FontStyle" Value="Italic" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding AssociatedProgramName}" Value="">
|
||||
<Setter Property="Text" Value="请先关联 CNC 程序" />
|
||||
<Setter Property="Foreground" Value="#999" />
|
||||
<Setter Property="FontStyle" Value="Italic" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
||||
<Run Foreground="#888" Text="偏移 X | Offset X: " />
|
||||
<Run Text="{Binding OffsetX, Mode=OneWay, StringFormat='{}{0:F2}'}" />
|
||||
</TextBlock>
|
||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
||||
<Run Foreground="#888" Text="偏移 Y | Offset Y: " />
|
||||
<Run Text="{Binding OffsetY, Mode=OneWay, StringFormat='{}{0:F2}'}" />
|
||||
</TextBlock>
|
||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
||||
<Run Foreground="#888" Text="状态 | Enabled: " />
|
||||
<Run Text="{Binding IsEnabled, Mode=OneWay}" />
|
||||
|
||||
<!-- 分隔线 | Separator -->
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,0,0,8"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<!-- 选中单元格详情 | Selected cell details -->
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="11"
|
||||
FontWeight="Bold"
|
||||
Foreground="#555"
|
||||
Text="选中单元格 | Selected Cell" />
|
||||
<StackPanel DataContext="{Binding SelectedCell}">
|
||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
||||
<Run Foreground="#888" Text="位置 | Position: " />
|
||||
<Run Text="{Binding Row, Mode=OneWay, StringFormat='R{0}'}" />
|
||||
<Run Text=", " />
|
||||
<Run Text="{Binding Column, Mode=OneWay, StringFormat='C{0}'}" />
|
||||
</TextBlock>
|
||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
||||
<Run Foreground="#888" Text="偏移 X | Offset X: " />
|
||||
<Run Text="{Binding OffsetX, Mode=OneWay, StringFormat='{}{0:F2}'}" />
|
||||
</TextBlock>
|
||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
||||
<Run Foreground="#888" Text="偏移 Y | Offset Y: " />
|
||||
<Run Text="{Binding OffsetY, Mode=OneWay, StringFormat='{}{0:F2}'}" />
|
||||
</TextBlock>
|
||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
||||
<Run Foreground="#888" Text="状态 | Enabled: " />
|
||||
<Run Text="{Binding IsEnabled, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- 垂直分隔线 | Vertical separator -->
|
||||
<Rectangle
|
||||
Grid.Column="1"
|
||||
Width="1"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<!-- ═══ 右侧:矩阵网格视图 | Right: matrix grid view ═══ -->
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<!-- 网格标题与统计 | Grid title and statistics -->
|
||||
<RowDefinition Height="Auto" />
|
||||
<!-- 矩阵网格 | Matrix grid -->
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 矩阵网格标题和统计标签 | Matrix grid title and statistics label -->
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Margin="8,6,8,2"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="12"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333"
|
||||
VerticalAlignment="Center"
|
||||
Text="矩阵网格" />
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="11"
|
||||
Foreground="#666"
|
||||
VerticalAlignment="Center">
|
||||
<Run Text="已启用:" /><Run Text="{Binding EnabledCount, Mode=OneWay}" /><Run Text=" / 共 " /><Run Text="{Binding TotalCount, Mode=OneWay}" /><Run Text=" 个位置" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- 垂直分隔线 | Vertical separator -->
|
||||
<Rectangle
|
||||
Grid.Column="1"
|
||||
Width="1"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<!-- ═══ 右侧:矩阵网格视图 | Right: matrix grid view ═══ -->
|
||||
<ScrollViewer
|
||||
Grid.Column="2"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl
|
||||
Margin="8"
|
||||
ItemsSource="{Binding Cells}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<!-- 使用 UniformGrid 按行列排列单元格 | Use UniformGrid to arrange cells by rows and columns -->
|
||||
<UniformGrid Columns="{Binding Columns}" Rows="{Binding Rows}" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<!-- 单元格视觉模板 | Cell visual template -->
|
||||
<Border
|
||||
x:Name="CellBorder"
|
||||
MinWidth="80"
|
||||
MinHeight="64"
|
||||
Margin="2"
|
||||
Padding="4"
|
||||
Background="#F0F8FF"
|
||||
BorderBrush="#B0C4DE"
|
||||
BorderThickness="1"
|
||||
CornerRadius="3"
|
||||
Cursor="Hand">
|
||||
<Border.InputBindings>
|
||||
<!-- 左键点击选中单元格 | Left click to select cell -->
|
||||
<MouseBinding
|
||||
Command="{Binding DataContext.SelectCellCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
MouseAction="LeftClick" />
|
||||
</Border.InputBindings>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<!-- 行列索引 | Row/Column index -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="11"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#333">
|
||||
<Run Text="{Binding Row, Mode=OneWay, StringFormat='R{0}'}" />
|
||||
<Run Text=" " />
|
||||
<Run Text="{Binding Column, Mode=OneWay, StringFormat='C{0}'}" />
|
||||
</TextBlock>
|
||||
<!-- 偏移坐标 | Offset coordinates -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="9"
|
||||
Foreground="#666">
|
||||
<Run Text="{Binding OffsetX, Mode=OneWay, StringFormat='X:{0:F1}'}" />
|
||||
<Run Text=" " />
|
||||
<Run Text="{Binding OffsetY, Mode=OneWay, StringFormat='Y:{0:F1}'}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<!-- 启用/禁用切换按钮 | Enable/Disable toggle button -->
|
||||
<Button
|
||||
x:Name="ToggleBtn"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,2,0,0"
|
||||
Padding="4,1"
|
||||
Background="Transparent"
|
||||
<!-- 矩阵网格内容 | Matrix grid content -->
|
||||
<ScrollViewer
|
||||
Grid.Row="1"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl
|
||||
Margin="8"
|
||||
ItemsSource="{Binding Cells}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<!-- 使用 UniformGrid 按行列排列单元格 | Use UniformGrid to arrange cells by rows and columns -->
|
||||
<UniformGrid Columns="{Binding Columns}" Rows="{Binding Rows}" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<!-- 单元格视觉模板 | Cell visual template -->
|
||||
<Border
|
||||
x:Name="CellBorder"
|
||||
MinWidth="80"
|
||||
MinHeight="64"
|
||||
Margin="2"
|
||||
Padding="4"
|
||||
Background="White"
|
||||
BorderBrush="#B0C4DE"
|
||||
BorderThickness="1"
|
||||
Command="{Binding DataContext.ToggleCellCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="✓"
|
||||
Cursor="Hand"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="9"
|
||||
ToolTip="切换启用/禁用 | Toggle enabled/disabled" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<DataTemplate.Triggers>
|
||||
<!-- 禁用状态:灰色背景 | Disabled state: gray background -->
|
||||
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
|
||||
<Setter TargetName="CellBorder" Property="Background" Value="#E8E8E8" />
|
||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#CCCCCC" />
|
||||
<Setter TargetName="CellBorder" Property="Opacity" Value="0.6" />
|
||||
</DataTrigger>
|
||||
<!-- 选中状态:蓝色高亮 | Selected state: blue highlight -->
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter TargetName="CellBorder" Property="Background" Value="#E3F0FF" />
|
||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#5B9BD5" />
|
||||
<Setter TargetName="CellBorder" Property="BorderThickness" Value="2" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
CornerRadius="3"
|
||||
Cursor="Hand">
|
||||
<Border.InputBindings>
|
||||
<!-- 左键点击选中单元格 | Left click to select cell -->
|
||||
<MouseBinding
|
||||
Command="{Binding DataContext.SelectCellCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
MouseAction="LeftClick" />
|
||||
</Border.InputBindings>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<!-- 行列索引 | Row/Column index -->
|
||||
<TextBlock
|
||||
x:Name="CellIndexText"
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="11"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#333">
|
||||
<Run Text="{Binding Row, Mode=OneWay, StringFormat='R{0}'}" />
|
||||
<Run Text=" " />
|
||||
<Run Text="{Binding Column, Mode=OneWay, StringFormat='C{0}'}" />
|
||||
</TextBlock>
|
||||
<!-- 偏移坐标 | Offset coordinates -->
|
||||
<TextBlock
|
||||
x:Name="CellOffsetText"
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="9"
|
||||
Foreground="#666">
|
||||
<Run Text="{Binding OffsetX, Mode=OneWay, StringFormat='X:{0:F1}'}" />
|
||||
<Run Text=" " />
|
||||
<Run Text="{Binding OffsetY, Mode=OneWay, StringFormat='Y:{0:F1}'}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<!-- 启用/禁用切换按钮 | Enable/Disable toggle button -->
|
||||
<Button
|
||||
x:Name="ToggleBtn"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,2,0,0"
|
||||
Padding="4,1"
|
||||
Background="Transparent"
|
||||
BorderBrush="#B0C4DE"
|
||||
BorderThickness="1"
|
||||
Command="{Binding DataContext.ToggleCellCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="✓"
|
||||
Cursor="Hand"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="9"
|
||||
ToolTip="切换启用/禁用 | Toggle enabled/disabled" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<DataTemplate.Triggers>
|
||||
<!-- ═══ 执行状态颜色样式 | Execution status color styles ═══ -->
|
||||
|
||||
<!-- 未执行状态:默认白色背景 | NotExecuted: default white background -->
|
||||
<DataTrigger Binding="{Binding Status}" Value="{x:Static models:MatrixCellStatus.NotExecuted}">
|
||||
<Setter TargetName="CellBorder" Property="Background" Value="White" />
|
||||
</DataTrigger>
|
||||
|
||||
<!-- 执行中状态:蓝色高亮 | Executing: blue highlight -->
|
||||
<DataTrigger Binding="{Binding Status}" Value="{x:Static models:MatrixCellStatus.Executing}">
|
||||
<Setter TargetName="CellBorder" Property="Background" Value="#2196F3" />
|
||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#1565C0" />
|
||||
<Setter TargetName="CellIndexText" Property="Foreground" Value="White" />
|
||||
<Setter TargetName="CellOffsetText" Property="Foreground" Value="#E3F2FD" />
|
||||
</DataTrigger>
|
||||
|
||||
<!-- 已完成状态:绿色背景 | Completed: green background -->
|
||||
<DataTrigger Binding="{Binding Status}" Value="{x:Static models:MatrixCellStatus.Completed}">
|
||||
<Setter TargetName="CellBorder" Property="Background" Value="#4CAF50" />
|
||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#2E7D32" />
|
||||
<Setter TargetName="CellIndexText" Property="Foreground" Value="White" />
|
||||
<Setter TargetName="CellOffsetText" Property="Foreground" Value="#E8F5E9" />
|
||||
</DataTrigger>
|
||||
|
||||
<!-- 错误状态:红色背景 + ToolTip 显示错误信息 | Error: red background + ToolTip with error message -->
|
||||
<DataTrigger Binding="{Binding Status}" Value="{x:Static models:MatrixCellStatus.Error}">
|
||||
<Setter TargetName="CellBorder" Property="Background" Value="#F44336" />
|
||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#C62828" />
|
||||
<Setter TargetName="CellBorder" Property="ToolTip" Value="{Binding ErrorMessage}" />
|
||||
<Setter TargetName="CellIndexText" Property="Foreground" Value="White" />
|
||||
<Setter TargetName="CellOffsetText" Property="Foreground" Value="#FFEBEE" />
|
||||
</DataTrigger>
|
||||
|
||||
<!-- 已禁用状态:灰色半透明 | Disabled: gray semi-transparent -->
|
||||
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
|
||||
<Setter TargetName="CellBorder" Property="Background" Value="#E8E8E8" />
|
||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#CCCCCC" />
|
||||
<Setter TargetName="CellBorder" Property="Opacity" Value="0.6" />
|
||||
</DataTrigger>
|
||||
|
||||
<!-- 选中状态:蓝色高亮边框 | Selected state: blue highlight border -->
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#5B9BD5" />
|
||||
<Setter TargetName="CellBorder" Property="BorderThickness" Value="2" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- ═══ 底部状态栏 | Bottom status bar ═══ -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
BorderBrush="{StaticResource SeparatorBrush}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="8,4">
|
||||
<TextBlock
|
||||
FontFamily="{StaticResource CsdFont}"
|
||||
FontSize="11"
|
||||
Foreground="#555"
|
||||
Text="{Binding StatusText}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="系统设置"
|
||||
Width="1000"
|
||||
Width="800"
|
||||
Height="700"
|
||||
MinWidth="900"
|
||||
MinWidth="800"
|
||||
MinHeight="600"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ShowInTaskbar="False"
|
||||
|
||||
Reference in New Issue
Block a user