This commit is contained in:
2026-03-02 11:20:08 +08:00
parent 74338fdb3a
commit 5be18ded2e
21 changed files with 5984 additions and 224 deletions

View File

@@ -25,11 +25,42 @@ namespace CapMachine.Wpf.ViewModels
/// <summary>
/// ZLG CAN/CANFD 合并配置 ViewModel模式切换单选
/// </summary>
/// <remarks>
/// 该 ViewModel 负责 ZLG CAN/CANFD 的 UI 配置与用户交互编排,典型职责包括:
/// - 打开/关闭设备、切换模式CAN/CANFD并与 <see cref="ZlgCanDriveService"/> 同步状态;
/// - 加载 DBC 并维护可绑定的信号集合(用于读写设置与实时显示);
/// - 管理“读写设置”弹窗的数据准备与回写(写入/读取规则、信号候选集合);
/// - 管理调度表/循环发送相关 UI 状态(软件调度 vs 硬件 auto_send 的选择由 Service/Driver 实现)。
///
/// 线程说明:
/// - 本类绝大多数方法由 UI 线程调用;
/// - Driver 接收线程产生的数据更新事件若需要影响 UI应通过属性通知或 Dispatcher 进行线程切换(具体由 Service/Driver 侧处理)。
/// </remarks>
public class ZlgCanDriveConfigViewModel : NavigationViewModel
{
/// <summary>
/// 构造函数。
/// </summary>
/// <param name="dialogService">Prism 弹窗服务,用于打开读写设置等对话框。</param>
/// <param name="freeSql">数据访问组件,用于读取/保存配置程序与读写配置等持久化数据。</param>
/// <param name="eventAggregator">事件聚合器,用于跨模块消息通知(如状态变化/日志等)。</param>
/// <param name="regionManager">区域导航服务,用于页面/视图切换。</param>
/// <param name="sysRunService">系统运行服务(与全局运行态/权限等相关)。</param>
/// <param name="comActionService">通信互斥控制服务,用于保证 CAN/LIN 等通道操作的互斥与安全。</param>
/// <param name="logService">日志服务,用于输出 UI 层操作过程与异常。</param>
/// <param name="logicRuleService">逻辑规则服务,为“写入规则/触发规则”下拉框提供数据源。</param>
/// <param name="configService">配置服务,用于读取/应用系统配置参数。</param>
/// <param name="zlgCanDriveService">ZLG CAN/CANFD 服务层负责驱动生命周期、DBC、发送/调度表编排。</param>
/// <param name="zlgLinDriveService">ZLG LIN 服务层,用于互斥判断或组合配置场景。</param>
/// <param name="mapper">对象映射器,用于 DTO/实体之间转换。</param>
/// <remarks>
/// 构造时会初始化:
/// - 波特率下拉数据源;
/// - 写入规则下拉数据源;
/// - CAN 配置程序列表(用于 UI 选择)。
///
/// 注意:构造函数仅做“数据源初始化与依赖注入”,不会直接打开设备。
/// </remarks>
public ZlgCanDriveConfigViewModel(IDialogService dialogService, IFreeSql freeSql,
IEventAggregator eventAggregator, IRegionManager regionManager, SysRunService sysRunService,
ComActionService comActionService, ILogService logService, LogicRuleService logicRuleService,
@@ -98,6 +129,16 @@ namespace CapMachine.Wpf.ViewModels
InitLoadCanConfigPro();
}
/// <summary>
/// 初始化“写入规则”下拉框数据源。
/// </summary>
/// <remarks>
/// 数据源来自 <see cref="LogicRuleService.LogicRuleDtos"/>。
/// - Key规则 Id
/// - Text规则名称
///
/// 该集合通常被“读写设置”弹窗中的 ComboBox 绑定使用。
/// </remarks>
private void InitWriteRuleCbx()
{
WriteRuleCbxItems = new ObservableCollection<CbxItems>();
@@ -111,6 +152,20 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 打开“读写设置”弹窗。
/// </summary>
/// <remarks>
/// 弹窗数据准备流程:
/// - 基于当前页面的 <see cref="ListWriteCanLinRWConfigDto"/> / <see cref="ListReadCanLinRWConfigDto"/> 克隆出可编辑副本;
/// - 根据 <see cref="ListCanDbcModel"/> 构建信号候选集合MsgName/SignalName/描述等);
/// - 通过 <see cref="DialogService"/> 打开 <c>DialogZlgCanLinRwConfigView</c>
/// - 用户点击 OK 后回调中调用 <c>ReloadCurrentConfigPro</c> 刷新当前配置并提示。
///
/// 前置条件:
/// - 必须先选择配置程序(<see cref="SelectCanLinConfigPro"/> 不为 null
/// - 推荐调用方在进入本方法前确保已连接设备且 DBC 已加载,否则候选信号集合可能为空。
/// </remarks>
private void OpenRwDialog()
{
try
@@ -159,6 +214,7 @@ namespace CapMachine.Wpf.ViewModels
{ "Title", "读写设置" },
{ "CanLinConfigProId", SelectCanLinConfigPro.Id },
{ "IsEditable", IsRwEditable },
{ "IsCanFdSchedule", SelectedMode == ZlgCanMode.CanFd },
{ "WriteConfigs", writeClones },
{ "ReadConfigs", readClones },
{ "SignalCandidates", candidates },
@@ -180,6 +236,16 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 克隆一个读写配置 DTO。
/// </summary>
/// <param name="src">源对象。</param>
/// <returns>新实例(字段逐个复制)。</returns>
/// <remarks>
/// 目的:
/// - 弹窗编辑时不直接修改页面当前集合,避免用户取消时污染原始配置;
/// - 用户确认OK后再由弹窗 ViewModel 负责持久化,页面侧随后通过 Reload 刷新。
/// </remarks>
private static CanLinRWConfigDto CloneRwDto(CanLinRWConfigDto src)
{
return new CanLinRWConfigDto
@@ -255,6 +321,9 @@ namespace CapMachine.Wpf.ViewModels
/// </summary>
public IMapper Mapper { get; }
/// <summary>
/// <see cref="ArbBaudRateCbxItems"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CbxItems> _arbBaudRateCbxItems = new ObservableCollection<CbxItems>();
/// <summary>
/// 仲裁波特率下拉项bps
@@ -265,6 +334,9 @@ namespace CapMachine.Wpf.ViewModels
set { _arbBaudRateCbxItems = value; RaisePropertyChanged(); }
}
/// <summary>
/// <see cref="DataBaudRateCbxItems"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CbxItems> _dataBaudRateCbxItems = new ObservableCollection<CbxItems>();
/// <summary>
/// 数据波特率下拉项bps
@@ -275,9 +347,23 @@ namespace CapMachine.Wpf.ViewModels
set { _dataBaudRateCbxItems = value; RaisePropertyChanged(); }
}
/// <summary>
/// 配置程序列表的本地缓存(数据库读取结果)。
/// </summary>
/// <remarks>
/// 与 <see cref="ListCanLinConfigPro"/> 的区别:
/// - 本字段用于保存从数据库一次性读取的实体列表;
/// - UI 绑定通常使用 <see cref="ListCanLinConfigPro"/>ObservableCollection
/// </remarks>
private List<CanLinConfigPro> _canLinConfigPros = new List<CanLinConfigPro>();
private ObservableCollection<CbxItems> _writeRuleCbxItems = new ObservableCollection<CbxItems>();
/// <summary>
/// 写入规则下拉项(逻辑规则列表)。
/// </summary>
/// <remarks>
/// 该集合由 <see cref="InitWriteRuleCbx"/> 初始化,通常绑定到读写设置弹窗或列表编辑处的 ComboBox。
/// </remarks>
public ObservableCollection<CbxItems> WriteRuleCbxItems
{
get { return _writeRuleCbxItems; }
@@ -285,6 +371,19 @@ namespace CapMachine.Wpf.ViewModels
}
private ObservableCollection<CanLinRWConfigDto> _listWriteCanLinRWConfigDto = new ObservableCollection<CanLinRWConfigDto>();
/// <summary>
/// 写入配置集合(绑定到写入 DataGrid
/// </summary>
/// <remarks>
/// 每条记录对应一个“写入信号”配置项,包含:
/// - MsgFrameName/SignalName定位 DBC 信号
/// - LogicRuleId写入规则
/// - DefautValue默认写入值
///
/// 注意:
/// - 配置程序激活(<see cref="IsCanConfigProActive"/> true时应禁止编辑。
/// - 读写互斥/重复校验主要在读写设置弹窗 ViewModel 中实现。
/// </remarks>
public ObservableCollection<CanLinRWConfigDto> ListWriteCanLinRWConfigDto
{
get { return _listWriteCanLinRWConfigDto; }
@@ -292,13 +391,36 @@ namespace CapMachine.Wpf.ViewModels
}
private ObservableCollection<CanLinRWConfigDto> _listReadCanLinRWConfigDto = new ObservableCollection<CanLinRWConfigDto>();
/// <summary>
/// 读取配置集合(绑定到读取 DataGrid
/// </summary>
/// <remarks>
/// 每条记录对应一个“读取信号”配置项,包含:
/// - MsgFrameName/SignalName定位 DBC 信号
/// - DefautValue默认值/初始值(视业务语义而定)
///
/// 读取配置通常用于“从接收帧解码后更新 UI/变量”的目标信号集合。
/// </remarks>
public ObservableCollection<CanLinRWConfigDto> ListReadCanLinRWConfigDto
{
get { return _listReadCanLinRWConfigDto; }
set { _listReadCanLinRWConfigDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// 当前在写入 DataGrid 中选中的行。
/// </summary>
/// <remarks>
/// 用于 Delete 等操作定位目标记录。该属性不直接通知 UI由 SelectionChanged 命令更新)。
/// </remarks>
private CanLinRWConfigDto? SelectedWriteCanLinRWConfigDto { get; set; }
/// <summary>
/// 当前在读取 DataGrid 中选中的行。
/// </summary>
/// <remarks>
/// 用于 Delete 等操作定位目标记录。该属性不直接通知 UI由 SelectionChanged 命令更新)。
/// </remarks>
private CanLinRWConfigDto? SelectedReadCanLinRWConfigDto { get; set; }
private string? _opTip;
@@ -352,6 +474,9 @@ namespace CapMachine.Wpf.ViewModels
set { _isCANConfigDatagridActive = value; RaisePropertyChanged(); }
}
/// <summary>
/// <see cref="SelectedMode"/> 的 backing 字段。
/// </summary>
private ZlgCanMode _selectedMode;
/// <summary>
/// 模式选择CAN/CANFD单选
@@ -431,7 +556,16 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 写入 DataGrid 选中项变化命令。
/// </summary>
/// <remarks>
/// 由 XAML 绑定到 SelectionChanged用于把当前选中行同步到 <see cref="SelectedWriteCanLinRWConfigDto"/>。
/// </remarks>
private DelegateCommand<object>? _writeGridSelectionChangedCmd;
/// <summary>
/// 写入 DataGrid 选中项变化命令(供 XAML 绑定)。
/// </summary>
public DelegateCommand<object> WriteGridSelectionChangedCmd
{
get
@@ -444,6 +578,10 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 写入 DataGrid SelectionChanged 命令处理。
/// </summary>
/// <param name="par">选中项(期望为 <see cref="CanLinRWConfigDto"/>)。</param>
private void WriteGridSelectionChangedCmdMethod(object par)
{
if (par is CanLinRWConfigDto dto)
@@ -452,7 +590,16 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 读取 DataGrid 选中项变化命令。
/// </summary>
/// <remarks>
/// 由 XAML 绑定到 SelectionChanged用于把当前选中行同步到 <see cref="SelectedReadCanLinRWConfigDto"/>。
/// </remarks>
private DelegateCommand<object>? _readGridSelectionChangedCmd;
/// <summary>
/// 读取 DataGrid 选中项变化命令(供 XAML 绑定)。
/// </summary>
public DelegateCommand<object> ReadGridSelectionChangedCmd
{
get
@@ -465,6 +612,10 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 读取 DataGrid SelectionChanged 命令处理。
/// </summary>
/// <param name="par">选中项(期望为 <see cref="CanLinRWConfigDto"/>)。</param>
private void ReadGridSelectionChangedCmdMethod(object par)
{
if (par is CanLinRWConfigDto dto)
@@ -473,7 +624,18 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 写入配置操作命令(保存/删除)。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 约定:
/// - "Edit":把 DataGrid 中当前列表逐条更新到数据库;
/// - "Delete":删除当前选中项。
/// </remarks>
private DelegateCommand<string>? _writeCmd;
/// <summary>
/// 写入配置操作命令(供 XAML 绑定)。
/// </summary>
public DelegateCommand<string> WriteCmd
{
get
@@ -486,6 +648,15 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 写入配置操作命令处理。
/// </summary>
/// <param name="par">操作类型("Edit"/"Delete")。</param>
/// <remarks>
/// 前置条件:
/// - 必须已选择配置程序(<see cref="SelectCanLinConfigPro"/> 不为 null
/// - 若配置已激活(<see cref="IsCanConfigProActive"/> true禁止修改写入配置。
/// </remarks>
private void WriteCmdMethod(string par)
{
if (SelectCanLinConfigPro == null)
@@ -528,7 +699,18 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 读取配置操作命令(保存/删除)。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 约定:
/// - "Edit":把 DataGrid 中当前列表逐条更新到数据库;
/// - "Delete":删除当前选中项。
/// </remarks>
private DelegateCommand<string>? _readCmd;
/// <summary>
/// 读取配置操作命令(供 XAML 绑定)。
/// </summary>
public DelegateCommand<string> ReadCmd
{
get
@@ -541,6 +723,15 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 读取配置操作命令处理。
/// </summary>
/// <param name="par">操作类型("Edit"/"Delete")。</param>
/// <remarks>
/// 前置条件:
/// - 必须已选择配置程序(<see cref="SelectCanLinConfigPro"/> 不为 null
/// - 若配置已激活(<see cref="IsCanConfigProActive"/> true禁止修改读取配置。
/// </remarks>
private void ReadCmdMethod(string par)
{
if (SelectCanLinConfigPro == null)
@@ -667,6 +858,9 @@ namespace CapMachine.Wpf.ViewModels
get { return SelectedMode == ZlgCanMode.Can ? "CAN-DBC文件路径:" : "CANFD-DBC文件路径:"; }
}
/// <summary>
/// <see cref="ListCanLinConfigPro"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CanLinConfigPro>? _listCanLinConfigPro;
/// <summary>
/// 配置程序集合。
@@ -716,6 +910,9 @@ namespace CapMachine.Wpf.ViewModels
private CanDbcModel? _selectedCanDbcModel;
/// <summary>
/// <see cref="SelectedCANConfigExdDto"/> 的 backing 字段。
/// </summary>
private CANConfigExdDto? _selectedCANConfigExdDto;
/// <summary>
/// CAN 配置 DTO。
@@ -726,6 +923,9 @@ namespace CapMachine.Wpf.ViewModels
set { _selectedCANConfigExdDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// <see cref="SelectedCANFdConfigExdDto"/> 的 backing 字段。
/// </summary>
private CANFdConfigExdDto? _selectedCANFdConfigExdDto;
/// <summary>
/// CANFD 配置 DTO。
@@ -736,6 +936,18 @@ namespace CapMachine.Wpf.ViewModels
set { _selectedCANFdConfigExdDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// 从数据库加载 CAN/CANFD 配置程序列表。
/// </summary>
/// <remarks>
/// 加载内容包含:
/// - 主表 <see cref="CanLinConfigPro"/>
/// - 扩展配置CAN/CANFD
/// - 读写配置项(包含逻辑规则)
/// - 调度表配置项CAN/CANFD
///
/// 该方法会刷新本地缓存 <see cref="_canLinConfigPros"/> 与 UI 绑定集合 <see cref="ListCanLinConfigPro"/>。
/// </remarks>
private void InitLoadCanConfigPro()
{
_canLinConfigPros = FreeSql.Select<CanLinConfigPro>()
@@ -753,6 +965,18 @@ namespace CapMachine.Wpf.ViewModels
/// <summary>
/// 同步选中的配置。
/// </summary>
/// <remarks>
/// 当 UI 选中不同配置程序时,本方法会同步以下内容到 ViewModel
/// - 根据配置程序的 CANLINInfo 自动切换 <see cref="SelectedMode"/>(避免右侧参数/DBC 显示与配置类型不一致);
/// - 映射 CAN/CANFD 两套扩展 DTO若不存在则为 null/默认);
/// - 从配置内容构建读写配置 DTO 列表;
/// - 根据模式加载对应调度表集合;
/// - 构建并注入 CmdData 到 <see cref="ZlgCanDriveService"/>(事件驱动发送依赖);
/// - 最后重置并尝试加载当前配置的 DBC 信号集合(未连接时也会尝试解析)。
///
/// 注意:
/// - 已连接或已激活时,外层命令会禁止切换配置程序;该方法通常只在允许切换时被调用。
/// </remarks>
private void SyncSelectedConfig()
{
if (SelectCanLinConfigPro == null)
@@ -828,6 +1052,16 @@ namespace CapMachine.Wpf.ViewModels
/// <summary>
/// 切换配置程序时重置并尝试加载当前配置对应的 DBC 信号集合。
/// </summary>
/// <remarks>
/// 背景当“未连接设备”时如果切换配置程序但不清空信号集合UI 会残留上一次配置的信号列表。
///
/// 行为:
/// - 先清空 <see cref="SelectedCanDbcModel"/>
/// - 未连接时:
/// - 若当前配置未设置 DBC 路径,则信号集合置空;
/// - 若有 DBC 路径,则调用 <see cref="ZlgCanDriveService.StartDbc"/> 解析并加载信号集合;
/// - 已连接时:理论上禁止切换配置程序;这里做保底清空,避免 UI 残留。
/// </remarks>
private void ResetAndTryLoadDbcSignalsForSelectedConfig()
{
SelectedCanDbcModel = null;
@@ -844,6 +1078,10 @@ namespace CapMachine.Wpf.ViewModels
try
{
ListCanDbcModel = ZlgCanDriveService.StartDbc(CurrentDbcPath);
if (SelectCanLinConfigPro != null)
{
ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro);
}
MatchSeletedAndCanDbcModel();
}
catch (Exception ex)
@@ -856,10 +1094,14 @@ namespace CapMachine.Wpf.ViewModels
return;
}
// 已连接时:禁止切换配置程序,理论上不会执行到这里;保底清空,避免 UI 残留
ListCanDbcModel = new ObservableCollection<CanDbcModel>();
// 已连接时:禁止切换配置程序。
// 注意:此处不应清空信号集合,否则在“保存读写设置/刷新配置”这类操作中会导致信号表瞬间变空。
return;
}
/// <summary>
/// <see cref="ListCANScheduleConfigDto"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CANScheduleConfigDto> _listCANScheduleConfigDto = new ObservableCollection<CANScheduleConfigDto>();
/// <summary>
/// CAN 调度表集合。
@@ -870,6 +1112,9 @@ namespace CapMachine.Wpf.ViewModels
set { _listCANScheduleConfigDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// <see cref="ListCANFdScheduleConfigDto"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CANFdScheduleConfigDto> _listCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>();
/// <summary>
/// CANFD 调度表集合。
@@ -981,12 +1226,24 @@ namespace CapMachine.Wpf.ViewModels
/// <summary>
/// 构建并加载命令数据到驱动。
/// </summary>
/// <remarks>
/// 数据流:
/// - 从当前配置程序的写入项RW=Write构建 <see cref="CanCmdData"/> 列表;
/// - 每条写入项会携带 MsgName/SignalName/默认值/逻辑规则;
/// - 最终调用 <see cref="ZlgCanDriveService.LoadCmdDataToDrive"/> 注入驱动。
///
/// 作用:
/// - 事件驱动发送:信号值变化可触发增量发送/覆盖 auto_send
/// - 调度表/循环发送:编码发送依赖当前 CmdData 中的信号值。
/// </remarks>
private void BuildAndLoadCmdDataToDrive()
{
try
{
if (SelectCanLinConfigPro?.CanLinConfigContents == null)
{
// 防御性清空:
// - 当前未选择配置程序或无配置内容时,服务层应收到空 CmdData避免沿用旧配置继续触发事件驱动发送。
ZlgCanDriveService.LoadCmdDataToDrive(new List<CanCmdData>());
return;
}
@@ -998,6 +1255,10 @@ namespace CapMachine.Wpf.ViewModels
var cmdList = new List<CanCmdData>();
foreach (var item in writeItems)
{
// 构建 CanCmdData
// - MsgName/SignalName 决定 DBC 编码定位;
// - 默认值 DefautValue 作为 SignalCmdValue 初始值,确保首次启动循环发送时有可编码数据;
// - LogicRule 透传到发送链路(驱动侧在事件触发时可能按规则决定是否发送/如何发送)。
cmdList.Add(new CanCmdData()
{
ConfigName = item.Name,
@@ -1008,6 +1269,7 @@ namespace CapMachine.Wpf.ViewModels
});
}
// 统一入口:将 CmdData 注入 Service/Driver建立事件驱动增量发送能力。
ZlgCanDriveService.LoadCmdDataToDrive(cmdList);
}
catch (Exception ex)
@@ -1085,6 +1347,9 @@ namespace CapMachine.Wpf.ViewModels
}
set
{
// 说明:
// - 该属性是“配置程序的持久化字段”DTO/DB的映射
// - 它本身不一定立即下发到驱动(驱动下发由 SchEnableCmdCall 或 CycleSend 开启前的同步逻辑完成)。
if (SelectedMode == ZlgCanMode.Can)
{
if (SelectedCANConfigExdDto == null) return;
@@ -1145,6 +1410,14 @@ namespace CapMachine.Wpf.ViewModels
/// <summary>
/// 匹配选中的配置和 DBC 模型。
/// </summary>
/// <remarks>
/// 用途:对信号集合做“是否已被配置为写入/读取”的标记,便于 UI 颜色或状态提示。
/// - RW.Write 记为 1
/// - RW.Read 记为 2
/// - 未配置记为 0
///
/// 该标记依赖 <see cref="SelectCanLinConfigPro.CanLinConfigContents"/> 与 <see cref="ListCanDbcModel"/>。
/// </remarks>
private void MatchSeletedAndCanDbcModel()
{
if (ListCanDbcModel == null || ListCanDbcModel.Count == 0) return;
@@ -1175,10 +1448,17 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 配置程序 DataGrid 选中项变化命令的 backing 字段。
/// </summary>
private DelegateCommand<object>? _canConfigProGridSelectionChangedCmd;
/// <summary>
/// 配置程序选中变化。
/// </summary>
/// <remarks>
/// - 打开/激活状态下禁止切换配置程序;
/// - 允许切换时会更新 <see cref="SelectCanLinConfigPro"/> 并调用 <see cref="SyncSelectedConfig"/>。
/// </remarks>
public DelegateCommand<object> CanConfigProGridSelectionChangedCmd
{
get
@@ -1191,6 +1471,17 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 配置程序 DataGrid SelectionChanged 命令处理。
/// </summary>
/// <param name="par">
/// 可能为:
/// - <see cref="CanLinConfigPro"/>(直接传入选中项);
/// - <see cref="SelectionChangedEventArgs"/>WPF SelectionChanged 参数)。
/// </param>
/// <remarks>
/// WPF 的 SelectionChanged 可能被重复触发/不同类型参数混用,因此这里做了多种分支兼容。
/// </remarks>
private void CanConfigProGridSelectionChangedCmdMethod(object par)
{
if (par == null) return;
@@ -1225,6 +1516,9 @@ namespace CapMachine.Wpf.ViewModels
SyncSelectedConfig();
}
/// <summary>
/// 配置程序切换前拦截命令的 backing 字段。
/// </summary>
private DelegateCommand<object>? _canConfigProGridPreviewMouseLeftButtonDownCmd;
/// <summary>
/// 配置程序切换前拦截(对齐图莫斯 PreviewMouseLeftButtonDown
@@ -1242,6 +1536,13 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 配置程序切换前拦截命令处理。
/// </summary>
/// <param name="par">鼠标事件参数(可能为 <see cref="System.Windows.Input.MouseButtonEventArgs"/>)。</param>
/// <remarks>
/// 若拦截命中,会将 MouseButtonEventArgs.Handled 置为 true阻止 DataGrid 继续改变选中项。
/// </remarks>
private void CanConfigProGridPreviewMouseLeftButtonDownCmdMethod(object par)
{
try
@@ -1266,10 +1567,17 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 选择 DBC 文件命令的 backing 字段。
/// </summary>
private DelegateCommand? _loadDbcCmd;
/// <summary>
/// 选择 DBC 文件。
/// </summary>
/// <remarks>
/// 该命令只负责让用户选择文件并写回 <see cref="CurrentDbcPath"/>
/// 不会自动解析;解析由 CAN 操作命令中的 "Parse" 或连接流程中的自动解析触发。
/// </remarks>
public DelegateCommand LoadDbcCmd
{
get
@@ -1282,6 +1590,14 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 选择 DBC 文件命令处理。
/// </summary>
/// <remarks>
/// 前置条件:必须已选择配置程序(<see cref="SelectCanLinConfigPro"/>)。
///
/// 选择成功后会更新 <see cref="CurrentDbcPath"/>;用户取消则保持原值。
/// </remarks>
private void LoadDbcCmdMethod()
{
try
@@ -1324,10 +1640,17 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 调度使能下发命令的 backing 字段。
/// </summary>
private DelegateCommand<object>? _schEnableCmd;
/// <summary>
/// 调度使能写入驱动。
/// </summary>
/// <remarks>
/// 该命令用于把 UI 的 <see cref="CurrentSchEnable"/> 立即同步到 <see cref="ZlgCanDriveService.SchEnable"/>。
/// - SchEnable 影响事件驱动发送与调度表/auto_send 相关行为。
/// </remarks>
public DelegateCommand<object> SchEnableCmd
{
get
@@ -1340,16 +1663,37 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 调度使能同步到驱动。
/// </summary>
/// <param name="par">未使用(与 XAML CommandParameter 兼容)。</param>
private void SchEnableCmdCall(object par)
{
// 与旧 Toomoss 行为对齐UI 勾选后立即下发到驱动
// 说明:
// - SchEnable 控制“事件驱动发送/覆盖更新 auto_send”的总开关
// - 仅修改 CurrentSchEnableDTO不会影响驱动必须执行该命令或在 CycleSend 开启前同步)。
ZlgCanDriveService.SchEnable = CurrentSchEnable;
}
/// <summary>
/// 配置程序操作命令的 backing 字段。
/// </summary>
private DelegateCommand<string>? _canLinConfigProCmd;
/// <summary>
/// 配置程序操作(新建/修改/删除)。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 约定:
/// - "Add":新建配置程序(按当前 <see cref="SelectedMode"/> 创建 CAN 或 CANFD 扩展表记录);
/// - "Edit":修改配置程序名称;
/// - "Delete":删除配置程序及其内容(包含读写配置、调度表、扩展表);
/// - "Active":激活/取消激活当前配置程序(激活后禁止切换配置程序)。
///
/// 激活语义:
/// - 激活要求设备已连接且 DBC 已解析成功;
/// - 激活后会把当前配置下发到服务层CmdData + 调度表),用于循环发送/硬件调度表等功能。
/// </remarks>
public DelegateCommand<string> CanLinConfigProCmd
{
get
@@ -1362,6 +1706,16 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 配置程序操作命令处理。
/// </summary>
/// <param name="par">操作类型Add/Edit/Delete/Active。</param>
/// <remarks>
/// 约束说明:
/// - 打开 CAN<see cref="ZlgCanDriveService.OpenState"/>)或已激活(<see cref="IsCanConfigProActive"/>)时,
/// 禁止新建/修改/删除与切换配置程序,避免“驱动正在运行但底层配置被替换”导致状态错乱。
/// - Delete 会同时清理 CAN/CANFD 两套扩展配置(如果都存在),避免孤儿记录残留。
/// </remarks>
private void CanLinConfigProCmdMethod(string par)
{
if (string.IsNullOrWhiteSpace(par)) return;
@@ -1683,10 +2037,20 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 调度表配置弹窗命令的 backing 字段。
/// </summary>
private DelegateCommand? _scheduleConfigCmd;
/// <summary>
/// 调度表配置(弹窗)。
/// </summary>
/// <remarks>
/// 打开调度表弹窗时,会从 <see cref="ZlgCanDriveService.CmdData"/> 中提取 MsgName 列表作为候选消息帧。
/// - CAN 模式:打开 DialogCANSchConfigView
/// - CANFD 模式:打开 DialogCANFdSchConfigView
///
/// 弹窗确认后会把返回的调度表 DTO 写回当前配置程序实体,并调用 <c>ReloadCurrentConfigPro</c> 刷新。
/// </remarks>
public DelegateCommand ScheduleConfigCmd
{
get
@@ -1699,6 +2063,17 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 调度表配置弹窗命令处理。
/// </summary>
/// <remarks>
/// 前置条件:
/// - 必须选中配置程序;
/// - 必须存在可发送消息CmdData 非空且 MsgName 有效),否则无法配置调度表。
///
/// 注意:
/// - 调度表本身并不直接发送;真正启动发送由 CAN 操作命令中的 "CycleSend" 触发。
/// </remarks>
private void ScheduleConfigCmdMethod()
{
try
@@ -1709,8 +2084,11 @@ namespace CapMachine.Wpf.ViewModels
return;
}
// 调度表弹窗需要 MsgName 候选列表:
// - 来源于当前已下发的 CmdData写入项聚合后的消息名
// - 这样能保证“可配置的调度表项”与“实际会被编码发送的消息帧”一致。
var msgList = ZlgCanDriveService.CmdData.GroupBy(a => a.MsgName).Select(a => a.Key).Where(a => !string.IsNullOrWhiteSpace(a)).ToList();
if (msgList == null || msgList.Count == 0)
if ((msgList == null || msgList.Count == 0) && (ListCANScheduleConfigDto == null || ListCANScheduleConfigDto.Count == 0))
{
MessageBox.Show("未发现写入指令数据CmdData 为空),无法配置调度表", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
@@ -1718,7 +2096,10 @@ namespace CapMachine.Wpf.ViewModels
if (SelectedMode == ZlgCanMode.Can)
{
DialogService.ShowDialog("DialogCANSchConfigView", new DialogParameters()
// CAN 调度表弹窗:
// - 入参包含当前调度表 DTO用于回显
// - 确认后返回新的调度表 DTO 集合并回写到配置程序实体
DialogService.ShowDialog(nameof(DialogZlgCANSchConfigView), new DialogParameters()
{
{ "ListMsg", msgList },
{ "ListCANScheduleConfigDto", ListCANScheduleConfigDto },
@@ -1727,13 +2108,16 @@ namespace CapMachine.Wpf.ViewModels
{
if (r.Result != ButtonResult.OK) return;
ListCANScheduleConfigDto = r.Parameters.GetValue<ObservableCollection<CANScheduleConfigDto>>("ReturnValue") ?? new ObservableCollection<CANScheduleConfigDto>();
// DTO -> 实体:用于持久化(保存到数据库),并在后续激活/循环发送前由 SyncSelectedConfig 再同步到 Service。
SelectCanLinConfigPro.CanScheduleConfigs = Mapper.Map<List<CANScheduleConfig>>(ListCANScheduleConfigDto.ToList());
ReloadCurrentConfigPro();
});
}
else
{
DialogService.ShowDialog("DialogCANFdSchConfigView", new DialogParameters()
// CANFD 调度表弹窗:语义同 CAN。
DialogService.ShowDialog(nameof(DialogZlgCANFDSchConfigView), new DialogParameters()
{
{ "ListMsg", msgList },
{ "ListCANFdScheduleConfigDto", ListCANFdScheduleConfigDto },
@@ -1754,10 +2138,20 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 读写配置操作命令的 backing 字段。
/// </summary>
private DelegateCommand<string>? _writeReadConfigCmd;
/// <summary>
/// 写入/读取/删除 信号配置项。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 常见取值:
/// - "OpenDialog":打开“读写设置”弹窗(需要已连接且信号集合非空);
/// - "Write":将当前选中信号加入写入配置;
/// - "Read":将当前选中信号加入读取配置;
/// - "Delete":删除当前选中信号的配置项。
/// </remarks>
public DelegateCommand<string> WriteReadConfigCmd
{
get
@@ -1770,6 +2164,22 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 写入/读取/删除信号配置项命令处理。
/// </summary>
/// <param name="par">操作类型。</param>
/// <remarks>
/// 数据来源:
/// - 操作对象为右侧信号列表中当前选中的 <see cref="SelectedCanDbcModel"/>。
///
/// 重要约束:
/// - 配置程序激活后禁止变更读写配置(避免发送/解析过程中配置被修改);
/// - "OpenDialog" 会强校验:设备已连接 + 信号集合非空,防止弹窗候选集合为空。
///
/// 注意:
/// - 读写互斥与更严格的冲突校验主要在读写设置弹窗 ViewModel 中实现;
/// - 此处只做“同一 RWInfo 下禁止重复”与基础防御校验。
/// </remarks>
private void WriteReadConfigCmdMethod(string par)
{
if (string.IsNullOrWhiteSpace(par)) return;
@@ -1866,6 +2276,14 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// 重新从数据库加载配置程序,并保持当前选中项。
/// </summary>
/// <remarks>
/// 用途:
/// - 在新增/删除/保存读写配置、保存调度表等操作后,刷新 UI 绑定对象,确保界面与数据库一致;
/// - 刷新后会重新执行 <see cref="SyncSelectedConfig"/>,从而重建 CmdData、读写列表与调度表列表。
/// </remarks>
private void ReloadCurrentConfigPro()
{
var id = SelectCanLinConfigPro?.Id;
@@ -1877,13 +2295,29 @@ namespace CapMachine.Wpf.ViewModels
SyncSelectedConfig();
}
if (SelectCanLinConfigPro != null)
{
ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro);
}
MatchSeletedAndCanDbcModel();
}
/// <summary>
/// CAN 操作命令的 backing 字段。
/// </summary>
private DelegateCommand<string>? _canOpCmd;
/// <summary>
/// CAN 操作命令。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 常见取值:
/// - "Open":按当前模式与参数打开设备并启动接收线程
/// - "Close":关闭设备(同时停止循环发送/循环接收,并退出激活态)
/// - "Parse":解析当前 DBC需要已连接且 DBC 路径有效)
/// - "CycleSend":循环发送开关(第一次开启,第二次关闭)
/// - "CycleRecive":循环接收开关
/// - "Save":保存当前配置(包括 CAN/CANFD 扩展配置与模式切换)
/// </remarks>
public DelegateCommand<string> CanOpCmd
{
get
@@ -1896,6 +2330,19 @@ namespace CapMachine.Wpf.ViewModels
}
}
/// <summary>
/// CAN 操作命令处理。
/// </summary>
/// <param name="par">操作类型。</param>
/// <remarks>
/// 循环发送两种实现:
/// - 若 <see cref="CurrentSchEnable"/> 为 true使用调度表硬件 auto_send会校验每个 MsgName 在调度表中都有配置。
/// - 若 <see cref="CurrentSchEnable"/> 为 false使用软件精确定时循环发送周期取 <see cref="CurrentCycle"/>)。
///
/// 互斥约束:
/// - 开启 CAN 前会检查 <see cref="ComActionService"/> 与 <see cref="ZlgLinDriveService"/> 状态,防止 CAN/LIN 同时占用。
/// - Close 时会通过 Service 层确保停止发送/接收与硬件 auto_send 清理。
/// </remarks>
private void CanOpCmdMethod(string par)
{
if (string.IsNullOrWhiteSpace(par))
@@ -1967,6 +2414,10 @@ namespace CapMachine.Wpf.ViewModels
if (!string.IsNullOrWhiteSpace(CurrentDbcPath))
{
ListCanDbcModel = ZlgCanDriveService.StartDbc(CurrentDbcPath);
if (SelectCanLinConfigPro != null)
{
ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro);
}
MatchSeletedAndCanDbcModel();
}
@@ -2000,6 +2451,10 @@ namespace CapMachine.Wpf.ViewModels
}
ListCanDbcModel = ZlgCanDriveService.StartDbc(CurrentDbcPath);
if (SelectCanLinConfigPro != null)
{
ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro);
}
MatchSeletedAndCanDbcModel();
LastError = null;
OpTip = "DBC 已解析";
@@ -2025,11 +2480,18 @@ namespace CapMachine.Wpf.ViewModels
BuildAndLoadCmdDataToDrive();
// 同步 SchEnable防止 UI 勾选但未触发 Command 导致服务端状态不一致)
// 说明:
// - SchEnable=true 时Service 会走“硬件调度表(auto_send)”路径;
// - SchEnable=false 时Service 会走“软件精确定时循环发送”路径;
// - 因此 CycleSend 开启前必须保证 Service.SchEnable 与 UI/DTO 一致。
ZlgCanDriveService.SchEnable = CurrentSchEnable;
if (ZlgCanDriveService.SchEnable)
{
// 使用调度表:校验每个 MsgName 都配置了周期
// 注意:
// - 这里校验的是“当前会被发送的消息帧集合”CmdData 聚合)是否都在调度表中配置;
// - 缺失配置会导致硬件 auto_send 无法周期性发送对应帧,因此直接阻止开启。
var groupMsg = ZlgCanDriveService.CmdData
.Where(a => !string.IsNullOrWhiteSpace(a.MsgName))
.GroupBy(a => a.MsgName)
@@ -2081,11 +2543,15 @@ namespace CapMachine.Wpf.ViewModels
ZlgCanDriveService.SetScheduleConfigs(ListCANFdScheduleConfigDto.ToList());
}
// 启动硬件调度表:
// - Service 层会 StopSchedule 清理旧任务/旧 auto_send然后下发 auto_send 并 apply
// - 运行中 CmdData 信号变化会由 Driver 覆盖更新对应 index 的帧数据(需要 IsCycleSend=true 且 SchEnable=true
ZlgCanDriveService.StartSchedule();
}
else
{
// 不使用调度表:按当前配置周期做精确循环发送
// 说明:该路径完全在 PC 侧按周期触发发送,精度受系统调度影响,但不依赖硬件 auto_send。
var cycle = Math.Max(1, CurrentCycle);
ZlgCanDriveService.StartPrecisionCycleSend(cycle);
}
@@ -2094,6 +2560,9 @@ namespace CapMachine.Wpf.ViewModels
}
else
{
// 关闭循环发送:
// - 先关闭事件驱动发送标志(避免 CmdData 变化继续触发增量发送/覆盖更新);
// - 再调用 StopSchedule停止软件任务 + clear_auto_send防止硬件继续发
ZlgCanDriveService.IsCycleSend = false;
ZlgCanDriveService.StopSchedule();
OpTip = "循环发送:已关闭";