CAN/LIN 导入和导出功能

This commit is contained in:
2026-04-09 21:56:22 +08:00
parent 48514b3162
commit 532374d1c4
46 changed files with 20232 additions and 109 deletions

View File

@@ -1,4 +1,4 @@
using AutoMapper;
using AutoMapper;
using CapMachine.Core;
using CapMachine.Model;
using CapMachine.Model.CANLIN;
@@ -152,12 +152,13 @@ namespace CapMachine.Wpf.ViewModels
}
/// <summary>
/// CAN/LIN配置变更事件处理
/// CAN/LIN 配置导入后刷新当前页面。
/// </summary>
/// <param name="msg"></param>
/// <param name="msg">事件消息。</param>
private void CanLinConfigChangedEventCall(string msg)
{
RefreshConfigList();
InitLoadCanConfigPro();
InitWriteRuleCbx();
}
/// <summary>
@@ -948,18 +949,12 @@ namespace CapMachine.Wpf.ViewModels
/// <exception cref="NotImplementedException"></exception>
private void SchEnableCmdCall(object par)
{
var dd = SelectedCANConfigExdDto.SchEnable;
var Result = (bool)par;
if (Result)
if (SelectedCANConfigExdDto == null)
{
CanDriveService.ToomossCanDrive.SchEnable = true;
return;
}
else
{
CanDriveService.ToomossCanDrive.SchEnable = false;
}
CanDriveService.ToomossCanDrive.SchEnable = SelectedCANConfigExdDto.SchEnable;
}
@@ -1362,22 +1357,22 @@ namespace CapMachine.Wpf.ViewModels
{ "SelectCanLinConfigProId",SelectCanLinConfigPro.Id},
}, (par) =>
{
if (par.Result == ButtonResult.OK)
{
////程序名称
ListCANScheduleConfigDto = par.Parameters.GetValue<ObservableCollection<CANScheduleConfigDto>>("ReturnValue");
//把更新后的最新值给当前的主集合中
SelectCanLinConfigPro.CanScheduleConfigs = Mapper.Map<List<CANScheduleConfig>>(ListCANScheduleConfigDto.ToList());
{
if (par.Result == ButtonResult.OK)
{
////程序名称
ListCANScheduleConfigDto = par.Parameters.GetValue<ObservableCollection<CANScheduleConfigDto>>("ReturnValue");
//把更新后的最新值给当前的主集合中
SelectCanLinConfigPro.CanScheduleConfigs = Mapper.Map<List<CANScheduleConfig>>(ListCANScheduleConfigDto.ToList());
}
else if (par.Result == ButtonResult.Cancel)
{
//取消
}
else if (par.Result == ButtonResult.Cancel)
{
//取消
}
}
});
});
}
else
{

View File

@@ -172,12 +172,13 @@ namespace CapMachine.Wpf.ViewModels
}
/// <summary>
/// CAN/LIN配置变更事件处理
/// CAN/LIN 配置导入后刷新当前页面。
/// </summary>
/// <param name="msg"></param>
/// <param name="msg">事件消息。</param>
private void CanLinConfigChangedEventCall(string msg)
{
RefreshConfigList();
InitLoadCanConfigPro();
InitWriteRuleCbx();
}
/// <summary>
@@ -212,7 +213,7 @@ namespace CapMachine.Wpf.ViewModels
canLinConfigPros = FreeSql.Select<CanLinConfigPro>().Where(a => a.CANLINInfo == CANLIN.CANFD)
.Include(a => a.CANFdConfigExd)
.IncludeMany(a => a.CanLinConfigContents, then => then.Include(b => b.LogicRule))//,then=> then.Include(b=>b.LogicRule)
//.IncludeMany(a => a.CanLinConfigContents)
.IncludeMany(a => a.CanFdScheduleConfigs)//
.ToList();
ListCanLinConfigPro = new ObservableCollection<CanLinConfigPro>(canLinConfigPros);
@@ -271,6 +272,12 @@ namespace CapMachine.Wpf.ViewModels
ListReadCanLinRWConfigDto = new ObservableCollection<CanLinRWConfigDto>();
}
//调度表配置信息
if (SelectCanLinConfigPro.CanFdScheduleConfigs != null && SelectCanLinConfigPro.CanFdScheduleConfigs.Count() > 0)
{
ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>(Mapper.Map<List<CANFdScheduleConfigDto>>(SelectCanLinConfigPro.CanFdScheduleConfigs));
}
//匹配选中的SelectCanLinConfigPro.CanLinConfigContents和ListCanDbcModel
MatchSeletedAndCanDbcModel();
}
@@ -519,6 +526,8 @@ namespace CapMachine.Wpf.ViewModels
CanFdDriveService.InitCanConfig(SelectCanLinConfigPro);
InitLoadCanConfigPro();
CanFdDriveService.ToomossCanFDDrive.LoadCmdDataToDrive(CanFdDriveService.CmdData);
}
else
{
@@ -636,6 +645,18 @@ namespace CapMachine.Wpf.ViewModels
SelectCanLinConfigProConfigName = SelectCanLinConfigPro.ConfigName;
//调度表配置信息
if (SelectCanLinConfigPro.CanFdScheduleConfigs != null && SelectCanLinConfigPro.CanFdScheduleConfigs.Count() > 0)
{
ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>(Mapper.Map<List<CANFdScheduleConfigDto>>(SelectCanLinConfigPro.CanFdScheduleConfigs));
}
else
{
ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>();
}
return;
}
//先判断是否是正确的集合数据防止DataGrid的数据源刷新导致的触发事件
@@ -705,6 +726,33 @@ namespace CapMachine.Wpf.ViewModels
#endregion
private DelegateCommand _btnTestPLC;
/// <summary>
/// 临时测试的方法
/// </summary>
public DelegateCommand btnTestPLC
{
set
{
_btnTestPLC = value;
}
get
{
if (_btnTestPLC == null)
{
_btnTestPLC = new DelegateCommand(() => btnTestPLCCall());
}
return _btnTestPLC;
}
}
//临时测试的方法
private void btnTestPLCCall()
{
CanFdDriveService.ToomossCanFDDrive.UpdateSchDataByCmdDataChanged();
}
#region Dbc操作
private ObservableCollection<CanDbcModel> _ListCanDbcModel;
@@ -938,6 +986,43 @@ namespace CapMachine.Wpf.ViewModels
set { _ArbBaudRateCbxItems = value; RaisePropertyChanged(); }
}
private DelegateCommand<object> _SchEnableCmd;
/// <summary>
/// 调度表的使能 操作的指令
/// </summary>
public DelegateCommand<object> SchEnableCmd
{
set
{
_SchEnableCmd = value;
}
get
{
if (_SchEnableCmd == null)
{
_SchEnableCmd = new DelegateCommand<object>((Par) => SchEnableCmdCall(Par));
}
return _SchEnableCmd;
}
}
/// <summary>
/// 调度表的使能信息
/// </summary>
/// <param name="par"></param>
/// <exception cref="NotImplementedException"></exception>
private void SchEnableCmdCall(object par)
{
if (SelectedCANConfigExdDto == null)
{
return;
}
CanFdDriveService.ToomossCanFDDrive.SchEnable = SelectedCANConfigExdDto.SchEnable;
}
private DelegateCommand<string> _CanOpCmd;
/// <summary>
/// CAN操作的指令
@@ -1029,6 +1114,7 @@ namespace CapMachine.Wpf.ViewModels
.Set(a => a.ArbBaudRate, SelectedCANConfigExdDto.ArbBaudRate)
.Set(a => a.ISOEnable, SelectedCANConfigExdDto.ISOEnable)
.Set(a => a.ResEnable, SelectedCANConfigExdDto.ResEnable)
.Set(a => a.SchEnable, SelectedCANConfigExdDto.SchEnable)
.Where(a => a.Id == SelectedCANConfigExdDto.Id)
.ExecuteUpdated();
}
@@ -1090,6 +1176,12 @@ namespace CapMachine.Wpf.ViewModels
break;
case "CycleSend"://循环发送你
//有可能加载完毕后不手动改变状态,导致值没有传输,所以这里赋值
CanFdDriveService.ToomossCanFDDrive.SchEnable = SelectedCANConfigExdDto.SchEnable;
//无论有没有调度表都要把这个配置给CanDriveService
CanFdDriveService.ListCANFdScheduleConfig = ListCANFdScheduleConfigDto.ToList();
//循环发送数据
CanFdDriveService.CycleSendMsg();
@@ -1153,6 +1245,26 @@ namespace CapMachine.Wpf.ViewModels
set { _WriteRuleCbxItems = value; RaisePropertyChanged(); }
}
private ObservableCollection<CbxItems> _ReadRuleCbxItems;
/// <summary>
/// 读取的规格集合
/// </summary>
public ObservableCollection<CbxItems> ReadRuleCbxItems
{
get { return _ReadRuleCbxItems; }
set { _ReadRuleCbxItems = value; RaisePropertyChanged(); }
}
private ObservableCollection<CANFdScheduleConfigDto> _ListCANFdScheduleConfigDto;
/// <summary>
///调度表集合
/// </summary>
public ObservableCollection<CANFdScheduleConfigDto> ListCANFdScheduleConfigDto
{
get { return _ListCANFdScheduleConfigDto; }
set { _ListCANFdScheduleConfigDto = value; RaisePropertyChanged(); }
}
//private string _SelectedWriteName;
///// <summary>
///// 选中的写入的Name
@@ -1298,6 +1410,45 @@ namespace CapMachine.Wpf.ViewModels
System.Windows.MessageBox.Show("请选中后再进行【删除】操作?", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand);
}
break;
case "WriteSch"://调度表
var data = CanFdDriveService.CmdData.GroupBy(a => a.MsgName).Select(a => a.Key).ToList();
if (data != null && data.Count > 0)
{
//弹窗
DialogService.ShowDialog("DialogCANFdSchConfigView", new DialogParameters() {
{"ListMsg", CanFdDriveService.CmdData.GroupBy(a=>a.MsgName).Select(a=>a.Key).ToList() },
{ "ListCANFdScheduleConfigDto",ListCANFdScheduleConfigDto},
{ "SelectCanLinConfigProId",SelectCanLinConfigPro.Id},
}, (par) =>
{
if (par.Result == ButtonResult.OK)
{
////程序名称
ListCANFdScheduleConfigDto = par.Parameters.GetValue<ObservableCollection<CANFdScheduleConfigDto>>("ReturnValue");
//把更新后的最新值给当前的主集合中
SelectCanLinConfigPro.CanFdScheduleConfigs = Mapper.Map<List<CANFdScheduleConfig>>(ListCANFdScheduleConfigDto.ToList());
}
else if (par.Result == ButtonResult.Cancel)
{
//取消
}
});
}
else
{
System.Windows.MessageBox.Show("未发现写入的执行的命令数据,无法配置?", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand);
}
break;
default:
break;

View File

@@ -0,0 +1,377 @@
using AutoMapper;
using CapMachine.Core;
using CapMachine.Model.CANLIN;
using CapMachine.Wpf.Dtos;
using Prism.Commands;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace CapMachine.Wpf.ViewModels
{
/// <summary>
/// CAN Fd 调度表配置
/// </summary>
public class DialogCANFdSchConfigViewModel : DialogViewModel
{
/// <summary>
/// 构造函数
/// </summary>
public DialogCANFdSchConfigViewModel(IFreeSql freeSql, IMapper mapper)
{
Title = "调度表 CAN 配置";
FreeSql = freeSql;
Mapper = mapper;
SendOrderCbxItems = new ObservableCollection<CbxItems>()
{
new CbxItems(){
Key="0",
Text="并行",
},
new CbxItems(){
Key="1",
Text="顺序",
},
};
//默认只能用1号调度器
SchTabIndexCbxItems = new ObservableCollection<CbxItems>()
{
new CbxItems(){
Key="0",
Text="0",
},
//new CbxItems(){
// Key="1",
// Text="1",
//},
//new CbxItems(){
// Key="2",
// Text="2",
//},
//new CbxItems(){
// Key="3",
// Text="3",
//},
//new CbxItems(){
// Key="4",
// Text="4",
//},
};
}
public IFreeSql FreeSql { get; }
public IMapper Mapper { get; }
private string name;
/// <summary>
/// 名称
/// </summary>
public string Name
{
get { return name; }
set { name = value; RaisePropertyChanged(); }
}
private ObservableCollection<CANFdScheduleConfigDto> _ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>();
/// <summary>
/// CAN 调度表数据集合
/// </summary>
public ObservableCollection<CANFdScheduleConfigDto> ListCANFdScheduleConfigDto
{
get { return _ListCANFdScheduleConfigDto; }
set { _ListCANFdScheduleConfigDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// 消息/帧报文信息集合
/// </summary>
public List<string> ListMsg { get; set; }
private ObservableCollection<CbxItems> _MsgCbxItems;
/// <summary>
/// 消息名称 集合信息
/// </summary>
public ObservableCollection<CbxItems> MsgCbxItems
{
get { return _MsgCbxItems; }
set { _MsgCbxItems = value; RaisePropertyChanged(); }
}
/// <summary>
/// 选中的程序的Id
/// </summary>
public long SelectCanLinConfigProId { get; set; }
private ObservableCollection<CbxItems> _SendOrderCbxItems;
/// <summary>
/// 发送方式 集合信息
/// </summary>
public ObservableCollection<CbxItems> SendOrderCbxItems
{
get { return _SendOrderCbxItems; }
set { _SendOrderCbxItems = value; RaisePropertyChanged(); }
}
private string? _CurSendOrder;
/// <summary>
/// 当前发送方式
/// </summary>
public string? CurSendOrder
{
get { return _CurSendOrder; }
set { _CurSendOrder = value; RaisePropertyChanged(); }
}
private ObservableCollection<CbxItems> _SchTabIndexCbxItems;
/// <summary>
/// 调度器序号 集合信息
/// </summary>
public ObservableCollection<CbxItems> SchTabIndexCbxItems
{
get { return _SchTabIndexCbxItems; }
set { _SchTabIndexCbxItems = value; RaisePropertyChanged(); }
}
private CANFdScheduleConfigDto _CurSelectedItem;
/// <summary>
/// 选中的数据
/// </summary>
public CANFdScheduleConfigDto CurSelectedItem
{
get { return _CurSelectedItem; }
set { _CurSelectedItem = value; RaisePropertyChanged(); }
}
private DelegateCommand<object> _GridSelectionChangedCmd;
/// <summary>
/// 选中行数据命令
/// </summary>
public DelegateCommand<object> GridSelectionChangedCmd
{
set
{
_GridSelectionChangedCmd = value;
}
get
{
if (_GridSelectionChangedCmd == null)
{
_GridSelectionChangedCmd = new DelegateCommand<object>((par) => GridSelectionChangedCmdMethod(par));
}
return _GridSelectionChangedCmd;
}
}
private void GridSelectionChangedCmdMethod(object par)
{
//先判断是否是正确的集合数据防止DataGrid的数据源刷新导致的触发事件
var Selecteddata = par as CANFdScheduleConfigDto;
if (Selecteddata != null)
{
CurSelectedItem = Selecteddata;
}
}
//OpCmd
private DelegateCommand<string> _OpCmd;
/// <summary>
/// 增加方法命令
/// </summary>
public DelegateCommand<string> OpCmd
{
set
{
_OpCmd = value;
}
get
{
if (_OpCmd == null)
{
_OpCmd = new DelegateCommand<string>((Par) => OpCmdCall(Par));
}
return _OpCmd;
}
}
private void OpCmdCall(string Par)
{
switch (Par)
{
case "Add":
ListCANFdScheduleConfigDto.Add(new CANFdScheduleConfigDto
{
CanLinConfigProId = SelectCanLinConfigProId,
Cycle = 100,
SchTabIndex = 0,
OrderSend = 1,
});
break;
case "Delete":
if (CurSelectedItem != null)
{
//直接删除掉如果没有ID的话这就不需要删除了
FreeSql.Delete<CANFdScheduleConfig>(CurSelectedItem.Id).ExecuteAffrows();
ListCANFdScheduleConfigDto.Remove(CurSelectedItem);
CurSelectedItem = null;
}
else
{
MessageBox.Show("请选中后再进行【删除】操作?", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
}
break;
default:
break;
}
}
private DelegateCommand saveCmd;
/// <summary>
/// 保存命令
/// </summary>
public DelegateCommand SaveCmd
{
set
{
saveCmd = value;
}
get
{
if (saveCmd == null)
{
saveCmd = new DelegateCommand(() => SaveCmdMethod());
}
return saveCmd;
}
}
/// <summary>
/// 保存命令方法
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void SaveCmdMethod()
{
//检查空的数据
foreach (var item in ListCANFdScheduleConfigDto)
{
//整个的发送方式赋值给每个子项
item.OrderSend = CurSendOrder == "0" ? 0 : 1;
if (string.IsNullOrEmpty(item.MsgName))
{
MessageBox.Show("请确认消息名称是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
if (item.Cycle == 0)
{
MessageBox.Show("请确认周期是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
if (item.OrderSend >= 2)
{
MessageBox.Show("请确认发送方式是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
}
//发送的控制帧都放到同一个调度表中,不需要检查了
////检查重复设置问题
//bool isRepeat = ListCANScheduleConfigDto.GroupBy(i => i.MsgName).Any(g => g.Count() > 1);
//if (isRepeat)
//{
// MessageBox.Show("请确认是否重复设置", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
// return;
//}
//检查数据是否正常
foreach (var item in ListCANFdScheduleConfigDto)
{
FreeSql.InsertOrUpdate<CANFdScheduleConfig>()
.SetSource(Mapper.Map<CANFdScheduleConfig>(item)).
ExecuteAffrows();
}
ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>(Mapper.Map<List<CANFdScheduleConfigDto>>(FreeSql.Select<CANFdScheduleConfig>().Where(a => a.CanLinConfigProId == SelectCanLinConfigProId).ToList()));
DialogParameters pars = new DialogParameters
{
{ "ReturnValue", ListCANFdScheduleConfigDto }
};
RaiseRequestClose(new DialogResult(ButtonResult.OK, pars));
}
private DelegateCommand cancelCmd;
/// <summary>
/// 保存命令
/// </summary>
public DelegateCommand CancelCmd
{
set
{
cancelCmd = value;
}
get
{
if (cancelCmd == null)
{
cancelCmd = new DelegateCommand(() => CancelCmdMethod());
}
return cancelCmd;
}
}
/// <summary>
/// 取消命令方法
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void CancelCmdMethod()
{
RaiseRequestClose(new DialogResult(ButtonResult.Cancel));
}
/// <summary>
/// 窗口打开时的传递的参数
/// </summary>
/// <param name="parameters"></param>
public override void OnDialogOpened(IDialogParameters parameters)
{
ListMsg = parameters.GetValue<List<string>>("ListMsg");
// 转换为CbxItems集合都是文本内容
MsgCbxItems = new ObservableCollection<CbxItems>(
ListMsg.Select(value => new CbxItems
{
Key = value,
Text = value
}));
ListCANFdScheduleConfigDto = parameters.GetValue<ObservableCollection<CANFdScheduleConfigDto>>("ListCANFdScheduleConfigDto");
//防止返回的数据为空,就无法增加了
if (ListCANFdScheduleConfigDto == null) ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>();
//Name = parameters.GetValue<string>("Name");
if (ListCANFdScheduleConfigDto.Count > 0)
{
CurSendOrder = ListCANFdScheduleConfigDto.FirstOrDefault()!.OrderSend.ToString();
}
SelectCanLinConfigProId = parameters.GetValue<long>("SelectCanLinConfigProId");
}
}
}

View File

@@ -1,4 +1,4 @@
using CapMachine.Core;
using CapMachine.Core;
using CapMachine.Wpf.PrismEvent;
using CapMachine.Wpf.Services;
using Microsoft.Win32;

View File

@@ -0,0 +1,358 @@
using AutoMapper;
using CapMachine.Core;
using CapMachine.Model.CANLIN;
using CapMachine.Wpf.Dtos;
using Prism.Commands;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
namespace CapMachine.Wpf.ViewModels
{
/// <summary>
/// ZLG CAN FD 调度表配置弹窗 ViewModel。
/// 说明:
/// - 调度表项本身由“读写设置”弹窗中的“加入定时调度表”能力生成,这里主要负责:
/// 1) 设置发送方式(并行/顺序)
/// 2) 配置周期Cycle
/// 3) 删除与保存落库
/// - 保存时会使用 InsertOrUpdate 做 Upsert并在保存完成后从数据库重载作为权威数据再回传给调用方。
/// </summary>
public class DialogZlgCANFDSchConfigViewModel : DialogViewModel
{
/// <summary>
/// 构造函数。
/// </summary>
/// <param name="freeSql">FreeSql。</param>
/// <param name="mapper">AutoMapper。</param>
public DialogZlgCANFDSchConfigViewModel(IFreeSql freeSql, IMapper mapper)
{
Title = "调度表 CANFD 配置";
FreeSql = freeSql;
Mapper = mapper;
SendOrderCbxItems = new ObservableCollection<CbxItems>()
{
new CbxItems(){
Key="0",
Text="并行",
},
new CbxItems(){
Key="1",
Text="顺序",
},
};
SchTabIndexCbxItems = new ObservableCollection<CbxItems>()
{
new CbxItems(){
Key="0",
Text="0",
},
};
}
/// <summary>
/// FreeSql。
/// </summary>
public IFreeSql FreeSql { get; }
/// <summary>
/// AutoMapper。
/// </summary>
public IMapper Mapper { get; }
private ObservableCollection<CANFdScheduleConfigDto> _listCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>();
/// <summary>
/// CAN FD 调度表数据集合。
/// </summary>
public ObservableCollection<CANFdScheduleConfigDto> ListCANFdScheduleConfigDto
{
get { return _listCANFdScheduleConfigDto; }
set { _listCANFdScheduleConfigDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// 消息名列表。
/// </summary>
public List<string>? ListMsg { get; set; }
private ObservableCollection<CbxItems> _msgCbxItems;
/// <summary>
/// 消息名称下拉框数据。
/// </summary>
public ObservableCollection<CbxItems> MsgCbxItems
{
get { return _msgCbxItems; }
set { _msgCbxItems = value; RaisePropertyChanged(); }
}
/// <summary>
/// 选中的程序 Id。
/// </summary>
public long SelectCanLinConfigProId { get; set; }
private ObservableCollection<CbxItems> _sendOrderCbxItems;
/// <summary>
/// 发送方式集合。
/// </summary>
public ObservableCollection<CbxItems> SendOrderCbxItems
{
get { return _sendOrderCbxItems; }
set { _sendOrderCbxItems = value; RaisePropertyChanged(); }
}
private string? _curSendOrder;
/// <summary>
/// 当前发送方式。
/// </summary>
public string? CurSendOrder
{
get { return _curSendOrder; }
set { _curSendOrder = value; RaisePropertyChanged(); }
}
private ObservableCollection<CbxItems> _schTabIndexCbxItems;
/// <summary>
/// 调度器序号集合。
/// </summary>
public ObservableCollection<CbxItems> SchTabIndexCbxItems
{
get { return _schTabIndexCbxItems; }
set { _schTabIndexCbxItems = value; RaisePropertyChanged(); }
}
private CANFdScheduleConfigDto _curSelectedItem;
/// <summary>
/// 当前选中项。
/// </summary>
public CANFdScheduleConfigDto CurSelectedItem
{
get { return _curSelectedItem; }
set { _curSelectedItem = value; RaisePropertyChanged(); }
}
private DelegateCommand<object> _gridSelectionChangedCmd;
/// <summary>
/// DataGrid 选中行变化。
/// </summary>
public DelegateCommand<object> GridSelectionChangedCmd
{
get
{
if (_gridSelectionChangedCmd == null)
{
_gridSelectionChangedCmd = new DelegateCommand<object>(GridSelectionChangedCmdMethod);
}
return _gridSelectionChangedCmd;
}
}
/// <summary>
/// 选中行变化处理。
/// </summary>
/// <param name="par">选中项。</param>
private void GridSelectionChangedCmdMethod(object par)
{
var selected = par as CANFdScheduleConfigDto;
if (selected != null)
{
CurSelectedItem = selected;
}
}
private DelegateCommand<string> _opCmd;
/// <summary>
/// 操作命令(删除)。
/// </summary>
public DelegateCommand<string> OpCmd
{
get
{
if (_opCmd == null)
{
_opCmd = new DelegateCommand<string>(OpCmdCall);
}
return _opCmd;
}
}
/// <summary>
/// 操作命令处理。
/// </summary>
/// <param name="par">参数。</param>
private void OpCmdCall(string par)
{
switch (par)
{
case "Add":
MessageBox.Show("调度表项由【读写设置】中的写入配置通过“加入定时调度表”生成,这里不支持新增。", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
break;
case "Delete":
if (CurSelectedItem != null)
{
FreeSql.Delete<CANFdScheduleConfig>(CurSelectedItem.Id).ExecuteAffrows();
ListCANFdScheduleConfigDto.Remove(CurSelectedItem);
CurSelectedItem = null;
}
else
{
MessageBox.Show("请选中后再进行【删除】操作?", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
}
break;
default:
break;
}
}
private DelegateCommand _saveCmd;
/// <summary>
/// 保存命令。
/// </summary>
public DelegateCommand SaveCmd
{
get
{
if (_saveCmd == null)
{
_saveCmd = new DelegateCommand(SaveCmdMethod);
}
return _saveCmd;
}
}
/// <summary>
/// 保存命令处理。
/// </summary>
private void SaveCmdMethod()
{
// 校验:确保每个调度项具备最小可用字段,避免落库无效配置。
foreach (var item in ListCANFdScheduleConfigDto)
{
// UI 上的“发送方式”是全局下拉框,这里同步写回每个子项,确保落库数据一致。
item.OrderSend = CurSendOrder == "0" ? 0 : 1;
if (string.IsNullOrEmpty(item.MsgName))
{
MessageBox.Show("消息名为空:请在【读写设置】-> 写入配置中选择报文后点击“加入定时调度表”,再回到此处设置周期。", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
if (item.Cycle <= 0)
{
MessageBox.Show("请确认周期是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
if (item.OrderSend >= 2)
{
MessageBox.Show("请确认发送方式是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
}
foreach (var item in ListCANFdScheduleConfigDto)
{
// Upsert逐项落库。
FreeSql.InsertOrUpdate<CANFdScheduleConfig>()
.SetSource(Mapper.Map<CANFdScheduleConfig>(item))
.ExecuteAffrows();
}
// 保存完成后重载:
// - 避免 UI 集合与数据库实际数据不一致;
// - 同时让新增项获取真实 Id。
ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>(
Mapper.Map<List<CANFdScheduleConfigDto>>(
FreeSql.Select<CANFdScheduleConfig>()
.Where(a => a.CanLinConfigProId == SelectCanLinConfigProId)
.ToList()));
DialogParameters pars = new DialogParameters
{
{ "ReturnValue", ListCANFdScheduleConfigDto }
};
RaiseRequestClose(new DialogResult(ButtonResult.OK, pars));
}
private DelegateCommand _cancelCmd;
/// <summary>
/// 取消命令。
/// </summary>
public DelegateCommand CancelCmd
{
get
{
if (_cancelCmd == null)
{
_cancelCmd = new DelegateCommand(CancelCmdMethod);
}
return _cancelCmd;
}
}
/// <summary>
/// 取消命令处理。
/// </summary>
private void CancelCmdMethod()
{
RaiseRequestClose(new DialogResult(ButtonResult.Cancel));
}
/// <summary>
/// 弹窗打开时参数接收并初始化 UI。
/// 说明:
/// - ListMsg用于构造消息名下拉框候选
/// - ListCANFdScheduleConfigDto用于回显当前调度表
/// - SelectCanLinConfigProId用于 Save 后按配置程序 id 重载。
/// </summary>
/// <param name="parameters">参数。</param>
public override void OnDialogOpened(IDialogParameters parameters)
{
if (parameters.ContainsKey("ListMsg"))
{
ListMsg = parameters.GetValue<List<string>>("ListMsg");
if (ListMsg != null)
{
// 转换为 CbxItems 集合Key/Text 都使用消息名)。
MsgCbxItems = new ObservableCollection<CbxItems>(
ListMsg.Select(value => new CbxItems
{
Key = value,
Text = value
}));
}
}
ListCANFdScheduleConfigDto = parameters.GetValue<ObservableCollection<CANFdScheduleConfigDto>>("ListCANFdScheduleConfigDto");
if (ListCANFdScheduleConfigDto == null) ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>();
if (ListCANFdScheduleConfigDto.Count > 0)
{
// 回显:默认取第一条的 OrderSend。
CurSendOrder = ListCANFdScheduleConfigDto.FirstOrDefault()!.OrderSend.ToString();
}
else
{
// 默认:顺序发送。
CurSendOrder = "1";
}
SelectCanLinConfigProId = parameters.GetValue<long>("SelectCanLinConfigProId");
}
}
}

View File

@@ -0,0 +1,402 @@
using AutoMapper;
using CapMachine.Core;
using CapMachine.Model;
using CapMachine.Model.CANLIN;
using CapMachine.Wpf.Dtos;
using CapMachine.Wpf.Services;
using ImTools;
using Prism.Commands;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace CapMachine.Wpf.ViewModels
{
/// <summary>
/// ZLG CAN 调度表配置
/// </summary>
public class DialogZlgCANSchConfigViewModel : DialogViewModel
{
/// <summary>
/// 构造函数
/// </summary>
public DialogZlgCANSchConfigViewModel(IFreeSql freeSql, IMapper mapper)
{
Title = "调度表 CAN 配置";
FreeSql = freeSql;
Mapper = mapper;
SendOrderCbxItems = new ObservableCollection<CbxItems>()
{
new CbxItems(){
Key="0",
Text="并行",
},
new CbxItems(){
Key="1",
Text="顺序",
},
};
//默认只能用1号调度器
SchTabIndexCbxItems = new ObservableCollection<CbxItems>()
{
new CbxItems(){
Key="0",
Text="0",
},
//new CbxItems(){
// Key="1",
// Text="1",
//},
//new CbxItems(){
// Key="2",
// Text="2",
//},
//new CbxItems(){
// Key="3",
// Text="3",
//},
//new CbxItems(){
// Key="4",
// Text="4",
//},
};
}
/// <summary>
/// FreeSql用于调度表项的删除/保存落库)。
/// </summary>
public IFreeSql FreeSql { get; }
/// <summary>
/// AutoMapperDTO 与实体映射)。
/// </summary>
public IMapper Mapper { get; }
private string name;
/// <summary>
/// 名称
/// </summary>
public string Name
{
get { return name; }
set { name = value; RaisePropertyChanged(); }
}
private ObservableCollection<CANScheduleConfigDto> _ListCANScheduleConfigDto = new ObservableCollection<CANScheduleConfigDto>();
/// <summary>
/// CAN 调度表数据集合
/// </summary>
public ObservableCollection<CANScheduleConfigDto> ListCANScheduleConfigDto
{
get { return _ListCANScheduleConfigDto; }
set { _ListCANScheduleConfigDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// 消息/帧报文信息集合
/// </summary>
public List<string>? ListMsg { get; set; }
private ObservableCollection<CbxItems> _MsgCbxItems;
/// <summary>
/// 消息名称 集合信息
/// </summary>
public ObservableCollection<CbxItems> MsgCbxItems
{
get { return _MsgCbxItems; }
set { _MsgCbxItems = value; RaisePropertyChanged(); }
}
/// <summary>
/// 选中的程序的Id
/// </summary>
public long SelectCanLinConfigProId { get; set; }
private ObservableCollection<CbxItems> _SendOrderCbxItems;
/// <summary>
/// 发送方式 集合信息
/// </summary>
public ObservableCollection<CbxItems> SendOrderCbxItems
{
get { return _SendOrderCbxItems; }
set { _SendOrderCbxItems = value; RaisePropertyChanged(); }
}
private string? _CurSendOrder;
/// <summary>
/// 当前发送方式
/// </summary>
public string? CurSendOrder
{
get { return _CurSendOrder; }
set { _CurSendOrder = value; RaisePropertyChanged(); }
}
private ObservableCollection<CbxItems> _SchTabIndexCbxItems;
/// <summary>
/// 调度器序号 集合信息
/// </summary>
public ObservableCollection<CbxItems> SchTabIndexCbxItems
{
get { return _SchTabIndexCbxItems; }
set { _SchTabIndexCbxItems = value; RaisePropertyChanged(); }
}
private CANScheduleConfigDto _CurSelectedItem;
/// <summary>
/// 当前选中的调度表项(用于删除等操作)。
/// </summary>
public CANScheduleConfigDto CurSelectedItem
{
get { return _CurSelectedItem; }
set { _CurSelectedItem = value; RaisePropertyChanged(); }
}
private DelegateCommand<object> _GridSelectionChangedCmd;
/// <summary>
/// DataGrid 选中行变化。
/// </summary>
public DelegateCommand<object> GridSelectionChangedCmd
{
set
{
_GridSelectionChangedCmd = value;
}
get
{
if (_GridSelectionChangedCmd == null)
{
_GridSelectionChangedCmd = new DelegateCommand<object>((par) => GridSelectionChangedCmdMethod(par));
}
return _GridSelectionChangedCmd;
}
}
private void GridSelectionChangedCmdMethod(object par)
{
// 先判断是否是正确的集合数据,防止 DataGrid 的 ItemsSource 刷新导致的误触发。
var Selecteddata = par as CANScheduleConfigDto;
if (Selecteddata != null)
{
CurSelectedItem = Selecteddata;
}
}
private DelegateCommand<string> _OpCmd;
/// <summary>
/// 操作命令(删除)。
/// 说明:新增调度表项由“读写设置”弹窗内的“加入定时调度表”生成,此处仅允许删除与保存。
/// </summary>
public DelegateCommand<string> OpCmd
{
set
{
_OpCmd = value;
}
get
{
if (_OpCmd == null)
{
_OpCmd = new DelegateCommand<string>((Par) => OpCmdCall(Par));
}
return _OpCmd;
}
}
private void OpCmdCall(string Par)
{
switch (Par)
{
case "Add":
MessageBox.Show("调度表项由【读写设置】中的写入配置通过“加入定时调度表”生成,这里不支持新增。", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
break;
case "Delete":
if (CurSelectedItem != null)
{
// 直接落库删除(无论 UI 是否还有引用,都以数据库为准)。
FreeSql.Delete<CANScheduleConfig>(CurSelectedItem.Id).ExecuteAffrows();
ListCANScheduleConfigDto.Remove(CurSelectedItem);
CurSelectedItem = null;
}
else
{
MessageBox.Show("请选中后再进行【删除】操作?", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
}
break;
default:
break;
}
}
private DelegateCommand saveCmd;
/// <summary>
/// 保存命令
/// </summary>
public DelegateCommand SaveCmd
{
set
{
saveCmd = value;
}
get
{
if (saveCmd == null)
{
saveCmd = new DelegateCommand(() => SaveCmdMethod());
}
return saveCmd;
}
}
/// <summary>
/// 保存命令方法。
/// 说明:
/// - 先做必要校验MsgName/Cycle/发送方式等),避免写入无效配置;
/// - 使用 InsertOrUpdate 保证“新增/修改”统一处理;
/// - 保存完成后重新从数据库加载,作为最终权威数据,并通过 DialogResult 回传给调用方。
/// </summary>
private void SaveCmdMethod()
{
// 校验:确保每个调度项具备最小可用字段。
foreach (var item in ListCANScheduleConfigDto)
{
// UI 上的“发送方式”是全局下拉框,这里同步写回到每个子项,确保落库数据一致。
item.OrderSend = CurSendOrder == "0" ? 0 : 1;
if (string.IsNullOrEmpty(item.MsgName))
{
MessageBox.Show("消息名为空:请在【读写设置】-> 写入配置中选择报文后点击“加入定时调度表”,再回到此处设置周期。", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
if (item.Cycle <= 0)
{
MessageBox.Show("请确认周期是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
if (item.OrderSend >= 2)
{
MessageBox.Show("请确认发送方式是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
}
//发送的控制帧都放到同一个调度表中,不需要检查了
////检查重复设置问题
//bool isRepeat = ListCANScheduleConfigDto.GroupBy(i => i.MsgName).Any(g => g.Count() > 1);
//if (isRepeat)
//{
// MessageBox.Show("请确认是否重复设置", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
// return;
//}
// Upsert逐项落库。
foreach (var item in ListCANScheduleConfigDto)
{
FreeSql.InsertOrUpdate<CANScheduleConfig>()
.SetSource(Mapper.Map<CANScheduleConfig>(item)).
ExecuteAffrows();
}
// 保存完成后重新加载:
// - 防止 UI 缓存与数据库实际数据不一致;
// - 同时也让新增项拿到真实 Id。
ListCANScheduleConfigDto = new ObservableCollection<CANScheduleConfigDto>(Mapper.Map<List<CANScheduleConfigDto>>(FreeSql.Select<CANScheduleConfig>().Where(a => a.CanLinConfigProId == SelectCanLinConfigProId).ToList()));
DialogParameters pars = new DialogParameters
{
{ "ReturnValue", ListCANScheduleConfigDto }
};
RaiseRequestClose(new DialogResult(ButtonResult.OK, pars));
}
private DelegateCommand cancelCmd;
/// <summary>
/// 取消命令。
/// </summary>
public DelegateCommand CancelCmd
{
set
{
cancelCmd = value;
}
get
{
if (cancelCmd == null)
{
cancelCmd = new DelegateCommand(() => CancelCmdMethod());
}
return cancelCmd;
}
}
/// <summary>
/// 取消命令方法。
/// </summary>
private void CancelCmdMethod()
{
RaiseRequestClose(new DialogResult(ButtonResult.Cancel));
}
/// <summary>
/// 弹窗打开时接收参数并初始化 UI。
/// 说明:
/// - ListMsg 用于构造消息名下拉框;
/// - ListCANScheduleConfigDto 用于回显现有调度表;
/// - SelectCanLinConfigProId 用于 Save 时按配置程序 id 过滤重载。
/// </summary>
/// <param name="parameters"></param>
public override void OnDialogOpened(IDialogParameters parameters)
{
if (parameters.ContainsKey("ListMsg"))
{
ListMsg = parameters.GetValue<List<string>>("ListMsg");
if (ListMsg != null)
{
// 转换为CbxItems集合都是文本内容
MsgCbxItems = new ObservableCollection<CbxItems>(
ListMsg.Select(value => new CbxItems
{
Key = value,
Text = value
}));
}
}
ListCANScheduleConfigDto = parameters.GetValue<ObservableCollection<CANScheduleConfigDto>>("ListCANScheduleConfigDto");
//防止返回的数据为空,就无法增加了
if (ListCANScheduleConfigDto == null) ListCANScheduleConfigDto = new ObservableCollection<CANScheduleConfigDto>();
//Name = parameters.GetValue<string>("Name");
if (ListCANScheduleConfigDto.Count > 0)
{
// 回显:默认取第一条的 OrderSend 作为当前下拉框选择。
CurSendOrder = ListCANScheduleConfigDto.FirstOrDefault()!.OrderSend.ToString();
}
else
{
// 默认:顺序发送。
CurSendOrder = "1";
}
SelectCanLinConfigProId = parameters.GetValue<long>("SelectCanLinConfigProId");
}
}
}

View File

@@ -0,0 +1,904 @@
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_sendLIN 当前使用软件调度,不允许写入 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按 keyRWInfo + 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>();
}
}
}

View File

@@ -1,9 +1,10 @@
using AutoMapper;
using AutoMapper;
using CapMachine.Core;
using CapMachine.Model.CANLIN;
using CapMachine.Wpf.CanDrive;
using CapMachine.Wpf.Dtos;
using CapMachine.Wpf.LinDrive;
using CapMachine.Wpf.PrismEvent;
using CapMachine.Wpf.Services;
using Prism.Commands;
using Prism.Events;
@@ -55,8 +56,6 @@ namespace CapMachine.Wpf.ViewModels
//MachineDataService = machineDataService;
DialogService = dialogService;
EventAggregator.GetEvent<LogicRuleChangeEvent>().Subscribe(LogicRuleChangeEventCall);
EventAggregator.GetEvent<CanLinConfigChangedEvent>().Subscribe(CanLinConfigChangedEventCall);
//数据波特率
@@ -148,12 +147,13 @@ namespace CapMachine.Wpf.ViewModels
}
/// <summary>
/// CAN/LIN配置变更事件处理
/// CAN/LIN 配置导入后刷新当前页面。
/// </summary>
/// <param name="msg"></param>
/// <param name="msg">事件消息。</param>
private void CanLinConfigChangedEventCall(string msg)
{
RefreshConfigList();
InitLoadLinConfigPro();
InitWriteRuleCbx();
}
/// <summary>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff