905 lines
34 KiB
C#
905 lines
34 KiB
C#
using CapMachine.Core;
|
||
using CapMachine.Model.CANLIN;
|
||
using CapMachine.Wpf.Dtos;
|
||
using CapMachine.Wpf.Services;
|
||
using FreeSql;
|
||
using Prism.Commands;
|
||
using Prism.Services.Dialogs;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
using System.Linq;
|
||
using System.Windows;
|
||
using System.Windows.Data;
|
||
|
||
namespace CapMachine.Wpf.ViewModels
|
||
{
|
||
/// <summary>
|
||
/// ZLG CAN/LIN 读写配置三栏管理弹窗 ViewModel。
|
||
/// 左侧:写入/读取配置;右侧:信号全集候选池;统一保存落库。
|
||
/// </summary>
|
||
public class DialogZlgCanLinRwConfigViewModel : DialogViewModel
|
||
{
|
||
private readonly IFreeSql _freeSql;
|
||
private readonly ILogService _logService;
|
||
private readonly LogicRuleService _logicRuleService;
|
||
|
||
private long _canLinConfigProId;
|
||
|
||
private bool _enableHardwareCycleSchedule = true;
|
||
private bool _useCanFdSchedule;
|
||
|
||
/// <summary>
|
||
/// 是否启用“加入定时调度表(ZLG auto_send)”能力。
|
||
/// 说明:该能力仅适用于 ZLG CAN/CANFD 硬件 auto_send;LIN 当前使用软件调度,不允许写入 CANScheduleConfig。
|
||
/// </summary>
|
||
public bool EnableHardwareCycleSchedule
|
||
{
|
||
get { return _enableHardwareCycleSchedule; }
|
||
private set
|
||
{
|
||
_enableHardwareCycleSchedule = value;
|
||
RaisePropertyChanged();
|
||
RaisePropertyChanged(nameof(CanAddCycleTimeSch));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否允许执行“加入定时调度表”操作。
|
||
/// 说明:
|
||
/// - 需要 <see cref="EnableHardwareCycleSchedule"/> 打开(调用方允许硬件调度表能力);
|
||
/// - 且弹窗处于可编辑态(<see cref="IsEditable"/>)。
|
||
/// </summary>
|
||
public bool CanAddCycleTimeSch
|
||
{
|
||
get { return EnableHardwareCycleSchedule && IsEditable; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否使用 CANFD 调度表(决定“加入定时调度表”写入 <see cref="CANFdScheduleConfig"/> 还是 <see cref="CANScheduleConfig"/>)。
|
||
/// </summary>
|
||
public bool UseCanFdSchedule
|
||
{
|
||
get { return _useCanFdSchedule; }
|
||
private set { _useCanFdSchedule = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
/// <param name="freeSql">FreeSql。</param>
|
||
/// <param name="logService">日志。</param>
|
||
/// <param name="logicRuleService">逻辑规则服务。</param>
|
||
public DialogZlgCanLinRwConfigViewModel(IFreeSql freeSql, ILogService logService, LogicRuleService logicRuleService)
|
||
{
|
||
_freeSql = freeSql;
|
||
_logService = logService;
|
||
_logicRuleService = logicRuleService;
|
||
|
||
Title = "读写设置";
|
||
|
||
WriteConfigs = new ObservableCollection<CanLinRWConfigDto>();
|
||
ReadConfigs = new ObservableCollection<CanLinRWConfigDto>();
|
||
SignalCandidates = new ObservableCollection<SignalCandidate>();
|
||
SignalTree = new ObservableCollection<SignalFrameNode>();
|
||
|
||
SignalCandidatesView = CollectionViewSource.GetDefaultView(SignalCandidates);
|
||
SignalCandidatesView.Filter = FilterSignalCandidate;
|
||
|
||
WriteNameCbxItems = new ObservableCollection<CbxItems>()
|
||
{
|
||
new CbxItems(){ Key="转速",Text="转速"},
|
||
new CbxItems(){ Key="功率限制",Text="功率限制"},
|
||
new CbxItems(){ Key="使能",Text="使能"},
|
||
new CbxItems(){ Key="Anti_Sleep",Text="Anti_Sleep"},
|
||
new CbxItems(){ Key="PTC使能",Text="PTC使能"},
|
||
new CbxItems(){ Key="PTC功率",Text="PTC功率"},
|
||
new CbxItems(){ Key="PTC水流量",Text="PTC水流量"},
|
||
new CbxItems(){ Key="PTC水温",Text="PTC水温"},
|
||
};
|
||
|
||
ReadNameCbxItems = new ObservableCollection<CbxItems>()
|
||
{
|
||
new CbxItems(){ Key="通讯Cmp转速",Text="通讯Cmp转速"},
|
||
new CbxItems(){ Key="通讯Cmp母线电压",Text="通讯Cmp母线电压"},
|
||
new CbxItems(){ Key="通讯Cmp母线电流",Text="通讯Cmp母线电流"},
|
||
new CbxItems(){ Key="通讯Cmp逆变器温度",Text="通讯Cmp逆变器温度"},
|
||
new CbxItems(){ Key="通讯Cmp相电流",Text="通讯Cmp相电流"},
|
||
new CbxItems(){ Key="通讯Cmp功率",Text="通讯Cmp功率"},
|
||
new CbxItems(){ Key="通讯Cmp芯片温度",Text="通讯Cmp芯片温度"},
|
||
new CbxItems(){ Key="通讯PTC入水温度",Text="通讯PTC入水温度"},
|
||
new CbxItems(){ Key="通讯PTC出水温度",Text="通讯PTC出水温度"},
|
||
new CbxItems(){ Key="通讯PTC峰值电流",Text="通讯PTC峰值电流"},
|
||
new CbxItems(){ Key="通讯PTC母线电流",Text="通讯PTC母线电流"},
|
||
new CbxItems(){ Key="通讯PTC膜温",Text="通讯PTC膜温"},
|
||
new CbxItems(){ Key="通讯PTC模块温度",Text="通讯PTC模块温度"},
|
||
};
|
||
|
||
IsEditable = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否允许编辑(由调用方根据 Active/打开状态决定)。
|
||
/// </summary>
|
||
public bool IsEditable { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 逻辑规则集合(下拉框 ItemsSource)。
|
||
/// </summary>
|
||
public IReadOnlyList<LogicRuleDto> LogicRuleDtos => _logicRuleService.LogicRuleDtos;
|
||
|
||
/// <summary>
|
||
/// 写入配置“名称”下拉框集合(参考 CANConfigViewModel)。
|
||
/// </summary>
|
||
public ObservableCollection<CbxItems> WriteNameCbxItems { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 读取配置“名称”下拉框集合(参考 CANConfigViewModel)。
|
||
/// </summary>
|
||
public ObservableCollection<CbxItems> ReadNameCbxItems { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 写入配置集合。
|
||
/// </summary>
|
||
public ObservableCollection<CanLinRWConfigDto> WriteConfigs { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 读取配置集合。
|
||
/// </summary>
|
||
public ObservableCollection<CanLinRWConfigDto> ReadConfigs { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 信号候选集合(右侧池)。
|
||
/// </summary>
|
||
public ObservableCollection<SignalCandidate> SignalCandidates { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 信号树(按帧分组)。
|
||
/// </summary>
|
||
public ObservableCollection<SignalFrameNode> SignalTree { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 候选信号视图(含过滤)。
|
||
/// </summary>
|
||
public ICollectionView SignalCandidatesView { get; private set; }
|
||
|
||
private string? _signalFilterText;
|
||
|
||
/// <summary>
|
||
/// 信号过滤文本(按 MsgName/SignalName/Name/Desc 匹配)。
|
||
/// </summary>
|
||
public string? SignalFilterText
|
||
{
|
||
get { return _signalFilterText; }
|
||
set
|
||
{
|
||
_signalFilterText = value;
|
||
RaisePropertyChanged();
|
||
SignalCandidatesView.Refresh();
|
||
RebuildSignalTree();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前选中的候选信号。
|
||
/// </summary>
|
||
public SignalCandidate? SelectedSignalCandidate { get; set; }
|
||
|
||
/// <summary>
|
||
/// 当前选中的写入配置行。
|
||
/// </summary>
|
||
public CanLinRWConfigDto? SelectedWriteConfig { get; set; }
|
||
|
||
private DelegateCommand? _addCycleTimeSch;
|
||
|
||
/// <summary>
|
||
/// 将当前选中的写入配置对应的报文加入“硬件定时调度表”(ZLG auto_send)。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 说明:
|
||
/// - 调度表按“报文/帧(MsgFrameName)”维度配置;同一报文可包含多个写入信号,但调度表只需要一条。
|
||
/// - 该命令会直接落库到 <see cref="CANScheduleConfig"/>,随后由主界面刷新配置程序时加载显示。
|
||
/// </remarks>
|
||
public DelegateCommand AddCycleTimeSch => _addCycleTimeSch ??= new DelegateCommand(AddCycleTimeSchMethod);
|
||
|
||
/// <summary>
|
||
/// 加入定时调度表命令处理。
|
||
/// </summary>
|
||
private void AddCycleTimeSchMethod()
|
||
{
|
||
if (!EnableHardwareCycleSchedule)
|
||
{
|
||
MessageBox.Show("当前模式不支持加入硬件定时调度表(LIN 使用软件调度表)", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (!IsEditable)
|
||
{
|
||
MessageBox.Show("当前状态禁止修改", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (_canLinConfigProId <= 0)
|
||
{
|
||
MessageBox.Show("配置程序ID无效,无法加入调度表", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (SelectedWriteConfig == null)
|
||
{
|
||
MessageBox.Show("请先选中写入配置中的一行", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(SelectedWriteConfig.MsgFrameName))
|
||
{
|
||
MessageBox.Show("写入配置的【消息名称】为空,无法加入调度表", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
var msgName = SelectedWriteConfig.MsgFrameName.Trim();
|
||
|
||
try
|
||
{
|
||
var exists = UseCanFdSchedule
|
||
? _freeSql.Select<CANFdScheduleConfig>()
|
||
.Where(a => a.CanLinConfigProId == _canLinConfigProId)
|
||
.Where(a => a.MsgName == msgName)
|
||
.Any()
|
||
: _freeSql.Select<CANScheduleConfig>()
|
||
.Where(a => a.CanLinConfigProId == _canLinConfigProId)
|
||
.Where(a => a.MsgName == msgName)
|
||
.Any();
|
||
|
||
if (exists)
|
||
{
|
||
MessageBox.Show($"该报文已在定时调度表中:{msgName}", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
return;
|
||
}
|
||
|
||
if (UseCanFdSchedule)
|
||
{
|
||
_freeSql.Insert<CANFdScheduleConfig>(new CANFdScheduleConfig
|
||
{
|
||
CanLinConfigProId = _canLinConfigProId,
|
||
MsgName = msgName,
|
||
Cycle = 100,
|
||
OrderSend = 1,
|
||
SchTabIndex = 0,
|
||
}).ExecuteAffrows();
|
||
}
|
||
else
|
||
{
|
||
_freeSql.Insert<CANScheduleConfig>(new CANScheduleConfig
|
||
{
|
||
CanLinConfigProId = _canLinConfigProId,
|
||
MsgName = msgName,
|
||
Cycle = 100,
|
||
OrderSend = 1,
|
||
SchTabIndex = 0,
|
||
}).ExecuteAffrows();
|
||
}
|
||
|
||
MessageBox.Show($"已加入定时调度表:{msgName}(默认周期 100ms,可在调度表中修改)", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logService.Error($"加入定时调度表失败:{msgName},{ex}");
|
||
MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前选中的读取配置行。
|
||
/// </summary>
|
||
public CanLinRWConfigDto? SelectedReadConfig { get; set; }
|
||
|
||
private DelegateCommand<object>? _signalTreeSelectionChangedCmd;
|
||
|
||
/// <summary>
|
||
/// 右侧信号树选中变化(仅当选中叶子节点时回写 SelectedSignalCandidate)。
|
||
/// </summary>
|
||
public DelegateCommand<object> SignalTreeSelectionChangedCmd =>
|
||
_signalTreeSelectionChangedCmd ??= new DelegateCommand<object>(SignalTreeSelectionChangedCmdMethod);
|
||
|
||
private void SignalTreeSelectionChangedCmdMethod(object par)
|
||
{
|
||
if (par is SignalCandidate leaf)
|
||
{
|
||
SelectedSignalCandidate = leaf;
|
||
RaisePropertyChanged(nameof(SelectedSignalCandidate));
|
||
return;
|
||
}
|
||
|
||
if (par is SignalFrameNode)
|
||
{
|
||
// 选中父节点时不变更 SelectedSignalCandidate
|
||
return;
|
||
}
|
||
}
|
||
|
||
private DelegateCommand? _addToWriteCmd;
|
||
|
||
/// <summary>
|
||
/// 将右侧选中信号添加到写入配置。
|
||
/// </summary>
|
||
public DelegateCommand AddToWriteCmd => _addToWriteCmd ??= new DelegateCommand(AddToWriteCmdMethod);
|
||
|
||
private void AddToWriteCmdMethod()
|
||
{
|
||
if (!IsEditable)
|
||
{
|
||
MessageBox.Show("当前状态禁止修改", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (SelectedSignalCandidate == null)
|
||
{
|
||
MessageBox.Show("请先在右侧信号集合中选中一条", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(SelectedSignalCandidate.SignalName) || string.IsNullOrWhiteSpace(SelectedSignalCandidate.MsgName))
|
||
{
|
||
MessageBox.Show("选中的信号数据不完整(MsgName/SignalName 为空)", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (WriteConfigs.Any(a => string.Equals(a.SignalName, SelectedSignalCandidate.SignalName, StringComparison.Ordinal) && a.RWInfo == RW.Write))
|
||
{
|
||
MessageBox.Show("该信号已在写入配置中,无需重复添加", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (ReadConfigs.Any(a => string.Equals(a.SignalName, SelectedSignalCandidate.SignalName, StringComparison.Ordinal) && a.RWInfo == RW.Read))
|
||
{
|
||
MessageBox.Show("该信号已在读取配置中,同一个信号不允许同时配置为写入与读取", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
WriteConfigs.Add(new CanLinRWConfigDto
|
||
{
|
||
Id = 0,
|
||
RWInfo = RW.Write,
|
||
Name = string.IsNullOrWhiteSpace(SelectedSignalCandidate.Name) ? SelectedSignalCandidate.SignalName : SelectedSignalCandidate.Name,
|
||
MsgFrameName = SelectedSignalCandidate.MsgName,
|
||
SignalName = SelectedSignalCandidate.SignalName,
|
||
DefautValue = "0",
|
||
LogicRuleId = 0,
|
||
});
|
||
|
||
RebuildSignalTree();
|
||
}
|
||
|
||
private DelegateCommand? _addToReadCmd;
|
||
|
||
/// <summary>
|
||
/// 将右侧选中信号添加到读取配置。
|
||
/// </summary>
|
||
public DelegateCommand AddToReadCmd => _addToReadCmd ??= new DelegateCommand(AddToReadCmdMethod);
|
||
|
||
private void AddToReadCmdMethod()
|
||
{
|
||
if (!IsEditable)
|
||
{
|
||
MessageBox.Show("当前状态禁止修改", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (SelectedSignalCandidate == null)
|
||
{
|
||
MessageBox.Show("请先在右侧信号集合中选中一条", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(SelectedSignalCandidate.SignalName) || string.IsNullOrWhiteSpace(SelectedSignalCandidate.MsgName))
|
||
{
|
||
MessageBox.Show("选中的信号数据不完整(MsgName/SignalName 为空)", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (ReadConfigs.Any(a => string.Equals(a.SignalName, SelectedSignalCandidate.SignalName, StringComparison.Ordinal) && a.RWInfo == RW.Read))
|
||
{
|
||
MessageBox.Show("该信号已在读取配置中,无需重复添加", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (WriteConfigs.Any(a => string.Equals(a.SignalName, SelectedSignalCandidate.SignalName, StringComparison.Ordinal) && a.RWInfo == RW.Write))
|
||
{
|
||
MessageBox.Show("该信号已在写入配置中,同一个信号不允许同时配置为写入与读取", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
ReadConfigs.Add(new CanLinRWConfigDto
|
||
{
|
||
Id = 0,
|
||
RWInfo = RW.Read,
|
||
Name = string.IsNullOrWhiteSpace(SelectedSignalCandidate.Name) ? SelectedSignalCandidate.SignalName : SelectedSignalCandidate.Name,
|
||
MsgFrameName = SelectedSignalCandidate.MsgName,
|
||
SignalName = SelectedSignalCandidate.SignalName,
|
||
DefautValue = "0",
|
||
LogicRuleId = 0,
|
||
});
|
||
|
||
RebuildSignalTree();
|
||
}
|
||
|
||
private DelegateCommand? _removeWriteCmd;
|
||
|
||
/// <summary>
|
||
/// 从写入配置移除当前选中行。
|
||
/// </summary>
|
||
public DelegateCommand RemoveWriteCmd => _removeWriteCmd ??= new DelegateCommand(RemoveWriteCmdMethod);
|
||
|
||
private void RemoveWriteCmdMethod()
|
||
{
|
||
if (!IsEditable)
|
||
{
|
||
MessageBox.Show("当前状态禁止修改", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (SelectedWriteConfig == null)
|
||
{
|
||
MessageBox.Show("请先选中写入列表中的一行", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
WriteConfigs.Remove(SelectedWriteConfig);
|
||
SelectedWriteConfig = null;
|
||
RaisePropertyChanged(nameof(SelectedWriteConfig));
|
||
|
||
RebuildSignalTree();
|
||
}
|
||
|
||
private DelegateCommand? _removeReadCmd;
|
||
|
||
/// <summary>
|
||
/// 从读取配置移除当前选中行。
|
||
/// </summary>
|
||
public DelegateCommand RemoveReadCmd => _removeReadCmd ??= new DelegateCommand(RemoveReadCmdMethod);
|
||
|
||
private void RemoveReadCmdMethod()
|
||
{
|
||
if (!IsEditable)
|
||
{
|
||
MessageBox.Show("当前状态禁止修改", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (SelectedReadConfig == null)
|
||
{
|
||
MessageBox.Show("请先选中读取列表中的一行", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
ReadConfigs.Remove(SelectedReadConfig);
|
||
SelectedReadConfig = null;
|
||
RaisePropertyChanged(nameof(SelectedReadConfig));
|
||
|
||
RebuildSignalTree();
|
||
}
|
||
|
||
private DelegateCommand? _saveCmd;
|
||
|
||
/// <summary>
|
||
/// 保存并落库。
|
||
/// </summary>
|
||
public DelegateCommand SaveCmd => _saveCmd ??= new DelegateCommand(SaveCmdMethod);
|
||
|
||
private void SaveCmdMethod()
|
||
{
|
||
if (!IsEditable)
|
||
{
|
||
MessageBox.Show("当前状态禁止修改", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
if (_canLinConfigProId <= 0)
|
||
{
|
||
MessageBox.Show("配置程序ID无效,无法保存", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
PersistRwConfigs();
|
||
|
||
var pars = new DialogParameters
|
||
{
|
||
{ "Saved", true }
|
||
};
|
||
RaiseRequestClose(new DialogResult(ButtonResult.OK, pars));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logService.Error($"ZLG 读写设置保存失败:{ex}");
|
||
MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
private DelegateCommand? _cancelCmd;
|
||
|
||
/// <summary>
|
||
/// 取消。
|
||
/// </summary>
|
||
public DelegateCommand CancelCmd => _cancelCmd ??= new DelegateCommand(CancelCmdMethod);
|
||
|
||
private void CancelCmdMethod()
|
||
{
|
||
RaiseRequestClose(new DialogResult(ButtonResult.Cancel));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 弹窗打开时接收参数。
|
||
/// </summary>
|
||
/// <param name="parameters">参数。</param>
|
||
public override void OnDialogOpened(IDialogParameters parameters)
|
||
{
|
||
_canLinConfigProId = parameters.GetValue<long>("CanLinConfigProId");
|
||
|
||
EnableHardwareCycleSchedule = parameters.ContainsKey("EnableHardwareCycleSchedule")
|
||
? parameters.GetValue<bool>("EnableHardwareCycleSchedule")
|
||
: true;
|
||
|
||
// 兼容参数命名:调用方传入的 key 为 "IsCanFdSchedule"。
|
||
UseCanFdSchedule = parameters.ContainsKey("IsCanFdSchedule")
|
||
? parameters.GetValue<bool>("IsCanFdSchedule")
|
||
: false;
|
||
|
||
IsEditable = parameters.ContainsKey("IsEditable") ? parameters.GetValue<bool>("IsEditable") : true;
|
||
RaisePropertyChanged(nameof(IsEditable));
|
||
RaisePropertyChanged(nameof(CanAddCycleTimeSch));
|
||
|
||
if (parameters.ContainsKey("WriteConfigs"))
|
||
{
|
||
var list = parameters.GetValue<ObservableCollection<CanLinRWConfigDto>>("WriteConfigs") ?? new ObservableCollection<CanLinRWConfigDto>();
|
||
WriteConfigs = list;
|
||
RaisePropertyChanged(nameof(WriteConfigs));
|
||
}
|
||
|
||
if (parameters.ContainsKey("ReadConfigs"))
|
||
{
|
||
var list = parameters.GetValue<ObservableCollection<CanLinRWConfigDto>>("ReadConfigs") ?? new ObservableCollection<CanLinRWConfigDto>();
|
||
ReadConfigs = list;
|
||
RaisePropertyChanged(nameof(ReadConfigs));
|
||
}
|
||
|
||
if (parameters.ContainsKey("SignalCandidates"))
|
||
{
|
||
var list = parameters.GetValue<ObservableCollection<SignalCandidate>>("SignalCandidates") ?? new ObservableCollection<SignalCandidate>();
|
||
SignalCandidates = list;
|
||
RaisePropertyChanged(nameof(SignalCandidates));
|
||
|
||
SignalCandidatesView = CollectionViewSource.GetDefaultView(SignalCandidates);
|
||
SignalCandidatesView.Filter = FilterSignalCandidate;
|
||
RaisePropertyChanged(nameof(SignalCandidatesView));
|
||
}
|
||
|
||
RebuildSignalTree();
|
||
|
||
if (parameters.ContainsKey("Title"))
|
||
{
|
||
Title = parameters.GetValue<string>("Title") ?? Title;
|
||
}
|
||
}
|
||
|
||
private bool FilterSignalCandidate(object obj)
|
||
{
|
||
if (obj is not SignalCandidate item)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(SignalFilterText))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
var key = SignalFilterText.Trim();
|
||
|
||
return ContainsIgnoreCase(item.MsgName, key)
|
||
|| ContainsIgnoreCase(item.SignalName, key)
|
||
|| ContainsIgnoreCase(item.Name, key)
|
||
|| ContainsIgnoreCase(item.Desc, key);
|
||
}
|
||
|
||
private void RebuildSignalTree()
|
||
{
|
||
// 依据过滤条件 + 全量候选池生成树。
|
||
// 树数据源独立于 ICollectionView,避免 TreeView 过滤复杂度。
|
||
var filtered = SignalCandidates
|
||
.Where(a => FilterSignalCandidate(a))
|
||
.ToList();
|
||
|
||
var groups = filtered
|
||
.GroupBy(a => string.IsNullOrWhiteSpace(a.MsgName) ? "(未命名帧)" : a.MsgName!.Trim(), StringComparer.Ordinal)
|
||
.OrderBy(a => a.Key, StringComparer.Ordinal)
|
||
.ToList();
|
||
|
||
SignalTree.Clear();
|
||
foreach (var g in groups)
|
||
{
|
||
var signalNodes = g
|
||
.OrderBy(a => a.SignalName ?? string.Empty, StringComparer.Ordinal)
|
||
.ThenBy(a => a.Name ?? string.Empty, StringComparer.Ordinal)
|
||
.Select(a => new SignalCandidate
|
||
{
|
||
MsgName = a.MsgName,
|
||
SignalName = a.SignalName,
|
||
Name = a.Name,
|
||
Desc = a.Desc,
|
||
AddedInfo = ComputeAddedInfo(a),
|
||
})
|
||
.ToList();
|
||
|
||
var node = new SignalFrameNode
|
||
{
|
||
FrameName = g.Key,
|
||
Signals = new ObservableCollection<SignalCandidate>(signalNodes)
|
||
};
|
||
SignalTree.Add(node);
|
||
}
|
||
|
||
RaisePropertyChanged(nameof(SignalTree));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算候选信号是否已被添加到写入/读取。
|
||
/// 0=未添加,1=已添加到写入,2=已添加到读取,3=同时存在于写入与读取。
|
||
/// </summary>
|
||
/// <param name="candidate">候选信号。</param>
|
||
/// <returns>AddedInfo 标志值。</returns>
|
||
private int ComputeAddedInfo(SignalCandidate candidate)
|
||
{
|
||
if (candidate == null || string.IsNullOrWhiteSpace(candidate.SignalName))
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
var signal = candidate.SignalName;
|
||
var inWrite = WriteConfigs.Any(a => a.RWInfo == RW.Write && string.Equals(a.SignalName, signal, StringComparison.Ordinal));
|
||
var inRead = ReadConfigs.Any(a => a.RWInfo == RW.Read && string.Equals(a.SignalName, signal, StringComparison.Ordinal));
|
||
|
||
if (inWrite && inRead) return 3;
|
||
if (inWrite) return 1;
|
||
if (inRead) return 2;
|
||
return 0;
|
||
}
|
||
|
||
private static bool ContainsIgnoreCase(string? src, string key)
|
||
{
|
||
if (string.IsNullOrEmpty(src)) return false;
|
||
return src.IndexOf(key, StringComparison.OrdinalIgnoreCase) >= 0;
|
||
}
|
||
|
||
private void PersistRwConfigs()
|
||
{
|
||
// 互斥约束:同一 SignalName 不允许同时出现在写入与读取
|
||
EnsureNoWriteReadConflict();
|
||
|
||
// 规范化 DTO(空值/默认值防御)
|
||
NormalizeRwConfigs(WriteConfigs, RW.Write);
|
||
NormalizeRwConfigs(ReadConfigs, RW.Read);
|
||
|
||
// 防重复:同一 SignalName 在同一 RW 列表中只允许一条
|
||
EnsureNoDuplicateSignal(WriteConfigs, RW.Write);
|
||
EnsureNoDuplicateSignal(ReadConfigs, RW.Read);
|
||
|
||
var existing = _freeSql.Select<CanLinRWConfig>()
|
||
.Where(a => a.CanLinConfigProId == _canLinConfigProId)
|
||
.Where(a => a.RWInfo == RW.Write || a.RWInfo == RW.Read)
|
||
.ToList();
|
||
|
||
var desiredWrite = WriteConfigs
|
||
.Where(a => !string.IsNullOrWhiteSpace(a.SignalName))
|
||
.Select(a => new DesiredItem(RW.Write, a.SignalName!, a))
|
||
.ToList();
|
||
|
||
var desiredRead = ReadConfigs
|
||
.Where(a => !string.IsNullOrWhiteSpace(a.SignalName))
|
||
.Select(a => new DesiredItem(RW.Read, a.SignalName!, a))
|
||
.ToList();
|
||
|
||
var desiredAll = desiredWrite.Concat(desiredRead).ToList();
|
||
|
||
// 以 (RWInfo + SignalName) 作为“业务主键”对齐数据库:
|
||
// - desiredKeySet:本次保存期望存在的 key 集合
|
||
// - existingByKey:当前数据库已存在的 key -> 实体
|
||
var desiredKeySet = new HashSet<string>(desiredAll.Select(a => BuildKey(a.Rw, a.SignalName)), StringComparer.Ordinal);
|
||
var existingByKey = existing.ToDictionary(a => BuildKey(a.RWInfo, a.SignalName ?? string.Empty), a => a, StringComparer.Ordinal);
|
||
|
||
// 删除:DB 中存在,但目标集合里不存在
|
||
foreach (var old in existing)
|
||
{
|
||
var key = BuildKey(old.RWInfo, old.SignalName ?? string.Empty);
|
||
if (!desiredKeySet.Contains(key))
|
||
{
|
||
// 保持与 UI 一致:用户在弹窗中移除的项,需要同步删除数据库记录。
|
||
_freeSql.Delete<CanLinRWConfig>(old.Id).ExecuteAffrows();
|
||
}
|
||
}
|
||
|
||
// Upsert:按 key(RWInfo + SignalName)更新或插入
|
||
foreach (var item in desiredAll)
|
||
{
|
||
var key = BuildKey(item.Rw, item.SignalName);
|
||
if (existingByKey.TryGetValue(key, out var old))
|
||
{
|
||
_freeSql.Update<CanLinRWConfig>(old.Id)
|
||
.Set(a => a.Name, item.Dto.Name)
|
||
.Set(a => a.MsgFrameName, item.Dto.MsgFrameName)
|
||
.Set(a => a.SignalName, item.Dto.SignalName)
|
||
.Set(a => a.DefautValue, item.Dto.DefautValue)
|
||
.Set(a => a.LogicRuleId, item.Dto.LogicRuleId)
|
||
.ExecuteAffrows();
|
||
}
|
||
else
|
||
{
|
||
_freeSql.Insert<CanLinRWConfig>(new CanLinRWConfig
|
||
{
|
||
CanLinConfigProId = _canLinConfigProId,
|
||
RWInfo = item.Rw,
|
||
Name = item.Dto.Name,
|
||
MsgFrameName = item.Dto.MsgFrameName,
|
||
SignalName = item.Dto.SignalName,
|
||
DefautValue = item.Dto.DefautValue,
|
||
LogicRuleId = item.Dto.LogicRuleId,
|
||
}).ExecuteAffrows();
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void NormalizeRwConfigs(IEnumerable<CanLinRWConfigDto> list, RW rw)
|
||
{
|
||
foreach (var item in list)
|
||
{
|
||
item.RWInfo = rw;
|
||
if (string.IsNullOrWhiteSpace(item.SignalName))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(item.Name))
|
||
{
|
||
item.Name = item.SignalName;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(item.MsgFrameName))
|
||
{
|
||
item.MsgFrameName = string.Empty;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(item.DefautValue))
|
||
{
|
||
item.DefautValue = "0";
|
||
}
|
||
|
||
if (item.LogicRuleId < 0)
|
||
{
|
||
item.LogicRuleId = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void EnsureNoDuplicateSignal(IEnumerable<CanLinRWConfigDto> list, RW rw)
|
||
{
|
||
var duplicates = list
|
||
.Where(a => !string.IsNullOrWhiteSpace(a.SignalName))
|
||
.GroupBy(a => a.SignalName!, StringComparer.Ordinal)
|
||
.Where(g => g.Count() > 1)
|
||
.Select(g => g.Key)
|
||
.ToList();
|
||
|
||
if (duplicates.Count > 0)
|
||
{
|
||
throw new InvalidOperationException($"{rw} 配置中存在重复信号:{string.Join(",", duplicates)}");
|
||
}
|
||
}
|
||
|
||
private void EnsureNoWriteReadConflict()
|
||
{
|
||
var writeSet = new HashSet<string>(
|
||
WriteConfigs
|
||
.Where(a => a.RWInfo == RW.Write)
|
||
.Select(a => a.SignalName)
|
||
.Where(a => !string.IsNullOrWhiteSpace(a))
|
||
.Select(a => a!),
|
||
StringComparer.Ordinal);
|
||
|
||
var readSet = new HashSet<string>(
|
||
ReadConfigs
|
||
.Where(a => a.RWInfo == RW.Read)
|
||
.Select(a => a.SignalName)
|
||
.Where(a => !string.IsNullOrWhiteSpace(a))
|
||
.Select(a => a!),
|
||
StringComparer.Ordinal);
|
||
|
||
writeSet.IntersectWith(readSet);
|
||
if (writeSet.Count > 0)
|
||
{
|
||
throw new InvalidOperationException($"同一信号不允许同时配置为写入与读取,冲突信号:{string.Join(",", writeSet)}");
|
||
}
|
||
}
|
||
|
||
private static string BuildKey(RW rw, string signalName)
|
||
{
|
||
return $"{(int)rw}:{signalName}";
|
||
}
|
||
|
||
private readonly struct DesiredItem
|
||
{
|
||
/// <summary>
|
||
/// 读写类型。
|
||
/// </summary>
|
||
public RW Rw { get; }
|
||
|
||
/// <summary>
|
||
/// 信号名称。
|
||
/// </summary>
|
||
public string SignalName { get; }
|
||
|
||
/// <summary>
|
||
/// 原始 DTO。
|
||
/// </summary>
|
||
public CanLinRWConfigDto Dto { get; }
|
||
|
||
public DesiredItem(RW rw, string signalName, CanLinRWConfigDto dto)
|
||
{
|
||
Rw = rw;
|
||
SignalName = signalName;
|
||
Dto = dto;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 右侧信号候选项。
|
||
/// </summary>
|
||
public class SignalCandidate
|
||
{
|
||
/// <summary>
|
||
/// 消息名称/帧名称。
|
||
/// </summary>
|
||
public string? MsgName { get; set; }
|
||
|
||
/// <summary>
|
||
/// 信号名称。
|
||
/// </summary>
|
||
public string? SignalName { get; set; }
|
||
|
||
/// <summary>
|
||
/// 配置名称(若解析层已有中文名则传入)。
|
||
/// </summary>
|
||
public string? Name { get; set; }
|
||
|
||
/// <summary>
|
||
/// 描述。
|
||
/// </summary>
|
||
public string? Desc { get; set; }
|
||
|
||
/// <summary>
|
||
/// 已添加标记。
|
||
/// 0=未添加,1=已添加到写入,2=已添加到读取,3=同时存在于写入与读取。
|
||
/// </summary>
|
||
public int AddedInfo { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 帧节点。
|
||
/// </summary>
|
||
public class SignalFrameNode
|
||
{
|
||
/// <summary>
|
||
/// 帧名。
|
||
/// </summary>
|
||
public string FrameName { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 帧内信号集合。
|
||
/// </summary>
|
||
public ObservableCollection<SignalCandidate> Signals { get; set; } = new ObservableCollection<SignalCandidate>();
|
||
}
|
||
}
|
||
}
|