This commit is contained in:
2026-03-13 01:22:28 +01:00
parent 695e4560b5
commit 86641919f8
27 changed files with 755 additions and 344 deletions

View File

@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<ApplicationIcon>Resource\arac_master.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="Resource\arac_master.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LeagueAPI\LeagueAPI.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

9
ARAMUtility/App.xaml Normal file
View File

@@ -0,0 +1,9 @@
<Application x:Class="ARAMUtility.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ARAMUtility"
StartupUri="View/MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

12
ARAMUtility/App.xaml.cs Normal file
View File

@@ -0,0 +1,12 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace ARAMUtility;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}

View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

73
ARAMUtility/Resources.Designer.cs generated Normal file
View File

@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ARAMUtility {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ARAMUtility.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon AracMasterIcon {
get {
object obj = ResourceManager.GetObject("AracMasterIcon", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
}
}

124
ARAMUtility/Resources.resx Normal file
View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="AracMasterIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resource\arac_master.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

View File

@@ -0,0 +1,99 @@
<Window x:Class="ARAMUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:ARAMUtility.ViewModel"
mc:Ignorable="d"
Title="{Binding Title}"
Height="480"
Width="700"
Background="#0a1a2a"
>
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="5" />
<Setter Property="Padding" Value="10,5,10,5" />
</Style>
<DataTemplate x:Key="ChampionItemTemplate" DataType="vm:ChampionViewModel">
<Border x:Name="OuterBorder"
BorderBrush="Black"
BorderThickness="3"
Margin="5"
Width="256"
Height="256">
<Grid ToolTip="{Binding Name}">
<Image Source="{Binding ImagePath}"
Stretch="UniformToFill" />
<TextBlock Text="{Binding AramBalanceText}"
Foreground="White"
Background="#99000000"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Padding="15,10"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
FontSize="24"
FontWeight="Bold"
Height="256"
Width="256" />
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsNeededForChallenge}" Value="True">
<Setter TargetName="OuterBorder"
Property="BorderBrush"
Value="Green" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top">
<MenuItem Header="Actions">
<MenuItem Header="Open Mayhem Lobby" Command="{Binding OpenLobbyCommand}" />
<MenuItem Header="Start Queueing" Command="{Binding StartQueueingCommand}" />
<MenuItem Header="Reconnect" Command="{Binding ConnectCommand}" />
<Separator />
<MenuItem Header="Reload ARAM Balance" Command="{Binding ReloadARAMBalanceCommand}" />
<Separator />
<MenuItem Header="Quit" Command="{Binding QuitCommand}" />
</MenuItem>
<MenuItem Header="{Binding ConnectionStatus}" IsEnabled="False" />
</Menu>
<Viewbox Stretch="Uniform" StretchDirection="DownOnly">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<ItemsControl x:Name="TeamChampionGrid"
Grid.Row="0"
Margin="5"
ItemsSource="{Binding TeamChampions}"
ItemTemplate="{StaticResource ChampionItemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl x:Name="BenchChampionGrid"
Grid.Row="1"
Margin="5"
ItemsSource="{Binding BenchChampions}"
ItemTemplate="{StaticResource ChampionItemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" Rows="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Viewbox>
</DockPanel>
</Window>

View File

@@ -0,0 +1,20 @@
using System.Windows;
using ARAMUtility.ViewModel;
namespace ARAMUtility;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
if (DataContext is MainViewModel viewModel)
{
Loaded += viewModel.OnInit;
Closing += (sender, e) => { viewModel?.Dispose(); };
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Text;
using CommunityToolkit.Mvvm.ComponentModel;
using LeagueAPI.Models.DDragon.Champions;
namespace ARAMUtility.ViewModel;
public partial class ChampionViewModel(ChampionData data) : ObservableObject
{
[ObservableProperty]
public partial int Id { get; set; } = data.Id;
[ObservableProperty]
public partial string? Name { get; set; } = data.Name;
[ObservableProperty]
public required partial string ImagePath { get; set; }
public string AramBalanceText { get; } = GetAramBalanceText(data);
[ObservableProperty]
public required partial bool IsNeededForChallenge { get; set; }
private static string GetAramBalanceText(ChampionData data)
{
StringBuilder sb = new();
sb.AppendLine(data.Name);
foreach (KeyValuePair<string, double> kv in data.AramBalance)
{
switch (kv.Key)
{
case "dmg_dealt":
if (kv.Value == 1) { continue; }
sb.AppendFormat("Dmg Dealt: {0:+#0%;-#0%}", kv.Value - 1);
break;
case "dmg_taken":
if (kv.Value == 1) { continue; }
sb.AppendFormat("Dmg Taken: {0:+#0%;-#0%}", kv.Value - 1);
break;
case "healing":
if (kv.Value == 1) { continue; }
sb.AppendFormat("Healing: {0:+#0%;-#0%}", kv.Value - 1);
break;
case "energyregen_mod":
if (kv.Value == 1) { continue; }
sb.AppendFormat("Energy Regen: {0:+#0%;-#0%}", kv.Value - 1);
break;
case "tenacity":
if (kv.Value == 1) { continue; }
sb.AppendFormat("Tenacity: {0:+#0%;-#0%}", kv.Value - 1);
break;
case "shielding":
if (kv.Value == 1) { continue; }
sb.AppendFormat("Shielding: {0:+#0%;-#0%}", kv.Value - 1);
break;
case "ability_haste":
sb.AppendFormat("Ability Haste: {0}", kv.Value);
break;
case "total_as":
if (kv.Value == 1) { continue; }
sb.AppendFormat("Total AS: {0}", kv.Value);
break;
default:
sb.AppendFormat("{0}: {1}", kv.Key, kv.Value);
break;
}
sb.AppendLine();
}
return sb.ToString();
}
}

View File

@@ -0,0 +1,239 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Text.Json;
using System.Windows;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LeagueAPI;
using LeagueAPI.ARAM;
using LeagueAPI.Models.ChampSelect;
using LeagueAPI.Models.DDragon.Champions;
using LeagueAPI.Models.ReadyCheck;
namespace ARAMUtility.ViewModel;
public partial class MainViewModel : ObservableObject, IDisposable
{
private const int TEAM_CHAMPIONS_MAX = 5;
private const int BENCH_CHAMPIONS_MAX = 10;
private bool _isDisposed;
private readonly Lock _syncRoot = new();
private readonly SemaphoreSlim _championUpdateSemaphore = new(1, 1);
private Dispatcher Dispatcher { get; } = Dispatcher.CurrentDispatcher;
[ObservableProperty]
public partial string Title { get; set; } = "ARAM Utility";
[ObservableProperty]
public partial ObservableCollection<ChampionViewModel> TeamChampions { get; private set; } = [];
[ObservableProperty]
public partial ObservableCollection<ChampionViewModel> BenchChampions { get; private set; } = [];
[ObservableProperty]
public partial bool IsDisconnected { get; private set; } = false;
[ObservableProperty]
public partial string ConnectionStatus { get; private set; } = "Not connected.";
private Dictionary<int, ChampionData> _allChampions = [];
private List<int> _needChampionIds = [];
private readonly APIClient _client = new();
private readonly LcuWebsocket _lcuWebsocket = new();
private Task _lcuWebsocketTask;
private readonly ARAMBalanceService _aramBalanceService = new();
public MainViewModel()
{
_lcuWebsocket.Connecting += (_, _) => UpdateConnectionStatus(false, "Connecting ...");
_lcuWebsocket.Connected += (_, _) => UpdateConnectionStatus(false, "Connected.");
_lcuWebsocket.Disconnected += (_, _) => UpdateConnectionStatus(true, "Disconnected.");
_lcuWebsocket.LcuApiEvent += OnLcuApiEvent;
#if DEBUG
_lcuWebsocket.LcuApiException += (sender, e) =>
{
File.AppendAllText("socketerror.log", $"[{DateTime.Now:s}] {e.Message}\n{e.StackTrace}\n\n");
};
#endif
_lcuWebsocketTask = _lcuWebsocket.Connect();
}
internal async void OnInit(object? sender, RoutedEventArgs e)
{
ChampionData[] champions = await _client.GetAllChampionsAsync();
Dictionary<int, ChampionData> championDictionary = champions
.Where(c => c.Id != -1)
.ToDictionary(key => key.Id);
lock (_syncRoot)
{
_allChampions = championDictionary;
}
await UpdateNeedChampionIdsAsync();
await FillChampionLists();
}
private void UpdateConnectionStatus(bool isConnected, string statusMessage)
{
Dispatcher.Invoke(() =>
{
IsDisconnected = isConnected;
ConnectionStatus = statusMessage;
});
}
private async void OnLcuApiEvent(object? sender, LcuApiEvent apiEvent)
{
switch (apiEvent.Uri)
{
case "/lol-matchmaking/v1/ready-check":
LolMatchmakingMatchmakingReadyCheckResource? readyCheck = apiEvent.Data.Deserialize<LolMatchmakingMatchmakingReadyCheckResource>();
if (readyCheck is not null && readyCheck.PlayerResponse == LolMatchmakingMatchmakingReadyCheckResponse.Accepted)
{
await UpdateNeedChampionIdsAsync();
}
break;
case "/lol-champ-select/v1/session":
ChampSelectSession? session = apiEvent.Data.Deserialize<ChampSelectSession>();
await ShowChampionsAsync(session);
break;
default:
File.AppendAllText("socket.log", $"{apiEvent.Uri}: {apiEvent.EventType} - {apiEvent.Data?.ToJsonString()}\n");
break;
}
}
private async Task UpdateNeedChampionIdsAsync()
{
IEnumerable<int> completedChampionIds = await _client.GetAllRandomAllChampionsCompletedChampionsAsync();
IEnumerable<int> needChampionIds = _allChampions.Keys.Except(completedChampionIds);
lock (_syncRoot)
{
_needChampionIds = [.. needChampionIds];
}
}
private async Task FillChampionLists()
{
string defaultImagePath = await ResourceService.GetChampionIconPathAsync(-1);
while (TeamChampions.Count < TEAM_CHAMPIONS_MAX)
{
TeamChampions.Add(GetEmptyChampionViewModel());
}
while (BenchChampions.Count < BENCH_CHAMPIONS_MAX)
{
BenchChampions.Add(GetEmptyChampionViewModel());
}
ChampionViewModel GetEmptyChampionViewModel()
{
return new ChampionViewModel(new ChampionData()) { ImagePath = defaultImagePath, IsNeededForChallenge = false };
}
}
private async Task ShowChampionsAsync(ChampSelectSession? session)
{
await _championUpdateSemaphore.WaitAsync();
(IEnumerable<int> teamChampions, IEnumerable<int> benchChampions) = APIClient.GetSelectableChampionIds(session);
if (!teamChampions.Any() && !benchChampions.Any())
{
TeamChampions.Clear();
BenchChampions.Clear();
}
else
{
await UpdateChampions(TeamChampions, teamChampions);
await UpdateChampions(BenchChampions, benchChampions);
}
await FillChampionLists();
_championUpdateSemaphore.Release();
async Task UpdateChampions(ObservableCollection<ChampionViewModel> viewModel, IEnumerable<int> championIds)
{
viewModel.Clear();
await _aramBalanceService.EnsureIsLoadedAsync();
foreach (int championId in championIds)
{
ChampionData? championData = await _client.GetChampionByIdAsync(championId);
if (championData is null)
{
continue;
}
string imagePath = await ResourceService.GetChampionIconPathAsync(championId);
ChampionViewModel vm = new(championData with { AramBalance = _aramBalanceService.GetAramStats(championData.Id) })
{
IsNeededForChallenge = _needChampionIds.Contains(championData.Id),
ImagePath = imagePath,
};
viewModel.Add(vm);
}
}
}
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(IsDisconnected))]
private async Task Connect()
{
if (_lcuWebsocketTask is null || _lcuWebsocketTask.IsCompleted)
{
_lcuWebsocketTask?.Dispose();
_lcuWebsocketTask = _lcuWebsocket.Connect();
}
}
[RelayCommand(AllowConcurrentExecutions = false)]
private async Task OpenLobby()
{
await _client.CreateMayhemLobbyAsync();
}
[RelayCommand(AllowConcurrentExecutions = false)]
private async Task StartQueueing()
{
await _client.StartMatchmakingQueueAsync();
}
[RelayCommand(AllowConcurrentExecutions = false)]
private async Task ReloadARAMBalance()
{
await _aramBalanceService.ReloadAsync(force: true);
MessageBox.Show("Reloaded ARAM balance data.", "Info", MessageBoxButton.OK, MessageBoxImage.Exclamation);
}
[RelayCommand]
private async Task Quit()
{
Application.Current.Shutdown();
}
#region IDisposable
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
_allChampions = [];
_client.Dispose();
_lcuWebsocket.Dispose();
}
_isDisposed = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}