完成连接OpcUa服务器
This commit is contained in:
@@ -9,25 +9,15 @@ namespace DMS.Infrastructure.Interfaces.Services
|
|||||||
{
|
{
|
||||||
public interface IOpcUaService
|
public interface IOpcUaService
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 创建 OPC UA 会话
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcUaServerUrl">OPC UA 服务器地址</param>
|
|
||||||
/// <param name="stoppingToken">取消令牌</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task CreateSession(string opcUaServerUrl, CancellationToken stoppingToken = default);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 连接到 OPC UA 服务器(异步)
|
/// 连接到 OPC UA 服务器(异步)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="stoppingToken">取消令牌</param>
|
/// <param name="stoppingToken">取消令牌</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task ConnectAsync(CancellationToken stoppingToken = default);
|
public Task ConnectAsync(string opcUaServerUrl,CancellationToken stoppingToken = default);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 连接到 OPC UA 服务器(同步)
|
|
||||||
/// </summary>
|
|
||||||
public void Connect();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 断开 OPC UA 服务器连接
|
/// 断开 OPC UA 服务器连接
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace DMS.Infrastructure.Services
|
|||||||
public class OpcUaService : IOpcUaService
|
public class OpcUaService : IOpcUaService
|
||||||
{
|
{
|
||||||
private Session? _session;
|
private Session? _session;
|
||||||
private string? _serverUrl;
|
private string _serverUrl;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建 OPC UA 会话
|
/// 创建 OPC UA 会话
|
||||||
@@ -22,17 +22,14 @@ namespace DMS.Infrastructure.Services
|
|||||||
/// <param name="opcUaServerUrl">OPC UA 服务器地址</param>
|
/// <param name="opcUaServerUrl">OPC UA 服务器地址</param>
|
||||||
/// <param name="stoppingToken">取消令牌</param>
|
/// <param name="stoppingToken">取消令牌</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task CreateSession(string opcUaServerUrl, CancellationToken stoppingToken = default)
|
public async Task CreateSession( CancellationToken stoppingToken = default)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(opcUaServerUrl))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("OPC UA server URL cannot be null or empty.", nameof(opcUaServerUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_session = await OpcUaHelper.CreateOpcUaSessionAsync(opcUaServerUrl, stoppingToken);
|
_session = await OpcUaHelper.CreateOpcUaSessionAsync(_serverUrl, stoppingToken);
|
||||||
_serverUrl = opcUaServerUrl;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -43,8 +40,13 @@ namespace DMS.Infrastructure.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 连接到 OPC UA 服务器
|
/// 连接到 OPC UA 服务器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task ConnectAsync(CancellationToken stoppingToken = default)
|
public async Task ConnectAsync(string opcUaServerUrl, CancellationToken stoppingToken = default)
|
||||||
{
|
{
|
||||||
|
_serverUrl = opcUaServerUrl;
|
||||||
|
if (string.IsNullOrEmpty(opcUaServerUrl))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("OPC UA server URL cannot be null or empty.", nameof(opcUaServerUrl));
|
||||||
|
}
|
||||||
if (string.IsNullOrEmpty(_serverUrl))
|
if (string.IsNullOrEmpty(_serverUrl))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Server URL is not set. Please call CreateSession first.");
|
throw new InvalidOperationException("Server URL is not set. Please call CreateSession first.");
|
||||||
@@ -57,43 +59,10 @@ namespace DMS.Infrastructure.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重新创建会话
|
// 重新创建会话
|
||||||
await CreateSession(_serverUrl, stoppingToken);
|
await CreateSession( stoppingToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 连接到 OPC UA 服务器(同步版本,用于向后兼容)
|
|
||||||
/// </summary>
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(_serverUrl))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Server URL is not set. Please call CreateSession first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果已经连接,直接返回
|
|
||||||
if (_session?.Connected == true)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查会话是否存在但未连接
|
|
||||||
if (_session != null)
|
|
||||||
{
|
|
||||||
// 尝试重新激活会话
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_session.Reconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 如果重新连接失败,继续到重新创建会话的步骤
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有会话或重新连接失败,抛出异常提示需要调用 CreateSession
|
|
||||||
throw new InvalidOperationException("Session is not created or connection lost. Please call CreateSession first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 断开 OPC UA 服务器连接
|
/// 断开 OPC UA 服务器连接
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace DMS.WPF.Services
|
|||||||
{ typeof(ConfirmDialogViewModel), typeof(ConfirmDialog) },
|
{ typeof(ConfirmDialogViewModel), typeof(ConfirmDialog) },
|
||||||
{ typeof(VariableTableDialogViewModel), typeof(VariableTableDialog) },
|
{ typeof(VariableTableDialogViewModel), typeof(VariableTableDialog) },
|
||||||
{ typeof(ImportExcelDialogViewModel), typeof(ImportExcelDialog) },
|
{ typeof(ImportExcelDialogViewModel), typeof(ImportExcelDialog) },
|
||||||
|
{ typeof(ImportOpcUaDialogViewModel), typeof(ImportOpcUaDialog) },
|
||||||
{ typeof(VariableDialogViewModel), typeof(VariableDialog) },
|
{ typeof(VariableDialogViewModel), typeof(VariableDialog) },
|
||||||
// { typeof(MqttDialogViewModel), typeof(MqttDialog) }, // Add other mappings here
|
// { typeof(MqttDialogViewModel), typeof(MqttDialog) }, // Add other mappings here
|
||||||
// ... other dialogs
|
// ... other dialogs
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using DMS.Helper;
|
|
||||||
using DMS.Message;
|
|
||||||
using DMS.WPF.ViewModels;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using DMS.Core.Enums;
|
|
||||||
|
|
||||||
namespace DMS.Services;
|
|
||||||
|
|
||||||
public partial class NavgatorServices : ObservableRecipient, IRecipient<NavgatorMessage>
|
|
||||||
{
|
|
||||||
// [ObservableProperty]
|
|
||||||
private ViewModelBase currentViewModel;
|
|
||||||
|
|
||||||
public NavgatorServices()
|
|
||||||
{
|
|
||||||
IsActive = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// partial void OnCurrentViewModelChanging(ViewModelBase viewModel)
|
|
||||||
// {
|
|
||||||
// viewModel?.OnLoading();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// partial void OnCurrentViewModelChanged(ViewModelBase viewModel)
|
|
||||||
// {
|
|
||||||
// OnViewModelChanged?.Invoke();
|
|
||||||
// viewModel?.OnLoaded();
|
|
||||||
// }
|
|
||||||
|
|
||||||
public ViewModelBase CurrentViewModel
|
|
||||||
{
|
|
||||||
get => currentViewModel;
|
|
||||||
set { currentViewModel = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async void Receive(NavgatorMessage message)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ViewModelBase nextViewModel = message.Value;
|
|
||||||
//如果OnExit返回False终止跳转
|
|
||||||
|
|
||||||
if (currentViewModel != null)
|
|
||||||
{
|
|
||||||
var isExit = await currentViewModel.OnExitAsync();
|
|
||||||
if (!isExit)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextViewModel?.OnLoading();
|
|
||||||
CurrentViewModel = message.Value;
|
|
||||||
OnViewModelChanged?.Invoke();
|
|
||||||
currentViewModel?.OnLoaded();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
NotificationHelper.ShowError($"切换视图时发生了错误:{e.Message}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action OnViewModelChanged;
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
// 文件: DMS.WPF/Services/NavigationService.cs
|
// 文件: DMS.WPF/Services/NavigationService.cs
|
||||||
|
|
||||||
|
using DMS.Helper;
|
||||||
|
using DMS.ViewModels;
|
||||||
using DMS.WPF.ViewModels;
|
using DMS.WPF.ViewModels;
|
||||||
|
using DMS.WPF.ViewModels.Items;
|
||||||
|
using DMS.WPF.Views;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using DMS.ViewModels;
|
|
||||||
using DMS.WPF.ViewModels.Items;
|
|
||||||
using DMS.WPF.Views;
|
|
||||||
|
|
||||||
namespace DMS.WPF.Services;
|
namespace DMS.WPF.Services;
|
||||||
|
|
||||||
@@ -39,6 +40,13 @@ public class NavigationService : INavigationService
|
|||||||
|
|
||||||
var mainViewModel = App.Current.Services.GetRequiredService<MainViewModel>();
|
var mainViewModel = App.Current.Services.GetRequiredService<MainViewModel>();
|
||||||
var viewModel = GetViewModelByKey(menu.TargetViewKey);
|
var viewModel = GetViewModelByKey(menu.TargetViewKey);
|
||||||
|
if (viewModel == null)
|
||||||
|
{
|
||||||
|
|
||||||
|
NotificationHelper.ShowError($"切换界面失败,没有找到界面:{menu.TargetViewKey}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (viewModel is INavigatable navigatableViewModel)
|
if (viewModel is INavigatable navigatableViewModel)
|
||||||
{
|
{
|
||||||
@@ -70,7 +78,7 @@ public class NavigationService : INavigationService
|
|||||||
case "SettingView":
|
case "SettingView":
|
||||||
return App.Current.Services.GetRequiredService<SettingViewModel>();
|
return App.Current.Services.GetRequiredService<SettingViewModel>();
|
||||||
default:
|
default:
|
||||||
throw new KeyNotFoundException($"未找到与键 '{key}' 关联的视图模型类型。请检查 NavigationService 的映射配置。");
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,8 +106,8 @@ public partial class DevicesViewModel : ViewModelBase, INavigatable
|
|||||||
{
|
{
|
||||||
Header = device.Name,
|
Header = device.Name,
|
||||||
Icon = SegoeFluentIcons.Devices2.Glyph,
|
Icon = SegoeFluentIcons.Devices2.Glyph,
|
||||||
TargetViewKey = "DevicesView"
|
TargetViewKey = "DeviceDetailView"
|
||||||
};
|
};
|
||||||
if (device.IsAddDefVarTable)
|
if (device.IsAddDefVarTable)
|
||||||
{
|
{
|
||||||
dto.VariableTable = new VariableTableDto()
|
dto.VariableTable = new VariableTableDto()
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using DMS.Core.Models;
|
using DMS.Core.Models;
|
||||||
using DMS.Helper;
|
using DMS.Helper;
|
||||||
|
using DMS.Infrastructure.Interfaces.Services;
|
||||||
using DMS.WPF.ViewModels.Items;
|
using DMS.WPF.ViewModels.Items;
|
||||||
|
using Opc.Ua;
|
||||||
using Opc.Ua.Client;
|
using Opc.Ua.Client;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
@@ -27,14 +29,25 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<Varia
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isConnected;
|
private bool _isConnected;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _connectButtonText="连接服务器";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isConnectButtonEnabled = true;
|
||||||
|
|
||||||
private Session _session;
|
private Session _session;
|
||||||
|
|
||||||
public ImportOpcUaDialogViewModel()
|
private readonly IOpcUaService _opcUaService;
|
||||||
|
|
||||||
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
|
public ImportOpcUaDialogViewModel(IOpcUaService opcUaService)
|
||||||
{
|
{
|
||||||
//OpcUaNodes = new ObservableCollection<OpcUaNode>();
|
//OpcUaNodes = new ObservableCollection<OpcUaNode>();
|
||||||
SelectedNodeVariables = new ObservableCollection<Variable>();
|
SelectedNodeVariables = new ObservableCollection<Variable>();
|
||||||
// Automatically connect when the ViewModel is created
|
this._opcUaService = opcUaService;
|
||||||
//ConnectC.Execute(null);
|
|
||||||
|
_cancellationTokenSource=new CancellationTokenSource();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,27 +57,29 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<Varia
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 断开现有连接
|
// 断开现有连接
|
||||||
if (_session != null && _session.Connected)
|
if (!_opcUaService.IsConnected())
|
||||||
{
|
{
|
||||||
await _session.CloseAsync();
|
await _opcUaService.ConnectAsync(EndpointUrl, _cancellationTokenSource.Token);
|
||||||
_session.Dispose();
|
|
||||||
_session = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IsConnected = false;
|
IsConnected= _opcUaService.IsConnected();
|
||||||
SelectedNodeVariables.Clear();
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
ConnectButtonText = "已连接";
|
||||||
|
IsConnectButtonEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
//_session = await ServiceHelper.CreateOpcUaSessionAsync(EndpointUrl);
|
|
||||||
|
|
||||||
NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {EndpointUrl}");
|
|
||||||
IsConnected = true;
|
|
||||||
|
|
||||||
// 浏览根节点
|
// 浏览根节点
|
||||||
|
var rootNodeId = new NodeId(ObjectIds.ObjectsFolder);
|
||||||
|
var list=_opcUaService.BrowseNodes(rootNodeId);
|
||||||
//await BrowseNodes(OpcUaNodes, ObjectIds.ObjectsFolder);
|
//await BrowseNodes(OpcUaNodes, ObjectIds.ObjectsFolder);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
|
IsConnectButtonEnabled = false;
|
||||||
|
ConnectButtonText = "连接服务器";
|
||||||
NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex);
|
NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ public partial class MqttsViewModel : ViewModelBase
|
|||||||
private readonly DataServices _dataServices;
|
private readonly DataServices _dataServices;
|
||||||
private readonly IDialogService _dialogService;
|
private readonly IDialogService _dialogService;
|
||||||
private readonly ILogger<MqttsViewModel> _logger;
|
private readonly ILogger<MqttsViewModel> _logger;
|
||||||
private readonly NavgatorServices _navgatorServices;
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<MqttServerItemViewModel> _mqtts;
|
private ObservableCollection<MqttServerItemViewModel> _mqtts;
|
||||||
|
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 显示OPC UA导入对话框,让用户选择要导入的变量
|
// 显示OPC UA导入对话框,让用户选择要导入的变量
|
||||||
ImportOpcUaDialogViewModel importOpcUaDialogViewModel = new ImportOpcUaDialogViewModel();
|
ImportOpcUaDialogViewModel importOpcUaDialogViewModel = App.Current.Services.GetRequiredService<ImportOpcUaDialogViewModel>() ;
|
||||||
var importedVariables = await _dialogService.ShowDialogAsync(importOpcUaDialogViewModel);
|
var importedVariables = await _dialogService.ShowDialogAsync(importOpcUaDialogViewModel);
|
||||||
if (importedVariables == null || !importedVariables.Any())
|
if (importedVariables == null || !importedVariables.Any())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -22,9 +23,31 @@
|
|||||||
<ColumnDefinition Width="2*" />
|
<ColumnDefinition Width="2*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 连接区域 -->
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<TextBox
|
||||||
|
x:Name="EndpointUrlTextBox"
|
||||||
|
Width="300"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsEnabled="False"
|
||||||
|
Text="{Binding EndpointUrl, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Button
|
||||||
|
x:Name="ConnectButton"
|
||||||
|
Command="{Binding ConnectCommand}"
|
||||||
|
Content="{Binding ConnectButtonText}"
|
||||||
|
IsEnabled="{Binding IsConnectButtonEnabled}"
|
||||||
|
Style="{StaticResource AccentButtonStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 节点树 -->
|
<!-- 节点树 -->
|
||||||
<TreeView
|
<TreeView
|
||||||
Grid.Row="0"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="0,0,10,0"
|
Margin="0,0,10,0"
|
||||||
ItemsSource="{Binding OpcUaNodes}"
|
ItemsSource="{Binding OpcUaNodes}"
|
||||||
@@ -48,7 +71,7 @@
|
|||||||
|
|
||||||
<!-- 变量列表 -->
|
<!-- 变量列表 -->
|
||||||
<DataGrid
|
<DataGrid
|
||||||
Grid.Row="0"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
|
|||||||
@@ -10,16 +10,10 @@ namespace DMS.WPF.Views.Dialogs;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ImportOpcUaDialog : ContentDialog
|
public partial class ImportOpcUaDialog : ContentDialog
|
||||||
{
|
{
|
||||||
public ImportOpcUaDialogViewModel ViewModel
|
|
||||||
{
|
|
||||||
get => (ImportOpcUaDialogViewModel)DataContext;
|
|
||||||
set => DataContext = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImportOpcUaDialog(ImportOpcUaDialogViewModel viewModel)
|
public ImportOpcUaDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
ViewModel = viewModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
|
|||||||
Reference in New Issue
Block a user