using AutoMapper; using CapMachine.Core; using CapMachine.Model.CANLIN; using CapMachine.Wpf.Dtos; using CapMachine.Wpf.Services; using Prism.Commands; using Prism.Services.Dialogs; using System.Collections.ObjectModel; using System.Windows; using System.Linq; using System.ComponentModel; using System; namespace CapMachine.Wpf.ViewModels { public class DialogLINSchConfigViewModel : DialogViewModel { public DialogLINSchConfigViewModel(IFreeSql freeSql, IMapper mapper, LinDriveService linDriveService) { Title = "调度表 LIN 配置"; FreeSql = freeSql; Mapper = mapper; LinDriveService = linDriveService; //默认只能用1号调度器 SchTabIndexCbxItems = new ObservableCollection() { 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", //}, }; // 初始化树结构集合 SchTabTree = new ObservableCollection(); } public IFreeSql FreeSql { get; } public IMapper Mapper { get; } public LinDriveService LinDriveService { get; } private string name; /// /// 名称 /// public string Name { get { return name; } set { name = value; RaisePropertyChanged(); } } private ObservableCollection _ListLINScheduleConfigDto = new ObservableCollection(); /// /// LIN 调度表数据集合 /// public ObservableCollection ListLINScheduleConfigDto { get { return _ListLINScheduleConfigDto; } set { _ListLINScheduleConfigDto = value; RaisePropertyChanged(); } } /// /// 消息/帧报文信息集合 /// public List ListMsg { get; set; } private ObservableCollection _MsgCbxItems; /// /// 消息名称 集合信息 /// public ObservableCollection MsgCbxItems { get { return _MsgCbxItems; } set { _MsgCbxItems = value; RaisePropertyChanged(); } } /// /// 选中的程序的Id /// public long SelectCanLinConfigProId { get; set; } private ObservableCollection _SchTabIndexCbxItems; /// /// 调度器序号 集合信息 /// public ObservableCollection SchTabIndexCbxItems { get { return _SchTabIndexCbxItems; } set { _SchTabIndexCbxItems = value; RaisePropertyChanged(); } } private LINScheduleConfigDto _CurSelectedItem; /// /// 选中的数据 /// public LINScheduleConfigDto CurSelectedItem { get { return _CurSelectedItem; } set { _CurSelectedItem = value; RaisePropertyChanged(); } } //================ TreeView 数据源与选择逻辑 ================ // 说明: // 1) SchTabTree 为 XAML 中 TreeView 的 ItemsSource,父节点为调度表 LinSchTabNode,子节点为 LinMsgNode。 // 2) 仅允许“单选”一个调度表(父节点)。父节点上的 CheckBox 为 TwoWay 绑定 ViewModel 模型的 IsSelected 属性。 // 3) 我们在 BuildTree() 完成后,通过 AttachNodeHandlers() 给父节点订阅 PropertyChanged, // 当 IsSelected 变化时,统一走 OnSelectSchTab() 做单选互斥与子节点联动,避免在 XAML 上用行为触发器造成递归。 private ObservableCollection _SchTabTree; /// /// 调度表-消息 树结构数据源(父:SchTabName,子:消息/帧) /// public ObservableCollection SchTabTree { get { return _SchTabTree; } set { _SchTabTree = value; RaisePropertyChanged(); } } // 防止选择联动时的递归重入。 // 在 OnSelectSchTab() 中统一设置 _isUpdatingSelection = true, // 以避免模型属性变化再次触发 Node_PropertyChanged() / OnSelectSchTab() 形成递归导致 StackOverflow。 private bool _isUpdatingSelection = false; private DelegateCommand _SelectSchTabCmd; /// /// 选择/激活 调度表命令(单选),联动选中其下所有消息 /// public DelegateCommand SelectSchTabCmd { get { if (_SelectSchTabCmd == null) { _SelectSchTabCmd = new DelegateCommand(OnSelectSchTab); } return _SelectSchTabCmd; } set { _SelectSchTabCmd = value; } } /// /// 激活选中的调度表(父节点单选),并联动: /// 1) 取消其它调度表的选中; /// 2) 仅控制子节点可编辑状态(不强制选中/取消),子节点勾选独立由用户控制; /// 3) 同步底层 ListLINScheduleConfigDto 的 IsActive(只允许一个调度表 Active)。 /// 通过 _isUpdatingSelection 防重入,避免模型 -> 视图 -> 模型 的递归通知。 /// /// private void OnSelectSchTab(LinSchTabNode node) { if (node == null) return; if (_isUpdatingSelection) return; try { _isUpdatingSelection = true; // 单选:当前节点选中时,取消其它调度表选中 if (node.IsSelected) { foreach (var n in SchTabTree) { bool isTarget = ReferenceEquals(n, node); n.IsSelected = isTarget; // 仅控制可编辑性 foreach (var c in n.Children) c.CanEdit = isTarget; } } else { // 取消选中:仅禁止子节点编辑,保留勾选状态 foreach (var c in node.Children) c.CanEdit = false; } // 同步 DTO 激活状态:按当前树中第一个被选中的调度表名写回 var active = SchTabTree?.FirstOrDefault(n => n.IsSelected); foreach (var dto in ListLINScheduleConfigDto) { dto.IsActive = active != null && string.Equals(dto.SchTabName, active.SchTabName, StringComparison.Ordinal); } } finally { _isUpdatingSelection = false; } } /// /// 基于当前 ListLINScheduleConfigDto 构建树结构。 /// - 将 DTO 以 SchTabName 分组,生成父节点; /// - 子节点为每条消息/帧(MsgName, MsgNameIndex),IsSelected 与 DTO.IsActive 同步; /// - 若出现多个分组均被标记 IsActive,则仅保留第一个为选中,其它重置为未选中(保证单选约束); /// - 构建完成后调用 AttachNodeHandlers() 订阅父节点的 PropertyChanged,实现勾选即联动。 /// private void BuildTree() { var newTree = new ObservableCollection(); if (ListLINScheduleConfigDto != null && ListLINScheduleConfigDto.Any()) { var groups = ListLINScheduleConfigDto .GroupBy(x => x.SchTabName ?? string.Empty) .OrderBy(g => g.Key); foreach (var g in groups) { var node = new LinSchTabNode { SchTabName = g.Key, IsSelected = g.Any(x => x.IsActive), Children = new ObservableCollection( g.Select(x => new LinMsgNode { SchTabName = g.Key, MsgName = x.MsgName ?? string.Empty, MsgNameIndex = x.MsgNameIndex, IsSelected = x.IsMsgActived, CanEdit = g.Any(t => t.IsActive) // 仅激活的调度表可编辑 })) }; newTree.Add(node); } // 如果没有任何 IsActive,确保默认不选;若存在多个组被标记,保持第一个,其他置为 false var activeOnes = newTree.Where(n => n.IsSelected).ToList(); if (activeOnes.Count > 1) { var keep = activeOnes.First(); foreach (var n in activeOnes.Skip(1)) n.IsSelected = false; foreach (var dto in ListLINScheduleConfigDto) { dto.IsActive = string.Equals(dto.SchTabName, keep.SchTabName, StringComparison.Ordinal); } } } SchTabTree = newTree; // 绑定节点属性变化事件: // - 父节点 IsSelected 变化 -> Node_PropertyChanged() -> OnSelectSchTab() // - 子节点 IsSelected 变化 -> Child_PropertyChanged() -> 回写 DTO.IsMsgActived // 这样可以不依赖 XAML EventTrigger,减少 UI 侧的递归触发风险。 AttachNodeHandlers(SchTabTree); } /// /// 订阅父节点集合的 PropertyChanged。 /// 注意:子节点当前仅用于显示,不处理其 PropertyChanged。 /// private void AttachNodeHandlers(IEnumerable nodes) { if (nodes == null) return; foreach (var n in nodes) { n.PropertyChanged -= Node_PropertyChanged; n.PropertyChanged += Node_PropertyChanged; if (n.Children != null && n.Children.Any()) { foreach (var c in n.Children) { c.PropertyChanged -= Child_PropertyChanged; c.PropertyChanged += Child_PropertyChanged; } } } } /// /// 父节点属性变更回调:当 IsSelected 变化时,统一交由 OnSelectSchTab() 处理单选与联动。 /// private void Node_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == nameof(LinSchTabNode.IsSelected)) { OnSelectSchTab(sender as LinSchTabNode); } } /// /// 子节点属性变更:当 IsSelected 变化时,回写对应 DTO.IsMsgActived。 /// 仅当其父节点为选中状态时允许写回(CanEdit=true)。 /// private void Child_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName != nameof(LinMsgNode.IsSelected)) return; if (_isUpdatingSelection) return; try { _isUpdatingSelection = true; var child = sender as LinMsgNode; if (child == null) return; // 查找父节点 var parent = SchTabTree?.FirstOrDefault(n => n.Children.Contains(child)); if (parent == null) return; // 仅在父节点选中(可编辑)时回写 DTO if (!parent.IsSelected || !child.CanEdit) return; var dto = ListLINScheduleConfigDto?.FirstOrDefault(d => string.Equals(d.SchTabName ?? string.Empty, parent.SchTabName ?? string.Empty, StringComparison.Ordinal) && string.Equals(d.MsgName ?? string.Empty, child.MsgName ?? string.Empty, StringComparison.Ordinal) && d.MsgNameIndex == child.MsgNameIndex); if (dto != null) { dto.IsMsgActived = child.IsSelected; } } finally { _isUpdatingSelection = false; } } private DelegateCommand _GridSelectionChangedCmd; /// /// 选中行数据命令 /// public DelegateCommand GridSelectionChangedCmd { set { _GridSelectionChangedCmd = value; } get { if (_GridSelectionChangedCmd == null) { _GridSelectionChangedCmd = new DelegateCommand((par) => GridSelectionChangedCmdMethod(par)); } return _GridSelectionChangedCmd; } } private void GridSelectionChangedCmdMethod(object par) { //先判断是否是正确的集合数据,防止DataGrid的数据源刷新导致的触发事件 var Selecteddata = par as LINScheduleConfigDto; if (Selecteddata != null) { CurSelectedItem = Selecteddata; } } //OpCmd private DelegateCommand _OpCmd; /// /// 增加方法命令 /// public DelegateCommand OpCmd { set { _OpCmd = value; } get { if (_OpCmd == null) { _OpCmd = new DelegateCommand((Par) => OpCmdCall(Par)); } return _OpCmd; } } private void OpCmdCall(string Par) { switch (Par) { // 禁止新增/删除:来自 LDF 的数据不允许变更 case "LoadSch": //通过CAN驱动加载调度表 if (ListLINScheduleConfigDto != null && ListLINScheduleConfigDto.Count > 0) { var messageBoxResult = MessageBox.Show("检测到当前存在数据,下载后将要覆盖当前数据,请你确认?", "提示", MessageBoxButton.OKCancel, MessageBoxImage.Hand); if (messageBoxResult == MessageBoxResult.OK) { LoadSch(); } else { //不动作 } } else { LoadSch(); } break; default: break; } } /// /// 加载调度表 /// private void LoadSch() { var ListLINScheduleConfig = LinDriveService.ToomossLinDrive.GetLINScheduleConfigs(); ListLINScheduleConfigDto.Clear(); //先清空 FreeSql.Delete() .Where(a => a.CanLinConfigProId == SelectCanLinConfigProId) .ExecuteAffrows(); foreach (var item in ListLINScheduleConfig) { ListLINScheduleConfigDto.Add(Mapper.Map(item)); } // 重新构建树 BuildTree(); } private DelegateCommand saveCmd; /// /// 保存命令 /// public DelegateCommand SaveCmd { set { saveCmd = value; } get { if (saveCmd == null) { saveCmd = new DelegateCommand(() => SaveCmdMethod()); } return saveCmd; } } /// /// 保存命令方法 /// /// private void SaveCmdMethod() { //检查空的数据 foreach (var item in ListLINScheduleConfigDto) { if (string.IsNullOrEmpty(item.MsgName)) { MessageBox.Show("请确认消息名称是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } //if (item.Cycle == 0) //{ // MessageBox.Show("请确认周期是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); // return; //} } // 保存前:根据树中选中的调度表更新 DTO 的 IsActive,确保只保留一个激活调度表 var activeNode = SchTabTree?.FirstOrDefault(n => n.IsSelected); foreach (var dto in ListLINScheduleConfigDto) { dto.IsActive = activeNode != null && string.Equals(dto.SchTabName, activeNode.SchTabName, StringComparison.Ordinal); } // 保存前:将子节点的勾选状态写回 DTO.IsMsgActived foreach (var parent in SchTabTree) { foreach (var child in parent.Children) { var dto = ListLINScheduleConfigDto.FirstOrDefault(d => string.Equals(d.SchTabName ?? string.Empty, parent.SchTabName ?? string.Empty, StringComparison.Ordinal) && string.Equals(d.MsgName ?? string.Empty, child.MsgName ?? string.Empty, StringComparison.Ordinal) && d.MsgNameIndex == child.MsgNameIndex); if (dto != null) { dto.IsMsgActived = child.IsSelected; } } } //检查数据是否正常并持久化(结构来自 LDF,不允许新增/删除,仅更新激活状态与周期等) foreach (var item in ListLINScheduleConfigDto) { item.CanLinConfigProId = SelectCanLinConfigProId; FreeSql.InsertOrUpdate() .SetSource(Mapper.Map(item)) .ExecuteAffrows(); } ListLINScheduleConfigDto = new ObservableCollection( Mapper.Map>(FreeSql.Select().Where(a => a.CanLinConfigProId == SelectCanLinConfigProId).ToList())); // 重新构建树,反映保存结果 BuildTree(); DialogParameters pars = new DialogParameters { { "ReturnValue", ListLINScheduleConfigDto } }; RaiseRequestClose(new DialogResult(ButtonResult.OK, pars)); } private DelegateCommand cancelCmd; /// /// 保存命令 /// public DelegateCommand CancelCmd { set { cancelCmd = value; } get { if (cancelCmd == null) { cancelCmd = new DelegateCommand(() => CancelCmdMethod()); } return cancelCmd; } } /// /// 取消命令方法 /// /// private void CancelCmdMethod() { RaiseRequestClose(new DialogResult(ButtonResult.Cancel)); } /// /// 窗口打开时的传递的参数 /// /// public override void OnDialogOpened(IDialogParameters parameters) { ListMsg = parameters.GetValue>("ListMsg"); // 转换为CbxItems集合,都是文本内容 MsgCbxItems = new ObservableCollection( ListMsg.Select(value => new CbxItems { Key = value, Text = value })); ListLINScheduleConfigDto = parameters.GetValue>("ListLINScheduleConfigDto"); //防止返回的数据为空,就无法增加了 if (ListLINScheduleConfigDto == null) ListLINScheduleConfigDto = new ObservableCollection(); //Name = parameters.GetValue("Name"); SelectCanLinConfigProId = parameters.GetValue("SelectCanLinConfigProId"); // 初次打开时构建树,并根据已有 IsActive 状态渲染 BuildTree(); } } }