From 9b7c91eae9853b9dbb846b9335761fe7208534ac Mon Sep 17 00:00:00 2001 From: "David P.G" Date: Tue, 2 Sep 2025 13:09:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3TreeView=E7=9A=84TreeView=5FS?= =?UTF-8?q?electedItemChanged=E4=BA=8B=E4=BB=B6=E8=A2=AB=E5=A4=9A=E6=AC=A1?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DMS.WPF/Profiles/MappingProfile.cs | 3 + .../Dialogs/ImportOpcUaDialogViewModel.cs | 262 ++++++------------ .../Items/OpcUaNodeItemViewModel.cs | 49 ++++ .../ViewModels/Items/OpcUaNodeViewModel.cs | 50 ---- DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml | 19 +- .../Views/Dialogs/ImportOpcUaDialog.xaml.cs | 76 +++-- 6 files changed, 208 insertions(+), 251 deletions(-) create mode 100644 DMS.WPF/ViewModels/Items/OpcUaNodeItemViewModel.cs delete mode 100644 DMS.WPF/ViewModels/Items/OpcUaNodeViewModel.cs diff --git a/DMS.WPF/Profiles/MappingProfile.cs b/DMS.WPF/Profiles/MappingProfile.cs index a8fa396..1b3c28c 100644 --- a/DMS.WPF/Profiles/MappingProfile.cs +++ b/DMS.WPF/Profiles/MappingProfile.cs @@ -1,6 +1,7 @@ using AutoMapper; using DMS.Application.DTOs; using DMS.Core.Models; +using DMS.Infrastructure.Models; using DMS.WPF.ViewModels.Items; namespace DMS.WPF.Profiles @@ -13,6 +14,8 @@ namespace DMS.WPF.Profiles .ReverseMap(); CreateMap() .ReverseMap(); + CreateMap() + .ReverseMap(); CreateMap(); diff --git a/DMS.WPF/ViewModels/Dialogs/ImportOpcUaDialogViewModel.cs b/DMS.WPF/ViewModels/Dialogs/ImportOpcUaDialogViewModel.cs index 93e44a8..64f752d 100644 --- a/DMS.WPF/ViewModels/Dialogs/ImportOpcUaDialogViewModel.cs +++ b/DMS.WPF/ViewModels/Dialogs/ImportOpcUaDialogViewModel.cs @@ -1,5 +1,8 @@ +using AutoMapper; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using DMS.Core.Enums; +using DMS.Core.Helper; using DMS.Core.Models; using DMS.Helper; using DMS.Infrastructure.Interfaces.Services; @@ -17,10 +20,10 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase _selectedNodeVariables; + private ObservableCollection _selectedNodeVariables; public List SelectedVariables { get; set; } = new List(); @@ -39,15 +42,15 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase(); - SelectedNodeVariables = new ObservableCollection(); + SelectedNodeVariables = new ObservableCollection(); this._opcUaService = opcUaService; - RootOpcUaNode = new OpcUaNode() { DisplayName = "根节点", NodeId = Objects.ObjectsFolder }; + this._mapper = mapper; + RootOpcUaNode = new OpcUaNodeItemViewModel() { DisplayName = "根节点", NodeId = Objects.ObjectsFolder, IsExpanded = true }; _cancellationTokenSource = new CancellationTokenSource(); } @@ -65,6 +68,7 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase(RootOpcUaNode)); + RootOpcUaNode.Children = _mapper.Map>(childrens); } @@ -85,6 +90,18 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase /// 处理来自服务器的数据变化通知 /// @@ -97,176 +114,69 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase nodes, NodeId parentNodeId) - //{ - // try - // { - // Opc.Ua.ReferenceDescriptionCollection references; - // byte[] continuationPoint = null; - // _session.Browse( - // null, // RequestHeader - // new ViewDescription(), - // parentNodeId, - // 0u, - // BrowseDirection.Forward, - // Opc.Ua.ReferenceTypeIds.HierarchicalReferences, - // true, - // (uint)Opc.Ua.NodeClass.Object | (uint)Opc.Ua.NodeClass.Variable, - // out continuationPoint, - // out references - // ); - // foreach (var rd in references) - // { - // NodeType nodeType = NodeType.Folder; // 默认是文件夹 - // if ((rd.NodeClass & NodeClass.Variable) != 0) - // { - // nodeType = NodeType.Variable; - // } - // else if ((rd.NodeClass & NodeClass.Object) != 0) - // { - // nodeType = NodeType.Object; - // } - - // var opcUaNode = new OpcUaNode(rd.DisplayName.Text, (NodeId)rd.NodeId, nodeType); - // nodes.Add(opcUaNode); - - // // 如果是文件夹或对象,添加一个虚拟子节点,用于懒加载 - // if (nodeType == NodeType.Folder || nodeType == NodeType.Object) - // { - // opcUaNode.Children.Add(new OpcUaNode("Loading...", NodeId.Null, NodeType.Folder)); // 虚拟节点 - // } - // } - // } - // catch (Exception ex) - // { - // NlogHelper.Error($"浏览 OPC UA 节点失败: {parentNodeId} - {ex.Message}", ex); - // NotificationHelper.ShowError($"浏览 OPC UA 节点失败: {parentNodeId} - {ex.Message}", ex); - // } - //} - - //public async Task LoadNodeVariables(OpcUaNode node) - //{ - //if (node.NodeType == NodeType.Variable) - //{ - // // 如果是变量节点,直接显示它 - // SelectedNodeVariables.Clear(); - // SelectedNodeVariables.Add(new Variable - // { - // Name = node.DisplayName, - // NodeId = node.NodeId.ToString(), - // OpcUaNodeId = node.NodeId.ToString(), - // ProtocolType = ProtocolType.OpcUA, - // IsActive = true // 默认选中 - // }); - // return; - //} - - //if (node.IsLoaded || node.IsLoading) - //{ - // return; // 已经加载或正在加载 - //} - - //node.IsLoading = true; - //node.Children.Clear(); // 清除虚拟节点 - - //try - //{ - // Opc.Ua.ReferenceDescriptionCollection references; - // byte[] continuationPoint = null; - - // _session.Browse( - // null, // RequestHeader - // new ViewDescription(), - // node.NodeId, - // 0u, - // BrowseDirection.Forward, - // Opc.Ua.ReferenceTypeIds.HierarchicalReferences, - // true, - // (uint)Opc.Ua.NodeClass.Object | (uint)Opc.Ua.NodeClass.Variable, - // out continuationPoint, - // out references - // ); - - // foreach (var rd in references) - // { - // NodeType nodeType = NodeType.Folder; - // if ((rd.NodeClass & NodeClass.Variable) != 0) - // { - // nodeType = NodeType.Variable; - // } - // else if ((rd.NodeClass & NodeClass.Object) != 0) - // { - // nodeType = NodeType.Object; - // } - - // var opcUaNode = new OpcUaNode(rd.DisplayName.Text, (NodeId)rd.NodeId, nodeType); - // node.Children.Add(opcUaNode); - - // if (nodeType == NodeType.Folder || nodeType == NodeType.Object) - // { - // opcUaNode.Children.Add(new OpcUaNode("Loading...", NodeId.Null, NodeType.Folder)); // 虚拟节点 - // } - - // // 如果是变量,添加到右侧列表 - // if (nodeType == NodeType.Variable) - // { - // // Read the DataType attribute - // ReadValueId readValueId = new ReadValueId - // { - // NodeId = opcUaNode.NodeId, - // AttributeId = Attributes.DataType, - // // You might need to specify IndexRange and DataEncoding if dealing with arrays or specific encodings - // }; - - // DataValueCollection results; - // DiagnosticInfoCollection diagnosticInfos; - - // _session.Read( - // null, // RequestHeader - // 0, // MaxAge - // TimestampsToReturn.Source, - // new ReadValueIdCollection { readValueId }, - // out results, - // out diagnosticInfos - // ); - - // string dataType = string.Empty; - - // if (results != null && results.Count > 0 && results[0].Value != null) - // { - // // Convert the NodeId of the DataType to a readable string - // NodeId dataTypeNodeId = (NodeId)results[0].Value; - // dataType = _session.NodeCache.GetDisplayText(dataTypeNodeId); - // } - - // SelectedNodeVariables.Add(new Variable - // { - // Name = opcUaNode.DisplayName, - // OpcUaNodeId = opcUaNode.NodeId.ToString(), - // ProtocolType = ProtocolType.OpcUA, - // IsActive = true, // Default selected - // DataType = dataType // Assign the read DataType - // }); - // } - // } - - // node.IsLoaded = true; - //} - //catch (Exception ex) - //{ - // NlogHelper.Error($"加载 OPC UA 节点变量失败: {node.NodeId} - {ex.Message}", ex); - // NotificationHelper.ShowError($"加载 OPC UA 节点变量失败: {node.NodeId} - {ex.Message}", ex); - //} - //finally - //{ - // node.IsLoading = false; - //} - //} - - public ObservableCollection GetSelectedVariables() + private bool _isLoadingNodeVariables = false; + + public async Task LoadNodeVariables(OpcUaNodeItemViewModel node) { - return new ObservableCollection(SelectedVariables); + // 防止重复加载 + if (_isLoadingNodeVariables) + return; + + _isLoadingNodeVariables = true; + + try + { + SelectedNodeVariables.Clear(); + + if (node.NodeClass == NodeClass.Variable) + { + // 如果是变量节点,直接显示它 + SelectedNodeVariables.Add(new VariableItemViewModel + { + Name = node.DisplayName, + OpcUaNodeId = node.NodeId.ToString(), + Protocol = ProtocolType.OpcUa, + IsActive = true // 默认选中 + }); + return; + } + + // 加载节点的子项 + node.IsExpanded = true; + node.IsSelected = true; + + var childrens = await _opcUaService.BrowseNode(_mapper.Map(node)); + foreach (var children in childrens) + { + var opcNodeItem = _mapper.Map(children); + if (children.NodeClass == NodeClass.Variable) + { + SelectedNodeVariables.Add(new VariableItemViewModel + { + Name = children.DisplayName, // 修正:使用子节点的显示名称 + OpcUaNodeId = children.NodeId.ToString(), + Protocol = ProtocolType.OpcUa, + IsActive = true // 默认选中 + }); + } + else + { + node.Children.Add(opcNodeItem); + } + } + } + catch (Exception ex) + { + NlogHelper.Error($"加载 OPC UA 节点变量失败: {node.NodeId} - {ex.Message}", ex); + NotificationHelper.ShowError($"加载 OPC UA 节点变量失败: {node.NodeId} - {ex.Message}", ex); + } + finally + { + _isLoadingNodeVariables = false; + } } + + } \ No newline at end of file diff --git a/DMS.WPF/ViewModels/Items/OpcUaNodeItemViewModel.cs b/DMS.WPF/ViewModels/Items/OpcUaNodeItemViewModel.cs new file mode 100644 index 0000000..7102cb0 --- /dev/null +++ b/DMS.WPF/ViewModels/Items/OpcUaNodeItemViewModel.cs @@ -0,0 +1,49 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using DMS.Infrastructure.Models; +using Opc.Ua; +using System.Collections.ObjectModel; + +namespace DMS.WPF.ViewModels.Items +{ + /// + /// OPC UA节点的视图模型。 + /// + public partial class OpcUaNodeItemViewModel : ObservableObject + { + + [ObservableProperty] + private NodeId? _nodeId; + + [ObservableProperty] + private string? _displayName; + + [ObservableProperty] + private NodeClass _nodeClass; + + [ObservableProperty] + private object? _value; + + [ObservableProperty] + private OpcUaNodeItemViewModel? _parentNode; + + [ObservableProperty] + private ObservableCollection _children = new ObservableCollection(); + + [ObservableProperty] + private bool _isExpanded; + + [ObservableProperty] + private bool _isSelected; + + /// + /// 默认构造函数(用于设计时支持)。 + /// + public OpcUaNodeItemViewModel() + { + // 设计时数据支持 + + } + + + } +} \ No newline at end of file diff --git a/DMS.WPF/ViewModels/Items/OpcUaNodeViewModel.cs b/DMS.WPF/ViewModels/Items/OpcUaNodeViewModel.cs deleted file mode 100644 index 04bb0c9..0000000 --- a/DMS.WPF/ViewModels/Items/OpcUaNodeViewModel.cs +++ /dev/null @@ -1,50 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using Opc.Ua; -using System.Collections.ObjectModel; - -namespace DMS.WPF.ViewModels.Items -{ - public partial class OpcUaNodeViewModel : ObservableObject - { - [ObservableProperty] - private string _displayName; - - [ObservableProperty] - private NodeId _nodeId; - - [ObservableProperty] - private NodeType _nodeType; - - [ObservableProperty] - private bool _isExpanded; - - [ObservableProperty] - private bool _isLoading; - - [ObservableProperty] - private bool _isLoaded; - - public ObservableCollection Children { get; set; } - - public OpcUaNodeViewModel(string displayName, NodeId nodeId, NodeType nodeType) - { - DisplayName = displayName; - NodeId = nodeId; - NodeType = nodeType; - Children = new ObservableCollection(); - - // 如果是文件夹或对象,添加一个虚拟子节点,用于懒加载 - if (nodeType == NodeType.Folder || nodeType == NodeType.Object) - { - Children.Add(new OpcUaNodeViewModel("Loading...", NodeId.Null, NodeType.Folder)); // 虚拟节点 - } - } - } - - public enum NodeType - { - Folder, - Object, - Variable - } -} \ No newline at end of file diff --git a/DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml b/DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml index 330d1f3..ed0c3be 100644 --- a/DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml +++ b/DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml @@ -8,10 +8,10 @@ xmlns:vm="clr-namespace:DMS.WPF.ViewModels.Dialogs" Title="从OPC UA服务器导入变量" d:DataContext="{d:DesignInstance vm:ImportOpcUaDialogViewModel}" - PrimaryButtonClick="ContentDialog_PrimaryButtonClick" + CloseButtonCommand="{Binding CloseButtonCommand}" + CloseButtonText="取消" + PrimaryButtonCommand="{Binding PrimaryButtonCommand}" PrimaryButtonText="导入" - SecondaryButtonClick="ContentDialog_SecondaryButtonClick" - SecondaryButtonText="取消" mc:Ignorable="d"> @@ -47,6 +47,7 @@ + + + diff --git a/DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml.cs b/DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml.cs index 320dfa5..323c8ca 100644 --- a/DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml.cs +++ b/DMS.WPF/Views/Dialogs/ImportOpcUaDialog.xaml.cs @@ -1,7 +1,13 @@ +using DMS.Helper; +using DMS.Services; +using DMS.WPF.Helper; +using DMS.WPF.ViewModels.Dialogs; +using DMS.WPF.ViewModels.Items; +using iNKORE.UI.WPF.Modern.Controls; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using DMS.WPF.ViewModels.Dialogs; -using iNKORE.UI.WPF.Modern.Controls; +using System.Windows.Threading; namespace DMS.WPF.Views.Dialogs; @@ -10,41 +16,67 @@ namespace DMS.WPF.Views.Dialogs; /// public partial class ImportOpcUaDialog : ContentDialog { + private const int ContentAreaMaxWidth = 1200; + private const int ContentAreaMaxHeight = 800; + + + public ImportOpcUaDialog() { InitializeComponent(); + this.Opened += OnOpened; } - private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) + private void OnOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) { - // 在这里处理导入逻辑,例如获取选中的变量 - // ViewModel.ImportSelectedVariables(); - } - - private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) - { - // 处理取消逻辑 + //修改对话框内容的最大宽度和最大高度 + var backgroundElementBorder = VisualTreeFinder.FindVisualChildByName(this, "BackgroundElement"); + backgroundElementBorder.MaxWidth = ContentAreaMaxWidth; + backgroundElementBorder.MaxWidth = ContentAreaMaxHeight; } private async void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) { - // if (e.NewValue is OpcUaNode selectedNode) - // { - // await ViewModel.LoadNodeVariables(selectedNode); - // } + try + { + //防止多次调用 + object selectedObj = this.treeView.SelectedItem; + if (SelectedItemChanged != null) + { + Dispatcher.BeginInvoke(DispatcherPriority.Background, SelectedItemChanged, selectedObj); + } + } + catch (Exception ex) + { + NotificationHelper.ShowError($"选择节点时发生了错误:{ex.Message}"); + } + + } + //事件 + public async void SelectedItemChanged(object selectedObj) + { + if (selectedObj is OpcUaNodeItemViewModel selectedNode) + { + + if (this.DataContext is ImportOpcUaDialogViewModel viewModel) + { + await viewModel.LoadNodeVariables(selectedNode); + } + } + } private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs args) { - // if (args.AddedItems!=null && args.AddedItems.Count>0) - // { - // foreach (var item in args.AddedItems) - // { - // ViewModel.SelectedVariables.Add((Variable)item); - // } - // - // } + // if (args.AddedItems!=null && args.AddedItems.Count>0) + // { + // foreach (var item in args.AddedItems) + // { + // ViewModel.SelectedVariables.Add((Variable)item); + // } + // + // } } }