Files
CapMachine/CapMachine.Wpf/ViewModels/DialogLINSchConfigViewModel.cs
2025-10-15 09:54:46 +08:00

582 lines
21 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<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",
//},
};
// 初始化树结构集合
SchTabTree = new ObservableCollection<LinSchTabNode>();
}
public IFreeSql FreeSql { get; }
public IMapper Mapper { get; }
public LinDriveService LinDriveService { get; }
private string name;
/// <summary>
/// 名称
/// </summary>
public string Name
{
get { return name; }
set { name = value; RaisePropertyChanged(); }
}
private ObservableCollection<LINScheduleConfigDto> _ListLINScheduleConfigDto = new ObservableCollection<LINScheduleConfigDto>();
/// <summary>
/// LIN 调度表数据集合
/// </summary>
public ObservableCollection<LINScheduleConfigDto> ListLINScheduleConfigDto
{
get { return _ListLINScheduleConfigDto; }
set { _ListLINScheduleConfigDto = 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> _SchTabIndexCbxItems;
/// <summary>
/// 调度器序号 集合信息
/// </summary>
public ObservableCollection<CbxItems> SchTabIndexCbxItems
{
get { return _SchTabIndexCbxItems; }
set { _SchTabIndexCbxItems = value; RaisePropertyChanged(); }
}
private LINScheduleConfigDto _CurSelectedItem;
/// <summary>
/// 选中的数据
/// </summary>
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<LinSchTabNode> _SchTabTree;
/// <summary>
/// 调度表-消息 树结构数据源SchTabName消息/帧)
/// </summary>
public ObservableCollection<LinSchTabNode> SchTabTree
{
get { return _SchTabTree; }
set { _SchTabTree = value; RaisePropertyChanged(); }
}
// 防止选择联动时的递归重入。
// 在 OnSelectSchTab() 中统一设置 _isUpdatingSelection = true
// 以避免模型属性变化再次触发 Node_PropertyChanged() / OnSelectSchTab() 形成递归导致 StackOverflow。
private bool _isUpdatingSelection = false;
private DelegateCommand<LinSchTabNode> _SelectSchTabCmd;
/// <summary>
/// 选择/激活 调度表命令(单选),联动选中其下所有消息
/// </summary>
public DelegateCommand<LinSchTabNode> SelectSchTabCmd
{
get
{
if (_SelectSchTabCmd == null)
{
_SelectSchTabCmd = new DelegateCommand<LinSchTabNode>(OnSelectSchTab);
}
return _SelectSchTabCmd;
}
set { _SelectSchTabCmd = value; }
}
/// <summary>
/// 激活选中的调度表(父节点单选),并联动:
/// 1) 取消其它调度表的选中;
/// 2) 仅控制子节点可编辑状态(不强制选中/取消),子节点勾选独立由用户控制;
/// 3) 同步底层 ListLINScheduleConfigDto 的 IsActive只允许一个调度表 Active
/// 通过 _isUpdatingSelection 防重入,避免模型 -> 视图 -> 模型 的递归通知。
/// </summary>
/// <param name="node"></param>
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;
}
}
/// <summary>
/// 基于当前 ListLINScheduleConfigDto 构建树结构。
/// - 将 DTO 以 SchTabName 分组,生成父节点;
/// - 子节点为每条消息/帧MsgName, MsgNameIndexIsSelected 与 DTO.IsActive 同步;
/// - 若出现多个分组均被标记 IsActive则仅保留第一个为选中其它重置为未选中保证单选约束
/// - 构建完成后调用 AttachNodeHandlers() 订阅父节点的 PropertyChanged实现勾选即联动。
/// </summary>
private void BuildTree()
{
var newTree = new ObservableCollection<LinSchTabNode>();
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<LinMsgNode>(
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);
}
/// <summary>
/// 订阅父节点集合的 PropertyChanged。
/// 注意:子节点当前仅用于显示,不处理其 PropertyChanged。
/// </summary>
private void AttachNodeHandlers(IEnumerable<LinSchTabNode> 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;
}
}
}
}
/// <summary>
/// 父节点属性变更回调:当 IsSelected 变化时,统一交由 OnSelectSchTab() 处理单选与联动。
/// </summary>
private void Node_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(LinSchTabNode.IsSelected))
{
OnSelectSchTab(sender as LinSchTabNode);
}
}
/// <summary>
/// 子节点属性变更:当 IsSelected 变化时,回写对应 DTO.IsMsgActived。
/// 仅当其父节点为选中状态时允许写回CanEdit=true
/// </summary>
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<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 LINScheduleConfigDto;
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)
{
// 禁止新增/删除:来自 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;
}
}
/// <summary>
/// 加载调度表
/// </summary>
private void LoadSch()
{
var ListLINScheduleConfig = LinDriveService.ToomossLinDrive.GetLINScheduleConfigs();
ListLINScheduleConfigDto.Clear();
//先清空
FreeSql.Delete<LINScheduleConfig>()
.Where(a => a.CanLinConfigProId == SelectCanLinConfigProId)
.ExecuteAffrows();
foreach (var item in ListLINScheduleConfig)
{
ListLINScheduleConfigDto.Add(Mapper.Map<LINScheduleConfigDto>(item));
}
// 重新构建树
BuildTree();
}
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 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<LINScheduleConfig>()
.SetSource(Mapper.Map<LINScheduleConfig>(item))
.ExecuteAffrows();
}
ListLINScheduleConfigDto = new ObservableCollection<LINScheduleConfigDto>(
Mapper.Map<List<LINScheduleConfigDto>>(FreeSql.Select<LINScheduleConfig>().Where(a => a.CanLinConfigProId == SelectCanLinConfigProId).ToList()));
// 重新构建树,反映保存结果
BuildTree();
DialogParameters pars = new DialogParameters
{
{ "ReturnValue", ListLINScheduleConfigDto }
};
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
}));
ListLINScheduleConfigDto = parameters.GetValue<ObservableCollection<LINScheduleConfigDto>>("ListLINScheduleConfigDto");
//防止返回的数据为空,就无法增加了
if (ListLINScheduleConfigDto == null) ListLINScheduleConfigDto = new ObservableCollection<LINScheduleConfigDto>();
//Name = parameters.GetValue<string>("Name");
SelectCanLinConfigProId = parameters.GetValue<long>("SelectCanLinConfigProId");
// 初次打开时构建树,并根据已有 IsActive 状态渲染
BuildTree();
}
}
}