解决TreeView的TreeView_SelectedItemChanged事件被多次调用的问题
This commit is contained in:
@@ -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<Variable, VariableItemViewModel>()
|
||||
.ReverseMap();
|
||||
CreateMap<OpcUaNode, OpcUaNodeItemViewModel>()
|
||||
.ReverseMap();
|
||||
CreateMap<VariableItemViewModel, VariableItemViewModel>();
|
||||
|
||||
|
||||
|
||||
@@ -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<List<Varia
|
||||
private string _endpointUrl = "opc.tcp://127.0.0.1:4855"; // 默认值
|
||||
|
||||
[ObservableProperty]
|
||||
private OpcUaNode _rootOpcUaNode;
|
||||
private OpcUaNodeItemViewModel _rootOpcUaNode;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Variable> _selectedNodeVariables;
|
||||
private ObservableCollection<VariableItemViewModel> _selectedNodeVariables;
|
||||
|
||||
public List<Variable> SelectedVariables { get; set; } = new List<Variable>();
|
||||
|
||||
@@ -39,15 +42,15 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<Varia
|
||||
private Session _session;
|
||||
|
||||
private readonly IOpcUaService _opcUaService;
|
||||
|
||||
private readonly IMapper _mapper;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public ImportOpcUaDialogViewModel(IOpcUaService opcUaService)
|
||||
public ImportOpcUaDialogViewModel(IOpcUaService opcUaService,IMapper mapper)
|
||||
{
|
||||
//OpcUaNodes = new ObservableCollection<OpcUaNode>();
|
||||
SelectedNodeVariables = new ObservableCollection<Variable>();
|
||||
SelectedNodeVariables = new ObservableCollection<VariableItemViewModel>();
|
||||
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<List<Varia
|
||||
|
||||
if (_opcUaService.IsConnected)
|
||||
{
|
||||
IsConnected=true;
|
||||
ConnectButtonText = "已连接";
|
||||
IsConnectButtonEnabled = false;
|
||||
}
|
||||
@@ -72,7 +76,8 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<Varia
|
||||
|
||||
// 浏览根节点
|
||||
|
||||
await _opcUaService.BrowseNode(RootOpcUaNode);
|
||||
var childrens= await _opcUaService.BrowseNode(_mapper.Map<OpcUaNode>(RootOpcUaNode));
|
||||
RootOpcUaNode.Children = _mapper.Map<ObservableCollection<OpcUaNodeItemViewModel>>(childrens);
|
||||
|
||||
|
||||
}
|
||||
@@ -85,6 +90,18 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<Varia
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async void PrimaryButton()
|
||||
{
|
||||
await _opcUaService.DisconnectAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async void CloseButton()
|
||||
{
|
||||
await _opcUaService.DisconnectAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理来自服务器的数据变化通知
|
||||
/// </summary>
|
||||
@@ -97,176 +114,69 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<Varia
|
||||
}
|
||||
}
|
||||
|
||||
//private async Task BrowseNodes(ObservableCollection<OpcUaNode> 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;
|
||||
// }
|
||||
private bool _isLoadingNodeVariables = false;
|
||||
|
||||
// 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<Variable> GetSelectedVariables()
|
||||
public async Task LoadNodeVariables(OpcUaNodeItemViewModel node)
|
||||
{
|
||||
return new ObservableCollection<Variable>(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<OpcUaNode>(node));
|
||||
foreach (var children in childrens)
|
||||
{
|
||||
var opcNodeItem = _mapper.Map<OpcUaNodeItemViewModel>(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
49
DMS.WPF/ViewModels/Items/OpcUaNodeItemViewModel.cs
Normal file
49
DMS.WPF/ViewModels/Items/OpcUaNodeItemViewModel.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using DMS.Infrastructure.Models;
|
||||
using Opc.Ua;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace DMS.WPF.ViewModels.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// OPC UA节点的视图模型。
|
||||
/// </summary>
|
||||
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<OpcUaNodeItemViewModel> _children = new ObservableCollection<OpcUaNodeItemViewModel>();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isExpanded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isSelected;
|
||||
|
||||
/// <summary>
|
||||
/// 默认构造函数(用于设计时支持)。
|
||||
/// </summary>
|
||||
public OpcUaNodeItemViewModel()
|
||||
{
|
||||
// 设计时数据支持
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<OpcUaNodeViewModel> Children { get; set; }
|
||||
|
||||
public OpcUaNodeViewModel(string displayName, NodeId nodeId, NodeType nodeType)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
NodeId = nodeId;
|
||||
NodeType = nodeType;
|
||||
Children = new ObservableCollection<OpcUaNodeViewModel>();
|
||||
|
||||
// 如果是文件夹或对象,添加一个虚拟子节点,用于懒加载
|
||||
if (nodeType == NodeType.Folder || nodeType == NodeType.Object)
|
||||
{
|
||||
Children.Add(new OpcUaNodeViewModel("Loading...", NodeId.Null, NodeType.Folder)); // 虚拟节点
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum NodeType
|
||||
{
|
||||
Folder,
|
||||
Object,
|
||||
Variable
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -47,6 +47,7 @@
|
||||
|
||||
<!-- 节点树 -->
|
||||
<TreeView
|
||||
Name="treeView"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,10,0"
|
||||
@@ -67,6 +68,18 @@
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
</TreeView>
|
||||
|
||||
<!-- 变量列表 -->
|
||||
|
||||
@@ -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,32 +16,58 @@ namespace DMS.WPF.Views.Dialogs;
|
||||
/// </summary>
|
||||
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<Border>(this, "BackgroundElement");
|
||||
backgroundElementBorder.MaxWidth = ContentAreaMaxWidth;
|
||||
backgroundElementBorder.MaxWidth = ContentAreaMaxHeight;
|
||||
}
|
||||
|
||||
private async void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> 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)
|
||||
|
||||
Reference in New Issue
Block a user