矩阵编排允许用户通过界面设定矩阵参数(行数、列数、行间距、列间距),将一个已编写好的 CNC 模板程序(.xp 文件)自动扩展为覆盖所有矩阵位置的完整检测序列,并按行优先顺序依次完成移轴→采图→检测的闭环执行
This commit is contained in:
@@ -632,6 +632,8 @@ namespace XplorePlane
|
|||||||
containerRegistry.RegisterSingleton<IInspectionResultStore, InspectionResultStore>();
|
containerRegistry.RegisterSingleton<IInspectionResultStore, InspectionResultStore>();
|
||||||
containerRegistry.RegisterSingleton<IImagePersistenceService, ImagePersistenceService>();
|
containerRegistry.RegisterSingleton<IImagePersistenceService, ImagePersistenceService>();
|
||||||
containerRegistry.RegisterSingleton<ICncExecutionService, CncExecutionService>();
|
containerRegistry.RegisterSingleton<ICncExecutionService, CncExecutionService>();
|
||||||
|
containerRegistry.RegisterSingleton<IMatrixOrchestrationService, MatrixOrchestrationService>();
|
||||||
|
containerRegistry.RegisterSingleton<MatrixSummaryWriter>();
|
||||||
|
|
||||||
// ── 主界面实时图像 / 探测器双队列服务(单例)──
|
// ── 主界面实时图像 / 探测器双队列服务(单例)──
|
||||||
containerRegistry.RegisterSingleton<IMainViewportService, MainViewportService>();
|
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,
|
string CncProgramPath,
|
||||||
IReadOnlyList<MatrixCell> Cells
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -24,6 +25,7 @@ namespace XplorePlane.Services.Matrix
|
|||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
Converters = { new JsonStringEnumConverter() }
|
Converters = { new JsonStringEnumConverter() }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,6 +42,8 @@ namespace XplorePlane.Services.Matrix
|
|||||||
public MatrixLayout CreateLayout(int rows, int columns, double rowSpacing, double columnSpacing)
|
public MatrixLayout CreateLayout(int rows, int columns, double rowSpacing, double columnSpacing)
|
||||||
{
|
{
|
||||||
ValidateDimensions(rows, columns);
|
ValidateDimensions(rows, columns);
|
||||||
|
ValidateSpacing(rowSpacing);
|
||||||
|
ValidateSpacing(columnSpacing);
|
||||||
|
|
||||||
var layout = new MatrixLayout(
|
var layout = new MatrixLayout(
|
||||||
Id: Guid.NewGuid(),
|
Id: Guid.NewGuid(),
|
||||||
@@ -62,6 +66,8 @@ namespace XplorePlane.Services.Matrix
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(layout);
|
ArgumentNullException.ThrowIfNull(layout);
|
||||||
ValidateDimensions(rows, columns);
|
ValidateDimensions(rows, columns);
|
||||||
|
ValidateSpacing(rowSpacing);
|
||||||
|
ValidateSpacing(columnSpacing);
|
||||||
|
|
||||||
var updated = layout with
|
var updated = layout with
|
||||||
{
|
{
|
||||||
@@ -249,8 +255,24 @@ namespace XplorePlane.Services.Matrix
|
|||||||
return cells.AsReadOnly();
|
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>
|
/// <summary>
|
||||||
/// 验证行列数参数 | Validate row and column dimensions
|
/// 验证行列数参数 | Validate row and column dimensions
|
||||||
|
/// Rows and Columns must be in [1, 50].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ValidateDimensions(int rows, int columns)
|
private static void ValidateDimensions(int rows, int columns)
|
||||||
{
|
{
|
||||||
@@ -258,9 +280,17 @@ namespace XplorePlane.Services.Matrix
|
|||||||
throw new ArgumentOutOfRangeException(nameof(rows),
|
throw new ArgumentOutOfRangeException(nameof(rows),
|
||||||
$"行数必须大于 0 | Rows must be greater than 0: {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)
|
if (columns <= 0)
|
||||||
throw new ArgumentOutOfRangeException(nameof(columns),
|
throw new ArgumentOutOfRangeException(nameof(columns),
|
||||||
$"列数必须大于 0 | Columns must be greater than 0: {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>
|
/// <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.Commands;
|
||||||
using Prism.Events;
|
using Prism.Events;
|
||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
using XplorePlane.Services.Cnc;
|
using XplorePlane.Services.Cnc;
|
||||||
@@ -20,12 +24,16 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
{
|
{
|
||||||
private readonly IMatrixService _matrixService;
|
private readonly IMatrixService _matrixService;
|
||||||
private readonly ICncProgramService _cncProgramService;
|
private readonly ICncProgramService _cncProgramService;
|
||||||
|
private readonly IMatrixOrchestrationService _orchestrationService;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
// 当前矩阵布局 | Current matrix layout
|
// 当前矩阵布局 | Current matrix layout
|
||||||
private MatrixLayout _currentLayout;
|
private MatrixLayout _currentLayout;
|
||||||
|
|
||||||
|
// 执行取消令牌源 | Execution cancellation token source
|
||||||
|
private CancellationTokenSource _executionCts;
|
||||||
|
|
||||||
private int _rows = 1;
|
private int _rows = 1;
|
||||||
private int _columns = 1;
|
private int _columns = 1;
|
||||||
private double _rowSpacing;
|
private double _rowSpacing;
|
||||||
@@ -34,29 +42,51 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
private MatrixCellViewModel _selectedCell;
|
private MatrixCellViewModel _selectedCell;
|
||||||
private string _associatedProgramPath;
|
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>
|
/// <summary>
|
||||||
/// 构造函数 | Constructor
|
/// 构造函数 | Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MatrixEditorViewModel(
|
public MatrixEditorViewModel(
|
||||||
IMatrixService matrixService,
|
IMatrixService matrixService,
|
||||||
ICncProgramService cncProgramService,
|
ICncProgramService cncProgramService,
|
||||||
|
IMatrixOrchestrationService orchestrationService,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
ILoggerService logger)
|
ILoggerService logger)
|
||||||
{
|
{
|
||||||
_matrixService = matrixService ?? throw new ArgumentNullException(nameof(matrixService));
|
_matrixService = matrixService ?? throw new ArgumentNullException(nameof(matrixService));
|
||||||
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
|
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
|
||||||
|
_orchestrationService = orchestrationService ?? throw new ArgumentNullException(nameof(orchestrationService));
|
||||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<MatrixEditorViewModel>();
|
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<MatrixEditorViewModel>();
|
||||||
|
|
||||||
_cells = new ObservableCollection<MatrixCellViewModel>();
|
_cells = new ObservableCollection<MatrixCellViewModel>();
|
||||||
|
|
||||||
// ── 命令初始化 | Command initialization ──
|
// ── 命令初始化 | Command initialization ──
|
||||||
UpdateLayoutCommand = new DelegateCommand(ExecuteUpdateLayout);
|
UpdateLayoutCommand = new DelegateCommand(ExecuteUpdateLayout, () => CanUpdateLayout)
|
||||||
|
.ObservesProperty(() => CanUpdateLayout);
|
||||||
ToggleCellCommand = new DelegateCommand<MatrixCellViewModel>(ExecuteToggleCell);
|
ToggleCellCommand = new DelegateCommand<MatrixCellViewModel>(ExecuteToggleCell);
|
||||||
SelectCellCommand = new DelegateCommand<MatrixCellViewModel>(ExecuteSelectCell);
|
SelectCellCommand = new DelegateCommand<MatrixCellViewModel>(ExecuteSelectCell);
|
||||||
AssociateProgramCommand = new DelegateCommand(async () => await ExecuteAssociateProgramAsync());
|
AssociateProgramCommand = new DelegateCommand(async () => await ExecuteAssociateProgramAsync());
|
||||||
SaveLayoutCommand = new DelegateCommand(async () => await ExecuteSaveLayoutAsync());
|
SaveLayoutCommand = new DelegateCommand(async () => await ExecuteSaveLayoutAsync());
|
||||||
LoadLayoutCommand = new DelegateCommand(async () => await ExecuteLoadLayoutAsync());
|
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");
|
_logger.Info("MatrixEditorViewModel 已初始化 | MatrixEditorViewModel initialized");
|
||||||
}
|
}
|
||||||
@@ -109,7 +139,89 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
public string AssociatedProgramPath
|
public string AssociatedProgramPath
|
||||||
{
|
{
|
||||||
get => _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 ────────────────────────────────────────────
|
// ── 命令 | Commands ────────────────────────────────────────────
|
||||||
@@ -132,6 +244,12 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
/// <summary>加载矩阵布局命令 | Load matrix layout command</summary>
|
/// <summary>加载矩阵布局命令 | Load matrix layout command</summary>
|
||||||
public DelegateCommand LoadLayoutCommand { get; }
|
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 ────────────────────
|
// ── 命令执行方法 | Command execution methods ────────────────────
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -140,6 +258,13 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void ExecuteUpdateLayout()
|
private void ExecuteUpdateLayout()
|
||||||
{
|
{
|
||||||
|
// 清除之前的错误 | Clear previous errors
|
||||||
|
RowsError = null;
|
||||||
|
ColumnsError = null;
|
||||||
|
RowSpacingError = null;
|
||||||
|
ColSpacingError = null;
|
||||||
|
CanUpdateLayout = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_currentLayout == null)
|
if (_currentLayout == null)
|
||||||
@@ -154,12 +279,56 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
RefreshCells();
|
RefreshCells();
|
||||||
_logger.Info("已更新矩阵布局 | Updated matrix layout: Rows={Rows}, Columns={Columns}", Rows, Columns);
|
_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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "更新矩阵布局失败 | Failed to update matrix layout");
|
_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>
|
/// <summary>
|
||||||
/// 选中指定单元格,更新高亮状态
|
/// 选中指定单元格,更新高亮状态
|
||||||
/// Select the specified cell and update highlight state
|
/// Select the specified cell and update highlight state
|
||||||
@@ -199,8 +368,8 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 关联 CNC 程序到当前矩阵布局(占位:从 AssociatedProgramPath 加载)
|
/// 关联 CNC 程序:使用 OpenFileDialog 选择 .xp 文件,验证文件有效性
|
||||||
/// Associate a CNC program with the current layout (placeholder: loads from AssociatedProgramPath)
|
/// Associate CNC program: use OpenFileDialog to select .xp file, validate file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ExecuteAssociateProgramAsync()
|
private async Task ExecuteAssociateProgramAsync()
|
||||||
{
|
{
|
||||||
@@ -210,26 +379,50 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
return;
|
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;
|
return;
|
||||||
}
|
|
||||||
|
var filePath = dialog.FileName;
|
||||||
|
var fileName = Path.GetFileName(filePath);
|
||||||
|
|
||||||
try
|
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);
|
_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)
|
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>
|
/// <summary>
|
||||||
/// 保存当前矩阵布局到文件 | Save current matrix layout to file
|
/// 保存当前矩阵布局:使用 SaveFileDialog 选择保存路径
|
||||||
|
/// Save current matrix layout: use SaveFileDialog to select save path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ExecuteSaveLayoutAsync()
|
private async Task ExecuteSaveLayoutAsync()
|
||||||
{
|
{
|
||||||
@@ -239,12 +432,21 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dialog = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Title = "保存矩阵方案",
|
||||||
|
Filter = "JSON 文件 (*.json)|*.json",
|
||||||
|
DefaultExt = ".json",
|
||||||
|
FileName = $"matrix_{_currentLayout.Id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 占位:实际应通过文件对话框选择路径 | Placeholder: should use file dialog to select path
|
await _matrixService.SaveAsync(_currentLayout, dialog.FileName);
|
||||||
var filePath = $"matrix_{_currentLayout.Id}.json";
|
_logger.Info("矩阵布局已保存 | Matrix layout saved: {FilePath}", dialog.FileName);
|
||||||
await _matrixService.SaveAsync(_currentLayout, filePath);
|
|
||||||
_logger.Info("矩阵布局已保存 | Matrix layout saved: {FilePath}", filePath);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -253,15 +455,24 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从文件加载矩阵布局 | Load matrix layout from file
|
/// 从文件加载矩阵布局:使用 OpenFileDialog 选择文件
|
||||||
|
/// Load matrix layout from file: use OpenFileDialog to select file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ExecuteLoadLayoutAsync()
|
private async Task ExecuteLoadLayoutAsync()
|
||||||
{
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "加载矩阵方案",
|
||||||
|
Filter = "JSON 文件 (*.json)|*.json",
|
||||||
|
CheckFileExists = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 占位:实际应通过文件对话框选择路径 | Placeholder: should use file dialog to select path
|
_currentLayout = await _matrixService.LoadAsync(dialog.FileName);
|
||||||
var filePath = $"matrix_layout.json";
|
|
||||||
_currentLayout = await _matrixService.LoadAsync(filePath);
|
|
||||||
|
|
||||||
// 同步属性到 ViewModel | Sync properties to ViewModel
|
// 同步属性到 ViewModel | Sync properties to ViewModel
|
||||||
Rows = _currentLayout.Rows;
|
Rows = _currentLayout.Rows;
|
||||||
@@ -271,14 +482,132 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
AssociatedProgramPath = _currentLayout.CncProgramPath;
|
AssociatedProgramPath = _currentLayout.CncProgramPath;
|
||||||
|
|
||||||
RefreshCells();
|
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);
|
_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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "加载矩阵布局失败 | Failed to load matrix layout");
|
_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 ───────────────────────────────────
|
// ── 辅助方法 | Helper methods ───────────────────────────────────
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -290,13 +619,21 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
Cells.Clear();
|
Cells.Clear();
|
||||||
|
|
||||||
if (_currentLayout?.Cells == null)
|
if (_currentLayout?.Cells == null)
|
||||||
|
{
|
||||||
|
EnabledCount = 0;
|
||||||
|
TotalCount = 0;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var cell in _currentLayout.Cells)
|
foreach (var cell in _currentLayout.Cells)
|
||||||
{
|
{
|
||||||
Cells.Add(new MatrixCellViewModel(cell));
|
Cells.Add(new MatrixCellViewModel(cell));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新统计 | Update statistics
|
||||||
|
TotalCount = Cells.Count;
|
||||||
|
EnabledCount = Cells.Count(c => c.IsEnabled);
|
||||||
|
|
||||||
// 尝试保持选中状态 | Try to preserve selection
|
// 尝试保持选中状态 | Try to preserve selection
|
||||||
if (SelectedCell != null)
|
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:prism="http://prismlibrary.com/"
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:models="clr-namespace:XplorePlane.Models"
|
||||||
|
xmlns:converters="clr-namespace:XplorePlane.Converters"
|
||||||
d:DesignHeight="700"
|
d:DesignHeight="700"
|
||||||
d:DesignWidth="900"
|
d:DesignWidth="900"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
@@ -19,6 +21,10 @@
|
|||||||
<SolidColorBrush x:Key="AccentBlue" Color="#E3F0FF" />
|
<SolidColorBrush x:Key="AccentBlue" Color="#E3F0FF" />
|
||||||
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
|
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
|
||||||
|
|
||||||
|
<!-- 转换器 | Converters -->
|
||||||
|
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||||
|
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||||
|
|
||||||
<!-- 配置面板标签样式 | Configuration panel label style -->
|
<!-- 配置面板标签样式 | Configuration panel label style -->
|
||||||
<Style x:Key="ConfigLabel" TargetType="TextBlock">
|
<Style x:Key="ConfigLabel" TargetType="TextBlock">
|
||||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
||||||
@@ -38,6 +44,15 @@
|
|||||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
</Style>
|
</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) -->
|
<!-- 工具栏按钮样式(与 CncPageView 一致)| Toolbar button style (consistent with CncPageView) -->
|
||||||
<Style x:Key="ToolbarBtn" TargetType="Button">
|
<Style x:Key="ToolbarBtn" TargetType="Button">
|
||||||
<Setter Property="Height" Value="28" />
|
<Setter Property="Height" Value="28" />
|
||||||
@@ -58,236 +73,401 @@
|
|||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="4">
|
CornerRadius="4">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<!-- 左侧:配置面板 | Left: configuration panel -->
|
<!-- 主内容区域 | Main content area -->
|
||||||
<ColumnDefinition Width="220" />
|
<RowDefinition Height="*" />
|
||||||
<!-- 分隔线 | Splitter -->
|
<!-- 底部状态栏 | Bottom status bar -->
|
||||||
<ColumnDefinition Width="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<!-- 右侧:矩阵网格 | Right: matrix grid -->
|
</Grid.RowDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<!-- ═══ 左侧:矩阵配置面板 | Left: matrix configuration panel ═══ -->
|
<Grid Grid.Row="0">
|
||||||
<ScrollViewer
|
<Grid.ColumnDefinitions>
|
||||||
Grid.Column="0"
|
<!-- 左侧:配置面板 | Left: configuration panel -->
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
<ColumnDefinition Width="220" />
|
||||||
VerticalScrollBarVisibility="Auto">
|
<!-- 分隔线 | Splitter -->
|
||||||
<StackPanel Margin="10,8">
|
<ColumnDefinition Width="Auto" />
|
||||||
<!-- 面板标题 | Panel title -->
|
<!-- 右侧:矩阵网格 | Right: matrix grid -->
|
||||||
<TextBlock
|
<ColumnDefinition Width="*" />
|
||||||
Margin="0,0,0,10"
|
</Grid.ColumnDefinitions>
|
||||||
FontFamily="{StaticResource CsdFont}"
|
|
||||||
FontSize="13"
|
|
||||||
FontWeight="Bold"
|
|
||||||
Foreground="#333"
|
|
||||||
Text="矩阵配置 | Matrix Config" />
|
|
||||||
|
|
||||||
<!-- 行数 | Rows -->
|
<!-- ═══ 左侧:矩阵配置面板 | Left: matrix configuration panel ═══ -->
|
||||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="行数 | Rows" />
|
<ScrollViewer
|
||||||
<TextBox
|
Grid.Column="0"
|
||||||
Margin="0,2,0,8"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
Style="{StaticResource ConfigInput}"
|
VerticalScrollBarVisibility="Auto">
|
||||||
Text="{Binding Rows, UpdateSourceTrigger=PropertyChanged}"
|
<StackPanel Margin="10,8">
|
||||||
ToolTip="矩阵行数 | Number of rows" />
|
<!-- 面板标题 | Panel title -->
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
FontFamily="{StaticResource CsdFont}"
|
||||||
|
FontSize="13"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="#333"
|
||||||
|
Text="矩阵配置 | Matrix Config" />
|
||||||
|
|
||||||
<!-- 列数 | Columns -->
|
<!-- 行数 | Rows -->
|
||||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="列数 | Columns" />
|
<TextBlock Style="{StaticResource ConfigLabel}" Text="行数 | Rows" />
|
||||||
<TextBox
|
<TextBox
|
||||||
Margin="0,2,0,8"
|
Margin="0,2,0,2"
|
||||||
Style="{StaticResource ConfigInput}"
|
Style="{StaticResource ConfigInput}"
|
||||||
Text="{Binding Columns, UpdateSourceTrigger=PropertyChanged}"
|
Text="{Binding Rows, UpdateSourceTrigger=PropertyChanged}"
|
||||||
ToolTip="矩阵列数 | Number of columns" />
|
ToolTip="矩阵行数 | Number of rows" />
|
||||||
|
<!-- 行数错误提示 | Rows error message -->
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource ErrorText}"
|
||||||
|
Text="{Binding RowsError}" />
|
||||||
|
|
||||||
<!-- 行间距 | Row Spacing -->
|
<!-- 列数 | Columns -->
|
||||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="行间距 (mm) | Row Spacing" />
|
<TextBlock Style="{StaticResource ConfigLabel}" Text="列数 | Columns" />
|
||||||
<TextBox
|
<TextBox
|
||||||
Margin="0,2,0,8"
|
Margin="0,2,0,2"
|
||||||
Style="{StaticResource ConfigInput}"
|
Style="{StaticResource ConfigInput}"
|
||||||
Text="{Binding RowSpacing, UpdateSourceTrigger=PropertyChanged}"
|
Text="{Binding Columns, UpdateSourceTrigger=PropertyChanged}"
|
||||||
ToolTip="行间距(毫米)| Row spacing in mm" />
|
ToolTip="矩阵列数 | Number of columns" />
|
||||||
|
<!-- 列数错误提示 | Columns error message -->
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource ErrorText}"
|
||||||
|
Text="{Binding ColumnsError}" />
|
||||||
|
|
||||||
<!-- 列间距 | Column Spacing -->
|
<!-- 行间距 | Row Spacing -->
|
||||||
<TextBlock Style="{StaticResource ConfigLabel}" Text="列间距 (mm) | Col Spacing" />
|
<TextBlock Style="{StaticResource ConfigLabel}" Text="行间距 (mm) | Row Spacing" />
|
||||||
<TextBox
|
<TextBox
|
||||||
Margin="0,2,0,8"
|
Margin="0,2,0,2"
|
||||||
Style="{StaticResource ConfigInput}"
|
Style="{StaticResource ConfigInput}"
|
||||||
Text="{Binding ColumnSpacing, UpdateSourceTrigger=PropertyChanged}"
|
Text="{Binding RowSpacing, UpdateSourceTrigger=PropertyChanged}"
|
||||||
ToolTip="列间距(毫米)| Column spacing in mm" />
|
ToolTip="行间距(毫米)| Row spacing in mm" />
|
||||||
|
<!-- 行间距错误提示 | Row spacing error message -->
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource ErrorText}"
|
||||||
|
Text="{Binding RowSpacingError}" />
|
||||||
|
|
||||||
<!-- 分隔线 | Separator -->
|
<!-- 列间距 | Column Spacing -->
|
||||||
<Rectangle
|
<TextBlock Style="{StaticResource ConfigLabel}" Text="列间距 (mm) | Col Spacing" />
|
||||||
Height="1"
|
<TextBox
|
||||||
Margin="0,4,0,8"
|
Margin="0,2,0,2"
|
||||||
Fill="{StaticResource SeparatorBrush}" />
|
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 -->
|
<!-- 分隔线 | Separator -->
|
||||||
<Button
|
<Rectangle
|
||||||
Command="{Binding UpdateLayoutCommand}"
|
Height="1"
|
||||||
Content="更新布局 | Update"
|
Margin="0,4,0,8"
|
||||||
Style="{StaticResource ToolbarBtn}"
|
Fill="{StaticResource SeparatorBrush}" />
|
||||||
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 -->
|
<!-- 操作按钮 | Action buttons -->
|
||||||
<Rectangle
|
<Button
|
||||||
Height="1"
|
Command="{Binding UpdateLayoutCommand}"
|
||||||
Margin="0,8,0,8"
|
Content="更新布局 | Update"
|
||||||
Fill="{StaticResource SeparatorBrush}" />
|
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 -->
|
<!-- 分隔线 | Separator -->
|
||||||
<TextBlock
|
<Rectangle
|
||||||
Margin="0,0,0,6"
|
Height="1"
|
||||||
FontFamily="{StaticResource CsdFont}"
|
Margin="0,4,0,8"
|
||||||
FontSize="11"
|
Fill="{StaticResource SeparatorBrush}" />
|
||||||
FontWeight="Bold"
|
|
||||||
Foreground="#555"
|
<!-- 运行/停止按钮 | Run/Stop buttons -->
|
||||||
Text="选中单元格 | Selected Cell" />
|
<Button
|
||||||
<StackPanel DataContext="{Binding SelectedCell}">
|
Command="{Binding RunMatrixCommand}"
|
||||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
Content="▶ 运行矩阵 | Run"
|
||||||
<Run Foreground="#888" Text="位置 | Position: " />
|
Style="{StaticResource ToolbarBtn}"
|
||||||
<Run Text="{Binding Row, Mode=OneWay, StringFormat='R{0}'}" />
|
ToolTip="开始执行矩阵检测 | Start matrix execution" />
|
||||||
<Run Text=", " />
|
<Button
|
||||||
<Run Text="{Binding Column, Mode=OneWay, StringFormat='C{0}'}" />
|
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>
|
||||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
|
||||||
<Run Foreground="#888" Text="偏移 X | Offset X: " />
|
<!-- 分隔线 | Separator -->
|
||||||
<Run Text="{Binding OffsetX, Mode=OneWay, StringFormat='{}{0:F2}'}" />
|
<Rectangle
|
||||||
</TextBlock>
|
Height="1"
|
||||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
Margin="0,0,0,8"
|
||||||
<Run Foreground="#888" Text="偏移 Y | Offset Y: " />
|
Fill="{StaticResource SeparatorBrush}" />
|
||||||
<Run Text="{Binding OffsetY, Mode=OneWay, StringFormat='{}{0:F2}'}" />
|
|
||||||
</TextBlock>
|
<!-- 选中单元格详情 | Selected cell details -->
|
||||||
<TextBlock FontFamily="{StaticResource CsdFont}" FontSize="11">
|
<TextBlock
|
||||||
<Run Foreground="#888" Text="状态 | Enabled: " />
|
Margin="0,0,0,6"
|
||||||
<Run Text="{Binding IsEnabled, Mode=OneWay}" />
|
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>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
|
|
||||||
<!-- 垂直分隔线 | Vertical separator -->
|
<!-- 矩阵网格内容 | Matrix grid content -->
|
||||||
<Rectangle
|
<ScrollViewer
|
||||||
Grid.Column="1"
|
Grid.Row="1"
|
||||||
Width="1"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
Fill="{StaticResource SeparatorBrush}" />
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<ItemsControl
|
||||||
<!-- ═══ 右侧:矩阵网格视图 | Right: matrix grid view ═══ -->
|
Margin="8"
|
||||||
<ScrollViewer
|
ItemsSource="{Binding Cells}">
|
||||||
Grid.Column="2"
|
<ItemsControl.ItemsPanel>
|
||||||
HorizontalScrollBarVisibility="Auto"
|
<ItemsPanelTemplate>
|
||||||
VerticalScrollBarVisibility="Auto">
|
<!-- 使用 UniformGrid 按行列排列单元格 | Use UniformGrid to arrange cells by rows and columns -->
|
||||||
<ItemsControl
|
<UniformGrid Columns="{Binding Columns}" Rows="{Binding Rows}" />
|
||||||
Margin="8"
|
</ItemsPanelTemplate>
|
||||||
ItemsSource="{Binding Cells}">
|
</ItemsControl.ItemsPanel>
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemTemplate>
|
||||||
<ItemsPanelTemplate>
|
<DataTemplate>
|
||||||
<!-- 使用 UniformGrid 按行列排列单元格 | Use UniformGrid to arrange cells by rows and columns -->
|
<!-- 单元格视觉模板 | Cell visual template -->
|
||||||
<UniformGrid Columns="{Binding Columns}" Rows="{Binding Rows}" />
|
<Border
|
||||||
</ItemsPanelTemplate>
|
x:Name="CellBorder"
|
||||||
</ItemsControl.ItemsPanel>
|
MinWidth="80"
|
||||||
<ItemsControl.ItemTemplate>
|
MinHeight="64"
|
||||||
<DataTemplate>
|
Margin="2"
|
||||||
<!-- 单元格视觉模板 | Cell visual template -->
|
Padding="4"
|
||||||
<Border
|
Background="White"
|
||||||
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"
|
|
||||||
BorderBrush="#B0C4DE"
|
BorderBrush="#B0C4DE"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Command="{Binding DataContext.ToggleCellCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
CornerRadius="3"
|
||||||
CommandParameter="{Binding}"
|
Cursor="Hand">
|
||||||
Content="✓"
|
<Border.InputBindings>
|
||||||
Cursor="Hand"
|
<!-- 左键点击选中单元格 | Left click to select cell -->
|
||||||
FontFamily="Microsoft YaHei UI"
|
<MouseBinding
|
||||||
FontSize="9"
|
Command="{Binding DataContext.SelectCellCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||||
ToolTip="切换启用/禁用 | Toggle enabled/disabled" />
|
CommandParameter="{Binding}"
|
||||||
</Grid>
|
MouseAction="LeftClick" />
|
||||||
</Border>
|
</Border.InputBindings>
|
||||||
<DataTemplate.Triggers>
|
<Grid>
|
||||||
<!-- 禁用状态:灰色背景 | Disabled state: gray background -->
|
<Grid.RowDefinitions>
|
||||||
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
|
<RowDefinition Height="*" />
|
||||||
<Setter TargetName="CellBorder" Property="Background" Value="#E8E8E8" />
|
<RowDefinition Height="Auto" />
|
||||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#CCCCCC" />
|
</Grid.RowDefinitions>
|
||||||
<Setter TargetName="CellBorder" Property="Opacity" Value="0.6" />
|
<StackPanel
|
||||||
</DataTrigger>
|
Grid.Row="0"
|
||||||
<!-- 选中状态:蓝色高亮 | Selected state: blue highlight -->
|
HorizontalAlignment="Center"
|
||||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
VerticalAlignment="Center">
|
||||||
<Setter TargetName="CellBorder" Property="Background" Value="#E3F0FF" />
|
<!-- 行列索引 | Row/Column index -->
|
||||||
<Setter TargetName="CellBorder" Property="BorderBrush" Value="#5B9BD5" />
|
<TextBlock
|
||||||
<Setter TargetName="CellBorder" Property="BorderThickness" Value="2" />
|
x:Name="CellIndexText"
|
||||||
</DataTrigger>
|
HorizontalAlignment="Center"
|
||||||
</DataTemplate.Triggers>
|
FontFamily="Microsoft YaHei UI"
|
||||||
</DataTemplate>
|
FontSize="11"
|
||||||
</ItemsControl.ItemTemplate>
|
FontWeight="SemiBold"
|
||||||
</ItemsControl>
|
Foreground="#333">
|
||||||
</ScrollViewer>
|
<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>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="系统设置"
|
Title="系统设置"
|
||||||
Width="1000"
|
Width="800"
|
||||||
Height="700"
|
Height="700"
|
||||||
MinWidth="900"
|
MinWidth="800"
|
||||||
MinHeight="600"
|
MinHeight="600"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
ShowInTaskbar="False"
|
ShowInTaskbar="False"
|
||||||
|
|||||||
Reference in New Issue
Block a user