Merge branch 'release/0.9'

This commit is contained in:
Mark van Renswoude 2021-12-20 12:20:05 +01:00
commit f351333973
152 changed files with 9290 additions and 2824 deletions

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
[*.cs]
# IDE0011: Add braces
csharp_prefer_braces = when_multiline
csharp_style_var_for_built_in_types=true:silent
csharp_style_var_when_type_is_apparent=true:silent
csharp_style_var_elsewhere=true:silent
dotnet_diagnostic.IDE0055.severity = none
dotnet_diagnostic.IDE0130.severity = none

190
.gitignore vendored
View File

@ -1,189 +1,5 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
.VS
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Bb]in/
[Oo]bj/
# Roslyn cache directories
*.ide/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
## TODO: Comment the next line if you want to checkin your
## web deploy settings but do note that will include unencrypted
## passwords
#*.pubxml
# NuGet Packages Directory
packages/*
## TODO: If the tool you use requires repositories.config
## uncomment the next line
#!packages/repositories.config
# Enable "build/" folder in the NuGet Packages folder since
# NuGet packages use it for MSBuild targets.
# This line needs to be after the ignore of the build folder
# (and the packages folder if the line above has been uncommented)
!packages/build/
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
bin
obj

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>

View File

@ -1,97 +0,0 @@
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using Newtonsoft.Json;
using PettingZoo.Model;
using PettingZoo.View;
using PettingZoo.ViewModel;
using SimpleInjector;
namespace PettingZoo
{
public partial class App
{
public void ApplicationStartup(object sender, StartupEventArgs e)
{
// WPF defaults to US for date formatting in bindings, this fixes it
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
var container = Bootstrap();
RunApplication(container);
}
private static Container Bootstrap()
{
var container = new Container();
container.RegisterSingleton(() => new UserSettings(new AppDataSettingsSerializer("Settings.json")));
container.Register<IConnectionFactory, RabbitMQClientConnectionFactory>();
container.Register<IConnectionInfoBuilder, WindowConnectionInfoBuilder>();
container.Register<MainWindow>();
container.Register<MainViewModel>();
// Note: don't run Verify! It'll create a MainWindow which will then become
// Application.Current.MainWindow and prevent the process from shutting down.
return container;
}
private static void RunApplication(Container container)
{
var mainWindow = container.GetInstance<MainWindow>();
mainWindow.Closed += (sender, args) => container.Dispose();
mainWindow.Show();
}
private class AppDataSettingsSerializer : IUserSettingsSerializer
{
private readonly string path;
private readonly string fullPath;
public AppDataSettingsSerializer(string filename)
{
var companyName = GetProductInfo<AssemblyCompanyAttribute>().Company;
var productName = GetProductInfo<AssemblyProductAttribute>().Product;
path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
companyName, productName);
fullPath = Path.Combine(path, filename);
}
public void Read(UserSettings settings)
{
if (File.Exists(fullPath))
JsonConvert.PopulateObject(File.ReadAllText(fullPath), settings);
}
public void Write(UserSettings settings)
{
Directory.CreateDirectory(path);
File.WriteAllText(fullPath, JsonConvert.SerializeObject(settings, Formatting.Indented));
}
private T GetProductInfo<T>()
{
var attributes = GetType().Assembly.GetCustomAttributes(typeof(T), true);
if (attributes.Length == 0)
throw new Exception("Missing product information in assembly");
return (T)attributes[0];
}
}
}
}

6
GitVersion.yml Normal file
View File

@ -0,0 +1,6 @@
mode: ContinuousDeployment
branches:
master:
mode: ContinuousDelivery
ignore:
sha: []

View File

@ -1,483 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DrawingImage x:Key="ClearIcon">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup>
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0,0,58,58" />
</DrawingGroup.ClipGeometry>
<DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FFEFEBDE">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M46.5,14L46.5,14 32.5,0 1.5,0 1.5,58 46.5,58z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<DrawingGroup>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M11.5,23L36.5,23C37.052,23 37.5,22.553 37.5,22 37.5,21.447 37.052,21 36.5,21L11.5,21C10.948,21 10.5,21.447 10.5,22 10.5,22.553 10.948,23 11.5,23z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M11.5,15L21.5,15C22.052,15 22.5,14.553 22.5,14 22.5,13.447 22.052,13 21.5,13L11.5,13C10.948,13 10.5,13.447 10.5,14 10.5,14.553 10.948,15 11.5,15z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M36.5,29L11.5,29C10.948,29 10.5,29.447 10.5,30 10.5,30.553 10.948,31 11.5,31L36.5,31C37.052,31 37.5,30.553 37.5,30 37.5,29.447 37.052,29 36.5,29z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M36.5,37L11.5,37C10.948,37 10.5,37.447 10.5,38 10.5,38.553 10.948,39 11.5,39L36.5,39C37.052,39 37.5,38.553 37.5,38 37.5,37.447 37.052,37 36.5,37z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M36.5,45L11.5,45C10.948,45 10.5,45.447 10.5,46 10.5,46.553 10.948,47 11.5,47L36.5,47C37.052,47 37.5,46.553 37.5,46 37.5,45.447 37.052,45 36.5,45z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M32.5,0L32.5,0 32.5,14 46.5,14z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FFED7161">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="12" RadiusY="12" Center="44.5,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M45.914,46L49.45,42.464C49.841,42.073 49.841,41.441 49.45,41.05 49.059,40.659 48.427,40.659 48.036,41.05L44.5,44.586 40.964,41.05C40.573,40.659 39.941,40.659 39.55,41.05 39.159,41.441 39.159,42.073 39.55,42.464L43.086,46 39.55,49.536C39.159,49.927 39.159,50.559 39.55,50.95 39.745,51.145 40.001,51.243 40.257,51.243 40.513,51.243 40.769,51.145 40.964,50.95L44.5,47.414 48.036,50.95C48.231,51.145 48.487,51.243 48.743,51.243 48.999,51.243 49.255,51.145 49.45,50.95 49.841,50.559 49.841,49.927 49.45,49.536L45.914,46z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="ConnectIcon">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup>
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0,0,59,59" />
</DrawingGroup.ClipGeometry>
<DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FF556080">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,19L3.608,19C1.616,19,0,17.384,0,15.392L0,3.608C0,1.616,1.616,0,3.608,0L54.391,0C56.384,0,58,1.616,58,3.608L58,15.391C58,17.384,56.384,19,54.392,19z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF38454F">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,38L3.608,38C1.616,38,0,36.384,0,34.392L0,22.608C0,20.616,1.616,19,3.608,19L54.391,19C56.384,19,58,20.616,58,22.608L58,34.391C58,36.384,56.384,38,54.392,38z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF556080">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,57L3.608,57C1.616,57,0,55.384,0,53.392L0,41.608C0,39.616,1.616,38,3.608,38L54.391,38C56.384,38,58,39.616,58,41.608L58,53.391C58,55.384,56.384,57,54.392,57z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,9.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,28.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,47.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FF26B999">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="12" RadiusY="12" Center="47,47" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M53.571,41.179C53.116,40.863,52.494,40.975,52.179,41.429L46.583,49.469 42.634,46.227C42.208,45.876 41.577,45.939 41.227,46.366 40.876,46.793 40.938,47.423 41.366,47.773L46.152,51.702C46.332,51.849 46.556,51.929 46.786,51.929 46.831,51.929 46.877,51.926 46.923,51.92 47.199,51.881 47.447,51.73 47.607,51.501L53.821,42.572C54.136,42.118,54.024,41.495,53.571,41.179z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="DisconnectIcon">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup>
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0,0,59,59" />
</DrawingGroup.ClipGeometry>
<DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FF556080">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,19L3.608,19C1.616,19,0,17.384,0,15.392L0,3.608C0,1.616,1.616,0,3.608,0L54.391,0C56.384,0,58,1.616,58,3.608L58,15.391C58,17.384,56.384,19,54.392,19z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF38454F">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,38L3.608,38C1.616,38,0,36.384,0,34.392L0,22.608C0,20.616,1.616,19,3.608,19L54.391,19C56.384,19,58,20.616,58,22.608L58,34.391C58,36.384,56.384,38,54.392,38z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF556080">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,57L3.608,57C1.616,57,0,55.384,0,53.392L0,41.608C0,39.616,1.616,38,3.608,38L54.391,38C56.384,38,58,39.616,58,41.608L58,53.391C58,55.384,56.384,57,54.392,57z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,9.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,28.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,47.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FFED7161">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="12" RadiusY="12" Center="47,47" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M48.414,47L51.95,43.464C52.341,43.073 52.341,42.441 51.95,42.05 51.559,41.659 50.927,41.659 50.536,42.05L47,45.586 43.464,42.05C43.073,41.659 42.441,41.659 42.05,42.05 41.659,42.441 41.659,43.073 42.05,43.464L45.586,47 42.05,50.536C41.659,50.927 41.659,51.559 42.05,51.95 42.245,52.145 42.501,52.243 42.757,52.243 43.013,52.243 43.269,52.145 43.464,51.95L47,48.414 50.536,51.95C50.731,52.145 50.987,52.243 51.243,52.243 51.499,52.243 51.755,52.145 51.95,51.95 52.341,51.559 52.341,50.927 51.95,50.536L48.414,47z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</ResourceDictionary>

View File

@ -1,60 +0,0 @@
<DrawingGroup xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DrawingGroup x:Name="DrawingLayer">
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0,0,58,58" />
</DrawingGroup.ClipGeometry>
<DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FFEFEBDE">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M46.5,14L46.5,14 32.5,0 1.5,0 1.5,58 46.5,58z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<DrawingGroup>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M11.5,23L36.5,23C37.052,23 37.5,22.553 37.5,22 37.5,21.447 37.052,21 36.5,21L11.5,21C10.948,21 10.5,21.447 10.5,22 10.5,22.553 10.948,23 11.5,23z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M11.5,15L21.5,15C22.052,15 22.5,14.553 22.5,14 22.5,13.447 22.052,13 21.5,13L11.5,13C10.948,13 10.5,13.447 10.5,14 10.5,14.553 10.948,15 11.5,15z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M36.5,29L11.5,29C10.948,29 10.5,29.447 10.5,30 10.5,30.553 10.948,31 11.5,31L36.5,31C37.052,31 37.5,30.553 37.5,30 37.5,29.447 37.052,29 36.5,29z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M36.5,37L11.5,37C10.948,37 10.5,37.447 10.5,38 10.5,38.553 10.948,39 11.5,39L36.5,39C37.052,39 37.5,38.553 37.5,38 37.5,37.447 37.052,37 36.5,37z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M36.5,45L11.5,45C10.948,45 10.5,45.447 10.5,46 10.5,46.553 10.948,47 11.5,47L36.5,47C37.052,47 37.5,46.553 37.5,46 37.5,45.447 37.052,45 36.5,45z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<GeometryDrawing Brush="#FFD5D0BB">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M32.5,0L32.5,0 32.5,14 46.5,14z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FFED7161">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="12" RadiusY="12" Center="44.5,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M45.914,46L49.45,42.464C49.841,42.073 49.841,41.441 49.45,41.05 49.059,40.659 48.427,40.659 48.036,41.05L44.5,44.586 40.964,41.05C40.573,40.659 39.941,40.659 39.55,41.05 39.159,41.441 39.159,42.073 39.55,42.464L43.086,46 39.55,49.536C39.159,49.927 39.159,50.559 39.55,50.95 39.745,51.145 40.001,51.243 40.257,51.243 40.513,51.243 40.769,51.145 40.964,50.95L44.5,47.414 48.036,50.95C48.231,51.145 48.487,51.243 48.743,51.243 48.999,51.243 49.255,51.145 49.45,50.95 49.841,50.559 49.841,49.927 49.45,49.536L45.914,46z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>

View File

@ -1,203 +0,0 @@
<DrawingGroup xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DrawingGroup x:Name="DrawingLayer">
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0,0,59,59" />
</DrawingGroup.ClipGeometry>
<DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FF556080">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,19L3.608,19C1.616,19,0,17.384,0,15.392L0,3.608C0,1.616,1.616,0,3.608,0L54.391,0C56.384,0,58,1.616,58,3.608L58,15.391C58,17.384,56.384,19,54.392,19z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF38454F">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,38L3.608,38C1.616,38,0,36.384,0,34.392L0,22.608C0,20.616,1.616,19,3.608,19L54.391,19C56.384,19,58,20.616,58,22.608L58,34.391C58,36.384,56.384,38,54.392,38z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF556080">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,57L3.608,57C1.616,57,0,55.384,0,53.392L0,41.608C0,39.616,1.616,38,3.608,38L54.391,38C56.384,38,58,39.616,58,41.608L58,53.391C58,55.384,56.384,57,54.392,57z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,9.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,28.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,47.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FF26B999">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="12" RadiusY="12" Center="47,47" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M53.571,41.179C53.116,40.863,52.494,40.975,52.179,41.429L46.583,49.469 42.634,46.227C42.208,45.876 41.577,45.939 41.227,46.366 40.876,46.793 40.938,47.423 41.366,47.773L46.152,51.702C46.332,51.849 46.556,51.929 46.786,51.929 46.831,51.929 46.877,51.926 46.923,51.92 47.199,51.881 47.447,51.73 47.607,51.501L53.821,42.572C54.136,42.118,54.024,41.495,53.571,41.179z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>

View File

@ -1,203 +0,0 @@
<DrawingGroup xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DrawingGroup x:Name="DrawingLayer">
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0,0,59,59" />
</DrawingGroup.ClipGeometry>
<DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FF556080">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,19L3.608,19C1.616,19,0,17.384,0,15.392L0,3.608C0,1.616,1.616,0,3.608,0L54.391,0C56.384,0,58,1.616,58,3.608L58,15.391C58,17.384,56.384,19,54.392,19z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF38454F">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,38L3.608,38C1.616,38,0,36.384,0,34.392L0,22.608C0,20.616,1.616,19,3.608,19L54.391,19C56.384,19,58,20.616,58,22.608L58,34.391C58,36.384,56.384,38,54.392,38z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF556080">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M54.392,57L3.608,57C1.616,57,0,55.384,0,53.392L0,41.608C0,39.616,1.616,38,3.608,38L54.391,38C56.384,38,58,39.616,58,41.608L58,53.391C58,55.384,56.384,57,54.392,57z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,9.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,8" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,11" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,28.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,27" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="3.5" RadiusY="3.5" Center="9.5,47.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="49,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="45,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="51,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="47,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="41,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="43,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="37,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="39,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="33,46" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8697CB">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" Center="35,49" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingGroup>
<GeometryDrawing Brush="#FFED7161">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="12" RadiusY="12" Center="47,47" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry FillRule="Nonzero" Figures="M48.414,47L51.95,43.464C52.341,43.073 52.341,42.441 51.95,42.05 51.559,41.659 50.927,41.659 50.536,42.05L47,45.586 43.464,42.05C43.073,41.659 42.441,41.659 42.05,42.05 41.659,42.441 41.659,43.073 42.05,43.464L45.586,47 42.05,50.536C41.659,50.927 41.659,51.559 42.05,51.95 42.245,52.145 42.501,52.243 42.757,52.243 43.013,52.243 43.269,52.145 43.464,51.95L47,48.414 50.536,51.95C50.731,52.145 50.987,52.243 51.243,52.243 51.499,52.243 51.755,52.145 51.95,51.95 52.341,51.559 52.341,50.927 51.95,50.536L48.414,47z" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>

View File

@ -1,25 +0,0 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace PettingZoo.Infrastructure
{
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void RaiseOtherPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@ -1,81 +0,0 @@
using System;
using System.Windows.Input;
namespace PettingZoo.Infrastructure
{
public class DelegateCommand<T> : ICommand
{
private readonly Func<T, bool> canExecute;
private readonly Action<T> execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<T> execute) : this(execute, null)
{
}
public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute((T)parameter);
}
public void Execute(object parameter)
{
execute((T)parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public class DelegateCommand : ICommand
{
private readonly Func<bool> canExecute;
private readonly Action execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action execute) : this(execute, null) { }
public DelegateCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute();
}
public void Execute(object parameter)
{
execute();
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
}

View File

@ -1,14 +0,0 @@
namespace PettingZoo.Model
{
public class ConnectionInfo
{
public string Host { get; set; }
public string VirtualHost { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Exchange { get; set; }
public string RoutingKey { get; set; }
}
}

View File

@ -1,46 +0,0 @@
using System;
namespace PettingZoo.Model
{
public enum ConnectionStatus
{
Disconnected,
Connecting,
Connected,
Error
}
public class StatusChangedEventArgs : EventArgs
{
public ConnectionStatus Status { get; private set; }
public string Context { get; private set; }
public StatusChangedEventArgs(ConnectionStatus status, string context)
{
Status = status;
Context = context;
}
}
public class MessageReceivedEventArgs : EventArgs
{
public MessageInfo MessageInfo { get; private set; }
public MessageReceivedEventArgs(MessageInfo messageInfo)
{
MessageInfo = messageInfo;
}
}
public interface IConnection : IDisposable
{
event EventHandler<StatusChangedEventArgs> StatusChanged;
event EventHandler<MessageReceivedEventArgs> MessageReceived;
}
}

View File

@ -1,7 +0,0 @@
namespace PettingZoo.Model
{
public interface IConnectionFactory
{
IConnection CreateConnection(ConnectionInfo connectionInfo);
}
}

View File

@ -1,7 +0,0 @@
namespace PettingZoo.Model
{
public interface IConnectionInfoBuilder
{
ConnectionInfo Build();
}
}

View File

@ -1,31 +0,0 @@
using System;
using System.Collections.Generic;
namespace PettingZoo.Model
{
public class MessageInfo
{
public DateTime Timestamp { get; set; }
public string Exchange { get; set; }
public string RoutingKey { get; set; }
public byte[] Body { get; set; }
public Dictionary<string, string> Properties;
public string ContentType
{
get
{
return Properties != null && Properties.ContainsKey(RabbitMQProperties.ContentType)
? Properties[RabbitMQProperties.ContentType]
: "";
}
}
public MessageInfo()
{
Timestamp = DateTime.Now;
}
}
}

View File

@ -1,190 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using PettingZoo.Properties;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace PettingZoo.Model
{
public class RabbitMQClientConnection : IConnection
{
private const int ConnectRetryDelay = 5000;
private readonly CancellationTokenSource connectionTaskToken;
private RabbitMQ.Client.IConnection connection;
private IModel model;
public event EventHandler<StatusChangedEventArgs> StatusChanged;
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
public RabbitMQClientConnection(ConnectionInfo connectionInfo)
{
connectionTaskToken = new CancellationTokenSource();
var connectionToken = connectionTaskToken.Token;
Task.Factory.StartNew(() => TryConnection(connectionInfo, connectionToken), connectionToken);
}
public void Dispose()
{
connectionTaskToken.Cancel();
if (model != null)
{
model.Dispose();
model = null;
}
if (connection != null)
{
connection.Dispose();
connection = null;
}
StatusChanged = null;
MessageReceived = null;
}
private void TryConnection(ConnectionInfo connectionInfo, CancellationToken cancellationToken)
{
var factory = new ConnectionFactory
{
HostName = connectionInfo.Host,
Port = connectionInfo.Port,
VirtualHost = connectionInfo.VirtualHost,
UserName = connectionInfo.Username,
Password = connectionInfo.Password
};
var statusContext = String.Format("{0}:{1}{2}", connectionInfo.Host, connectionInfo.Port, connectionInfo.VirtualHost);
while (!cancellationToken.IsCancellationRequested)
{
DoStatusChanged(ConnectionStatus.Connecting, statusContext);
try
{
connection = factory.CreateConnection();
model = connection.CreateModel();
var queueName = model.QueueDeclare().QueueName;
model.QueueBind(queueName, connectionInfo.Exchange, connectionInfo.RoutingKey);
var consumer = new EventingBasicConsumer(model);
consumer.Received += ClientReceived;
model.BasicConsume(queueName, true, consumer);
DoStatusChanged(ConnectionStatus.Connected, statusContext);
break;
}
catch (Exception e)
{
DoStatusChanged(ConnectionStatus.Error, e.Message);
Task.Delay(ConnectRetryDelay, cancellationToken).Wait(cancellationToken);
}
}
}
private void ClientReceived(object sender, BasicDeliverEventArgs args)
{
if (MessageReceived == null)
return;
MessageReceived(this, new MessageReceivedEventArgs(
new MessageInfo
{
Exchange = args.Exchange,
RoutingKey = args.RoutingKey,
Body = args.Body,
Properties = ConvertProperties(args.BasicProperties)
}
));
}
private void DoStatusChanged(ConnectionStatus status, string context = null)
{
if (StatusChanged != null)
StatusChanged(this, new StatusChangedEventArgs(status, context));
}
private static Dictionary<string, string> ConvertProperties(IBasicProperties basicProperties)
{
var properties = new Dictionary<string, string>();
if (basicProperties.IsDeliveryModePresent())
{
string deliveryMode;
switch (basicProperties.DeliveryMode)
{
case 1:
deliveryMode = Resources.DeliveryModeNonPersistent;
break;
case 2:
deliveryMode = Resources.DeliveryModePersistent;
break;
default:
deliveryMode = basicProperties.DeliveryMode.ToString(CultureInfo.InvariantCulture);
break;
}
properties.Add(RabbitMQProperties.DeliveryMode, deliveryMode);
}
if (basicProperties.IsContentTypePresent())
properties.Add(RabbitMQProperties.ContentType, basicProperties.ContentType);
if (basicProperties.IsContentEncodingPresent())
properties.Add(RabbitMQProperties.ContentEncoding, basicProperties.ContentEncoding);
if (basicProperties.IsPriorityPresent())
properties.Add(RabbitMQProperties.Priority, basicProperties.Priority.ToString(CultureInfo.InvariantCulture));
if (basicProperties.IsCorrelationIdPresent())
properties.Add(RabbitMQProperties.Priority, basicProperties.CorrelationId);
if (basicProperties.IsReplyToPresent())
properties.Add(RabbitMQProperties.ReplyTo, basicProperties.ReplyTo);
if (basicProperties.IsExpirationPresent())
properties.Add(RabbitMQProperties.Expiration, basicProperties.Expiration);
if (basicProperties.IsMessageIdPresent())
properties.Add(RabbitMQProperties.MessageId, basicProperties.MessageId);
if (basicProperties.IsTimestampPresent())
properties.Add(RabbitMQProperties.Timestamp, basicProperties.Timestamp.UnixTime.ToString(CultureInfo.InvariantCulture));
if (basicProperties.IsTypePresent())
properties.Add(RabbitMQProperties.Type, basicProperties.Type);
if (basicProperties.IsUserIdPresent())
properties.Add(RabbitMQProperties.UserId, basicProperties.UserId);
if (basicProperties.IsAppIdPresent())
properties.Add(RabbitMQProperties.UserId, basicProperties.AppId);
if (basicProperties.IsClusterIdPresent())
properties.Add(RabbitMQProperties.ClusterId, basicProperties.ClusterId);
foreach (var header in basicProperties.Headers)
properties.Add(header.Key, Encoding.UTF8.GetString((byte[])header.Value));
return properties;
}
}
}

View File

@ -1,10 +0,0 @@
namespace PettingZoo.Model
{
public class RabbitMQClientConnectionFactory : IConnectionFactory
{
public IConnection CreateConnection(ConnectionInfo connectionInfo)
{
return new RabbitMQClientConnection(connectionInfo);
}
}
}

View File

@ -1,19 +0,0 @@
namespace PettingZoo.Model
{
static class RabbitMQProperties
{
public const string ContentType = "content-type";
public const string ContentEncoding = "content-encoding";
public const string DeliveryMode = "delivery-mode";
public const string Priority = "priority";
public const string CorrelationId = "correlation-id";
public const string ReplyTo = "reply-to";
public const string Expiration = "expiration";
public const string MessageId = "message-id";
public const string Timestamp = "timestamp";
public const string Type = "type";
public const string UserId = "user-id";
public const string AppId = "app-id";
public const string ClusterId = "cluster-id";
}
}

View File

@ -1,58 +0,0 @@
namespace PettingZoo.Model
{
public interface IUserSettingsSerializer
{
void Read(UserSettings settings);
void Write(UserSettings settings);
}
public class ConnectionWindowSettings
{
public string LastHost { get; set; }
public string LastVirtualHost { get; set; }
public int LastPort { get; set; }
public string LastUsername { get; set; }
public string LastPassword { get; set; }
public string LastExchange { get; set; }
public string LastRoutingKey { get; set; }
public ConnectionWindowSettings()
{
LastHost = "localhost";
LastPort = 5672;
LastVirtualHost = "/";
LastUsername = "guest";
LastPassword = "guest";
LastExchange = "amqp";
LastRoutingKey = "#";
}
}
public class UserSettings
{
public ConnectionWindowSettings ConnectionWindow { get; private set; }
private readonly IUserSettingsSerializer serializer;
public UserSettings(IUserSettingsSerializer serializer)
{
ConnectionWindow = new ConnectionWindowSettings();
this.serializer = serializer;
serializer.Read(this);
}
public void Save()
{
serializer.Write(this);
}
}
}

View File

@ -0,0 +1,21 @@
namespace PettingZoo.Core.Connection
{
public class ConnectionParams
{
public string Host { get; }
public string VirtualHost { get; }
public int Port { get; }
public string Username { get; }
public string Password { get; }
public ConnectionParams(string host, string virtualHost, int port, string username, string password)
{
Host = host;
VirtualHost = virtualHost;
Port = port;
Username = username;
Password = password;
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Threading.Tasks;
namespace PettingZoo.Core.Connection
{
public interface IConnection : IAsyncDisposable
{
event EventHandler<StatusChangedEventArgs> StatusChanged;
ISubscriber Subscribe(string exchange, string routingKey);
ISubscriber Subscribe();
Task Publish(PublishMessageInfo messageInfo);
}
public enum ConnectionStatus
{
Disconnected,
Connecting,
Connected,
Error
}
public class StatusChangedEventArgs : EventArgs
{
public ConnectionStatus Status { get; }
public string? Context { get; }
public StatusChangedEventArgs(ConnectionStatus status, string? context)
{
Status = status;
Context = context;
}
}
}

View File

@ -0,0 +1,7 @@
namespace PettingZoo.Core.Connection
{
public interface IConnectionFactory
{
IConnection CreateConnection(ConnectionParams connectionInfo);
}
}

View File

@ -0,0 +1,27 @@
using System;
namespace PettingZoo.Core.Connection
{
public interface ISubscriber : IAsyncDisposable
{
string? QueueName { get; }
string? Exchange {get; }
string? RoutingKey { get; }
event EventHandler<MessageReceivedEventArgs>? MessageReceived;
void Start();
}
public class MessageReceivedEventArgs : EventArgs
{
public ReceivedMessageInfo MessageInfo { get; }
public MessageReceivedEventArgs(ReceivedMessageInfo messageInfo)
{
MessageInfo = messageInfo;
}
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
namespace PettingZoo.Core.Connection
{
public class BaseMessageInfo
{
public string Exchange { get; }
public string RoutingKey { get; }
public byte[] Body { get; }
public MessageProperties Properties { get; }
public BaseMessageInfo(string exchange, string routingKey, byte[] body, MessageProperties properties)
{
Exchange = exchange;
RoutingKey = routingKey;
Body = body;
Properties = properties;
}
}
public class ReceivedMessageInfo : BaseMessageInfo
{
public DateTime ReceivedTimestamp { get; }
public ReceivedMessageInfo(string exchange, string routingKey, byte[] body, MessageProperties properties, DateTime receivedTimestamp)
: base(exchange, routingKey, body, properties)
{
ReceivedTimestamp = receivedTimestamp;
}
}
public class PublishMessageInfo : BaseMessageInfo
{
public PublishMessageInfo(string exchange, string routingKey, byte[] body, MessageProperties properties)
: base(exchange, routingKey, body, properties)
{
}
}
public enum MessageDeliveryMode
{
NonPersistent = 1,
Persistent = 2
}
public class MessageProperties
{
private static readonly IReadOnlyDictionary<string, string> EmptyHeaders = new Dictionary<string, string>();
public MessageProperties(IReadOnlyDictionary<string, string>? headers)
{
Headers = headers ?? EmptyHeaders;
}
public string? AppId { get; init; }
public string? ContentEncoding { get; init; }
public string? ContentType { get; init; }
public string? CorrelationId { get; init; }
public MessageDeliveryMode? DeliveryMode { get; init; }
public string? Expiration { get; init; }
public IReadOnlyDictionary<string, string> Headers { get; }
public string? MessageId { get; init; }
public byte? Priority { get; init; }
public string? ReplyTo { get; init; }
public DateTime? Timestamp { get; init; }
public string? Type { get; init; }
public string? UserId { get; init; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
namespace PettingZoo.Core.Generator
{
public interface IExampleSource : IDisposable
{
IExampleFolder GetRootFolder();
}
public interface IExampleFolder
{
public string Name { get; }
public IReadOnlyList<IExampleFolder> Folders { get; }
public IReadOnlyList<IExampleMessage> Messages { get; }
}
public interface IExampleMessage
{
string Generate();
}
}

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<Version>0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="Rendering\MessagePropertiesRendererStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>MessagePropertiesRendererStrings.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Rendering\MessagePropertiesRendererStrings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>MessagePropertiesRendererStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -3,25 +3,23 @@ using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
namespace PettingZoo.Model
namespace PettingZoo.Core.Rendering
{
public class MessageBodyRenderer
{
public static Dictionary<string, Func<byte[], string>> ContentTypeHandlers = new Dictionary<string, Func<byte[], string>>
public static Dictionary<string, Func<byte[], string>> ContentTypeHandlers = new()
{
{ "application/json", RenderJson }
};
public static string Render(byte[] body, string contentType = "")
public static string Render(byte[] body, string? contentType)
{
Func<byte[], string> handler;
if (ContentTypeHandlers.TryGetValue(contentType, out handler))
return handler(body);
return (contentType != null) && ContentTypeHandlers.TryGetValue(contentType, out var handler)
? handler(body)
: Encoding.UTF8.GetString(body);
// ToDo hex output if required
return Encoding.UTF8.GetString(body);
}

View File

@ -0,0 +1,60 @@
using System.Collections.Generic;
using PettingZoo.Core.Connection;
namespace PettingZoo.Core.Rendering
{
public class MessagePropertiesRenderer
{
public static IDictionary<string, string> Render(MessageProperties properties)
{
var result = new Dictionary<string, string>();
if (properties.AppId != null)
result.Add(MessagePropertiesRendererStrings.AppId, properties.AppId);
if (properties.ContentEncoding != null)
result.Add(MessagePropertiesRendererStrings.ContentEncoding, properties.ContentEncoding);
if (properties.ContentType != null)
result.Add(MessagePropertiesRendererStrings.ContentType, properties.ContentType);
if (properties.CorrelationId != null)
result.Add(MessagePropertiesRendererStrings.CorrelationId, properties.CorrelationId);
if (properties.DeliveryMode != null)
result.Add(MessagePropertiesRendererStrings.DeliveryMode,
properties.DeliveryMode == MessageDeliveryMode.Persistent
? MessagePropertiesRendererStrings.DeliveryModePersistent
: MessagePropertiesRendererStrings.DeliveryModeNonPersistent);
if (properties.Expiration != null)
result.Add(MessagePropertiesRendererStrings.Expiration, properties.Expiration);
if (properties.MessageId != null)
result.Add(MessagePropertiesRendererStrings.MessageId, properties.MessageId);
if (properties.Priority != null)
result.Add(MessagePropertiesRendererStrings.Priority, properties.Priority.Value.ToString());
if (properties.ReplyTo != null)
result.Add(MessagePropertiesRendererStrings.ReplyTo, properties.ReplyTo);
if (properties.Timestamp != null)
result.Add(MessagePropertiesRendererStrings.Timestamp, properties.Timestamp.Value.ToString("G"));
if (properties.Type != null)
result.Add(MessagePropertiesRendererStrings.Type, properties.Type);
if (properties.UserId != null)
result.Add(MessagePropertiesRendererStrings.UserId, properties.UserId);
foreach (var (key, value) in properties.Headers)
{
if (!result.TryAdd(key, value))
result.TryAdd(MessagePropertiesRendererStrings.HeaderPrefix + key, value);
}
return result;
}
}
}

View File

@ -0,0 +1,198 @@
//------------------------------------------------------------------------------
// <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 PettingZoo.Core.Rendering {
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", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class MessagePropertiesRendererStrings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal MessagePropertiesRendererStrings() {
}
/// <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("PettingZoo.Core.Rendering.MessagePropertiesRendererStrings", typeof(MessagePropertiesRendererStrings).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 string similar to App ID.
/// </summary>
internal static string AppId {
get {
return ResourceManager.GetString("AppId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Content encoding.
/// </summary>
internal static string ContentEncoding {
get {
return ResourceManager.GetString("ContentEncoding", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Content type.
/// </summary>
internal static string ContentType {
get {
return ResourceManager.GetString("ContentType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Correlation ID.
/// </summary>
internal static string CorrelationId {
get {
return ResourceManager.GetString("CorrelationId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delivery mode.
/// </summary>
internal static string DeliveryMode {
get {
return ResourceManager.GetString("DeliveryMode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Transient (1).
/// </summary>
internal static string DeliveryModeNonPersistent {
get {
return ResourceManager.GetString("DeliveryModeNonPersistent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Persistent (2).
/// </summary>
internal static string DeliveryModePersistent {
get {
return ResourceManager.GetString("DeliveryModePersistent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Expiration.
/// </summary>
internal static string Expiration {
get {
return ResourceManager.GetString("Expiration", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Header: .
/// </summary>
internal static string HeaderPrefix {
get {
return ResourceManager.GetString("HeaderPrefix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Message ID.
/// </summary>
internal static string MessageId {
get {
return ResourceManager.GetString("MessageId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Priority.
/// </summary>
internal static string Priority {
get {
return ResourceManager.GetString("Priority", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reply To.
/// </summary>
internal static string ReplyTo {
get {
return ResourceManager.GetString("ReplyTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Timestamp.
/// </summary>
internal static string Timestamp {
get {
return ResourceManager.GetString("Timestamp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type.
/// </summary>
internal static string Type {
get {
return ResourceManager.GetString("Type", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to User ID.
/// </summary>
internal static string UserId {
get {
return ResourceManager.GetString("UserId", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,165 @@
<?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>
<data name="AppId" xml:space="preserve">
<value>App ID</value>
</data>
<data name="ContentEncoding" xml:space="preserve">
<value>Content encoding</value>
</data>
<data name="ContentType" xml:space="preserve">
<value>Content type</value>
</data>
<data name="CorrelationId" xml:space="preserve">
<value>Correlation ID</value>
</data>
<data name="DeliveryMode" xml:space="preserve">
<value>Delivery mode</value>
</data>
<data name="DeliveryModeNonPersistent" xml:space="preserve">
<value>Transient (1)</value>
</data>
<data name="DeliveryModePersistent" xml:space="preserve">
<value>Persistent (2)</value>
</data>
<data name="Expiration" xml:space="preserve">
<value>Expiration</value>
</data>
<data name="HeaderPrefix" xml:space="preserve">
<value>Header: </value>
</data>
<data name="MessageId" xml:space="preserve">
<value>Message ID</value>
</data>
<data name="Priority" xml:space="preserve">
<value>Priority</value>
</data>
<data name="ReplyTo" xml:space="preserve">
<value>Reply To</value>
</data>
<data name="Timestamp" xml:space="preserve">
<value>Timestamp</value>
</data>
<data name="Type" xml:space="preserve">
<value>Type</value>
</data>
<data name="UserId" xml:space="preserve">
<value>User ID</value>
</data>
</root>

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace PettingZoo.Core.Settings
{
public interface IConnectionSettingsRepository
{
Task<StoredConnectionSettings> GetLastUsed();
Task StoreLastUsed(bool storePassword, ConnectionSettings connectionSettings);
Task<IEnumerable<StoredConnectionSettings>> GetStored();
Task<StoredConnectionSettings> Add(string displayName, bool storePassword, ConnectionSettings connectionSettings);
Task<StoredConnectionSettings> Update(Guid id, string displayName, bool storePassword, ConnectionSettings connectionSettings);
Task Delete(Guid id);
}
public class ConnectionSettings
{
public string Host { get; }
public string VirtualHost { get; }
public int Port { get; }
public string Username { get; }
public string Password { get; }
public bool Subscribe { get; }
public string Exchange { get; }
public string RoutingKey { get; }
public static readonly ConnectionSettings Default = new("localhost", "/", 5672, "guest", "guest", false, "", "#");
public ConnectionSettings(string host, string virtualHost, int port, string username, string password,
bool subscribe, string exchange, string routingKey)
{
Host = host;
VirtualHost = virtualHost;
Port = port;
Username = username;
Password = password;
Subscribe = subscribe;
Exchange = exchange;
RoutingKey = routingKey;
}
public bool SameParameters(ConnectionSettings value, bool comparePassword = true)
{
return Host == value.Host &&
VirtualHost == value.VirtualHost &&
Port == value.Port &&
Username == value.Username &&
(!comparePassword || Password == value.Password) &&
Subscribe == value.Subscribe &&
Exchange == value.Exchange &&
RoutingKey == value.RoutingKey;
}
}
public class StoredConnectionSettings : ConnectionSettings
{
public Guid Id { get; }
public string DisplayName { get; }
public bool StorePassword { get; }
public StoredConnectionSettings(Guid id, string displayName, bool storePassword, string host, string virtualHost, int port, string username,
string password, bool subscribe, string exchange, string routingKey)
: base(host, virtualHost, port, username, password, subscribe, exchange, routingKey)
{
Id = id;
DisplayName = displayName;
StorePassword = storePassword;
}
public StoredConnectionSettings(Guid id, string displayName, bool storePassword, ConnectionSettings connectionSettings)
: base(connectionSettings.Host, connectionSettings.VirtualHost, connectionSettings.Port, connectionSettings.Username,
connectionSettings.Password, connectionSettings.Subscribe, connectionSettings.Exchange, connectionSettings.RoutingKey)
{
Id = id;
DisplayName = displayName;
StorePassword = storePassword;
}
}
}

View File

@ -0,0 +1,30 @@
using System.Threading.Tasks;
namespace PettingZoo.Core.Settings
{
public interface IUISettingsRepository
{
Task<MainWindowPositionSettings?> GetMainWindowPosition();
Task StoreMainWindowPosition(MainWindowPositionSettings settings);
}
public class MainWindowPositionSettings
{
public int Top { get; }
public int Left { get; }
public int Width { get; }
public int Height { get; }
public bool Maximized { get; }
public MainWindowPositionSettings(int top, int left, int width, int height, bool maximized)
{
Top = top;
Left = left;
Width = width;
Height = height;
Maximized = maximized;
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<Version>0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="RabbitMQ.Client" Version="6.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PettingZoo.Core\PettingZoo.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,156 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using PettingZoo.Core.Connection;
using RabbitMQ.Client;
namespace PettingZoo.RabbitMQ
{
public class RabbitMQClientConnection : Core.Connection.IConnection
{
private const int ConnectRetryDelay = 5000;
private readonly CancellationTokenSource connectionTaskToken = new();
private readonly Task connectionTask;
private readonly object connectionLock = new();
private global::RabbitMQ.Client.IConnection? connection;
private IModel? model;
public event EventHandler<StatusChangedEventArgs>? StatusChanged;
public RabbitMQClientConnection(ConnectionParams connectionParams)
{
connectionTask = Task.Factory.StartNew(() => TryConnection(connectionParams, connectionTaskToken.Token), CancellationToken.None);
}
public async ValueTask DisposeAsync()
{
connectionTaskToken.Cancel();
if (!connectionTask.IsCompleted)
await connectionTask;
lock (connectionLock)
{
if (model != null)
{
model.Dispose();
model = null;
}
if (connection != null)
{
connection.Dispose();
connection = null;
}
}
GC.SuppressFinalize(this);
}
public ISubscriber Subscribe(string exchange, string routingKey)
{
return CreateSubscriber(exchange, routingKey);
}
public ISubscriber Subscribe()
{
return CreateSubscriber(null, null);
}
private ISubscriber CreateSubscriber(string? exchange, string? routingKey)
{
lock (connectionLock)
{
var subscriber = new RabbitMQClientSubscriber(model, exchange, routingKey);
if (model != null)
return subscriber;
void ConnectSubscriber(object? sender, StatusChangedEventArgs args)
{
if (args.Status != ConnectionStatus.Connected)
return;
lock (connectionLock)
{
if (model == null)
return;
subscriber.Connected(model);
}
StatusChanged -= ConnectSubscriber;
}
StatusChanged += ConnectSubscriber;
return subscriber;
}
}
public Task Publish(PublishMessageInfo messageInfo)
{
if (model == null)
throw new InvalidOperationException("Not connected");
model.BasicPublish(messageInfo.Exchange, messageInfo.RoutingKey, false,
RabbitMQClientPropertiesConverter.Convert(messageInfo.Properties, model.CreateBasicProperties()),
messageInfo.Body);
return Task.CompletedTask;
}
private void TryConnection(ConnectionParams connectionParams, CancellationToken cancellationToken)
{
var factory = new ConnectionFactory
{
HostName = connectionParams.Host,
Port = connectionParams.Port,
VirtualHost = connectionParams.VirtualHost,
UserName = connectionParams.Username,
Password = connectionParams.Password
};
var statusContext = $"{connectionParams.Host}:{connectionParams.Port}{connectionParams.VirtualHost}";
while (!cancellationToken.IsCancellationRequested)
{
DoStatusChanged(ConnectionStatus.Connecting, statusContext);
try
{
connection = factory.CreateConnection();
model = connection.CreateModel();
DoStatusChanged(ConnectionStatus.Connected, statusContext);
break;
}
catch (Exception e)
{
DoStatusChanged(ConnectionStatus.Error, e.Message);
try
{
Task.Delay(ConnectRetryDelay, cancellationToken).Wait(cancellationToken);
}
catch (OperationCanceledException)
{
}
}
}
}
private void DoStatusChanged(ConnectionStatus status, string? context = null)
{
StatusChanged?.Invoke(this, new StatusChangedEventArgs(status, context));
}
}
}

View File

@ -0,0 +1,12 @@
using PettingZoo.Core.Connection;
namespace PettingZoo.RabbitMQ
{
public class RabbitMQClientConnectionFactory : IConnectionFactory
{
public IConnection CreateConnection(ConnectionParams connectionParams)
{
return new RabbitMQClientConnection(connectionParams);
}
}
}

View File

@ -0,0 +1,137 @@
using System;
using System.Linq;
using System.Text;
using PettingZoo.Core.Connection;
using RabbitMQ.Client;
namespace PettingZoo.RabbitMQ
{
public static class RabbitMQClientPropertiesConverter
{
public static MessageProperties Convert(IBasicProperties basicProperties)
{
return new MessageProperties(basicProperties.Headers?.ToDictionary(p => p.Key, p => Encoding.UTF8.GetString((byte[])p.Value)))
{
DeliveryMode = basicProperties.IsDeliveryModePresent()
? basicProperties.DeliveryMode == 2 ? MessageDeliveryMode.Persistent :
MessageDeliveryMode.NonPersistent
: null,
ContentType = basicProperties.IsContentTypePresent()
? basicProperties.ContentType
: null,
ContentEncoding = basicProperties.IsContentEncodingPresent()
? basicProperties.ContentEncoding
: null,
Priority = basicProperties.IsPriorityPresent()
? basicProperties.Priority
: null,
CorrelationId = basicProperties.IsCorrelationIdPresent()
? basicProperties.CorrelationId
: null,
ReplyTo = basicProperties.IsReplyToPresent()
? basicProperties.ReplyTo
: null,
Expiration = basicProperties.IsExpirationPresent()
? basicProperties.Expiration
: null,
MessageId = basicProperties.IsMessageIdPresent()
? basicProperties.MessageId
: null,
Timestamp = basicProperties.IsTimestampPresent()
? DateTimeOffset.FromUnixTimeMilliseconds(basicProperties.Timestamp.UnixTime).LocalDateTime
: null,
Type = basicProperties.IsTypePresent()
? basicProperties.Type
: null,
UserId = basicProperties.IsUserIdPresent()
? basicProperties.UserId
: null,
AppId = basicProperties.IsAppIdPresent()
? basicProperties.AppId
: null
};
}
public static IBasicProperties Convert(MessageProperties properties, IBasicProperties targetProperties)
{
if (properties.DeliveryMode != null)
targetProperties.DeliveryMode = properties.DeliveryMode == MessageDeliveryMode.Persistent ? (byte)2 : (byte)1;
else
targetProperties.ClearDeliveryMode();
if (properties.ContentType != null)
targetProperties.ContentType = properties.ContentType;
else
targetProperties.ClearContentType();
if (properties.ContentEncoding != null)
targetProperties.ContentEncoding = properties.ContentEncoding;
else
targetProperties.ClearContentEncoding();
if (properties.Priority != null)
targetProperties.Priority = properties.Priority.Value;
else
targetProperties.ClearPriority();
if (properties.CorrelationId != null)
targetProperties.CorrelationId = properties.CorrelationId;
else
targetProperties.ClearCorrelationId();
if (properties.ReplyTo != null)
targetProperties.ReplyTo = properties.ReplyTo;
else
targetProperties.ClearReplyTo();
if (properties.Expiration != null)
targetProperties.Expiration = properties.Expiration;
else
targetProperties.ClearExpiration();
if (properties.MessageId != null)
targetProperties.MessageId = properties.MessageId;
else
targetProperties.ClearMessageId();
if (properties.Timestamp != null)
targetProperties.Timestamp = new AmqpTimestamp(new DateTimeOffset(properties.Timestamp.Value).ToUnixTimeMilliseconds());
else
targetProperties.ClearTimestamp();
if (properties.Type != null)
targetProperties.Type = properties.Type;
else
targetProperties.ClearType();
if (properties.UserId != null)
targetProperties.UserId = properties.UserId;
else
targetProperties.ClearUserId();
if (properties.AppId != null)
targetProperties.AppId = properties.AppId;
else
targetProperties.ClearAppId();
if (properties.Headers.Count > 0)
targetProperties.Headers = properties.Headers.ToDictionary(p => p.Key, p => (object)Encoding.UTF8.GetBytes(p.Value));
else
targetProperties.ClearHeaders();
return targetProperties;
}
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.Threading.Tasks;
using PettingZoo.Core.Connection;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace PettingZoo.RabbitMQ
{
public class RabbitMQClientSubscriber : ISubscriber
{
private IModel? model;
private string? consumerTag;
private bool started;
public string? QueueName { get; private set; }
public string? Exchange { get; }
public string? RoutingKey { get; }
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public RabbitMQClientSubscriber(IModel? model, string? exchange, string? routingKey)
{
this.model = model;
Exchange = exchange;
RoutingKey = routingKey;
}
public ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
if (model != null && consumerTag != null && model.IsOpen)
model.BasicCancelNoWait(consumerTag);
return default;
}
public void Start()
{
started = true;
if (model == null)
return;
QueueName = model.QueueDeclare().QueueName;
if (Exchange != null && RoutingKey != null)
model.QueueBind(QueueName, Exchange, RoutingKey);
var consumer = new EventingBasicConsumer(model);
consumer.Received += ClientReceived;
consumerTag = model.BasicConsume(QueueName, true, consumer);
}
public void Connected(IModel newModel)
{
model = newModel;
if (started)
Start();
}
private void ClientReceived(object? sender, BasicDeliverEventArgs args)
{
MessageReceived?.Invoke(this, new MessageReceivedEventArgs(
new ReceivedMessageInfo(
args.Exchange,
args.RoutingKey,
args.Body.ToArray(),
RabbitMQClientPropertiesConverter.Convert(args.BasicProperties),
args.BasicProperties.Timestamp.UnixTime > 0
? DateTimeOffset.FromUnixTimeSeconds(args.BasicProperties.Timestamp.UnixTime).LocalDateTime
: DateTime.Now
)
));
}
}
}

View File

@ -0,0 +1,35 @@
using LiteDB;
using LiteDB.Async;
namespace PettingZoo.Settings.LiteDB
{
public class BaseLiteDBRepository
{
private readonly string databaseFilename;
protected static readonly BsonMapper Mapper = new()
{
EmptyStringToNull = false
};
public BaseLiteDBRepository(string databaseName)
{
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (appDataPath == null)
throw new IOException("Could not resolve application data path");
var databasePath = Path.Combine(appDataPath, @"PettingZoo");
if (!Directory.CreateDirectory(databasePath).Exists)
throw new IOException($"Failed to create directory: {databasePath}");
databaseFilename = Path.Combine(databasePath, $"{databaseName}.litedb");
}
protected ILiteDatabaseAsync GetDatabase()
{
return new LiteDatabaseAsync(databaseFilename, Mapper);
}
}
}

View File

@ -0,0 +1,133 @@
using PettingZoo.Core.Settings;
namespace PettingZoo.Settings.LiteDB
{
public class LiteDBConnectionSettingsRepository : BaseLiteDBRepository, IConnectionSettingsRepository
{
private static readonly Guid LastUsedId = new("1624147f-76b2-4b5e-8e6f-2ef1730a0a99");
private const string CollectionLastUsed = "lastUsed";
private const string CollectionStored = "stored";
public LiteDBConnectionSettingsRepository() : base(@"connectionSettings")
{
}
public async Task<StoredConnectionSettings> GetLastUsed()
{
using var database = GetDatabase();
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionLastUsed);
var lastUsed = await collection.FindOneAsync(r => true);
if (lastUsed == null)
return new StoredConnectionSettings(Guid.Empty, "", true, ConnectionSettings.Default);
return new StoredConnectionSettings(
Guid.Empty,
"",
lastUsed.Password != null,
lastUsed.Host,
lastUsed.VirtualHost,
lastUsed.Port,
lastUsed.Username,
lastUsed.Password ?? "",
lastUsed.Subscribe,
lastUsed.Exchange,
lastUsed.RoutingKey);
}
public async Task StoreLastUsed(bool storePassword, ConnectionSettings connectionSettings)
{
using var database = GetDatabase();
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionLastUsed);
await collection.UpsertAsync(ConnectionSettingsRecord.FromConnectionSettings(LastUsedId, connectionSettings, "", storePassword));
}
public async Task<IEnumerable<StoredConnectionSettings>> GetStored()
{
using var database = GetDatabase();
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionStored);
return (await collection.FindAllAsync())
.Select(r => new StoredConnectionSettings(r.Id, r.DisplayName, r.Password != null, r.Host, r.VirtualHost, r.Port, r.Username, r.Password ?? "", r.Subscribe, r.Exchange, r.RoutingKey))
.ToArray();
}
public async Task<StoredConnectionSettings> Add(string displayName, bool storePassword, ConnectionSettings connectionSettings)
{
using var database = GetDatabase();
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionStored);
var id = Guid.NewGuid();
await collection.InsertAsync(ConnectionSettingsRecord.FromConnectionSettings(id, connectionSettings, displayName, storePassword));
return new StoredConnectionSettings(id, displayName, storePassword, connectionSettings);
}
public async Task<StoredConnectionSettings> Update(Guid id, string displayName, bool storePassword, ConnectionSettings connectionSettings)
{
using var database = GetDatabase();
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionStored);
await collection.UpdateAsync(ConnectionSettingsRecord.FromConnectionSettings(id, connectionSettings, displayName, storePassword));
return new StoredConnectionSettings(id, displayName, storePassword, connectionSettings);
}
public async Task Delete(Guid id)
{
using var database = GetDatabase();
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionStored);
await collection.DeleteAsync(id);
}
// ReSharper disable MemberCanBePrivate.Local - for LiteDB
// ReSharper disable PropertyCanBeMadeInitOnly.Local
private class ConnectionSettingsRecord
{
public Guid Id { get; set; }
public string DisplayName { get; set; } = null!;
public string Host { get; set; } = null!;
public string VirtualHost { get; set; } = null!;
public int Port { get; set; }
public string Username { get; set; } = null!;
public string? Password { get; set; }
public bool Subscribe { get; set; }
public string Exchange { get; set; } = null!;
public string RoutingKey { get; set; } = null!;
public static ConnectionSettingsRecord FromConnectionSettings(Guid id, ConnectionSettings connectionSettings, string displayName, bool storePassword)
{
return new ConnectionSettingsRecord
{
Id = id,
DisplayName = displayName,
Host = connectionSettings.Host,
VirtualHost = connectionSettings.VirtualHost,
Port = connectionSettings.Port,
Username = connectionSettings.Username,
Password = storePassword ? connectionSettings.Password : null,
Subscribe = connectionSettings.Subscribe,
Exchange = connectionSettings.Exchange,
RoutingKey = connectionSettings.RoutingKey
};
}
}
// ReSharper restore PropertyCanBeMadeInitOnly.Local
// ReSharper restore MemberCanBePrivate.Local
}
}

View File

@ -0,0 +1,84 @@
using PettingZoo.Core.Settings;
namespace PettingZoo.Settings.LiteDB
{
public class LiteDBUISettingsRepository : BaseLiteDBRepository, IUISettingsRepository
{
private const string CollectionSettings = "settings";
public LiteDBUISettingsRepository() : base(@"uiSettings")
{
}
public async Task<MainWindowPositionSettings?> GetMainWindowPosition()
{
using var database = GetDatabase();
var collection = database.GetCollection(CollectionSettings);
var settings = await collection.FindByIdAsync(MainWindowPositionSettingsRecord.SettingsKey);
if (settings == null)
return null;
var position = Mapper.ToObject<MainWindowPositionSettingsRecord>(settings);
return new MainWindowPositionSettings(
position.Top,
position.Left,
position.Width,
position.Height,
position.Maximized);
}
public async Task StoreMainWindowPosition(MainWindowPositionSettings settings)
{
using var database = GetDatabase();
var collection = database.GetCollection(CollectionSettings);
await collection.UpsertAsync(
Mapper.ToDocument(new MainWindowPositionSettingsRecord
{
Top = settings.Top,
Left = settings.Left,
Width = settings.Width,
Height = settings.Height,
Maximized = settings.Maximized
}));
}
// ReSharper disable MemberCanBePrivate.Local - for LiteDB
// ReSharper disable PropertyCanBeMadeInitOnly.Local
private class BaseSettingsRecord
{
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public Guid Id { get; }
protected BaseSettingsRecord(Guid id)
{
Id = id;
}
}
private class MainWindowPositionSettingsRecord : BaseSettingsRecord
{
public static readonly Guid SettingsKey = new("fc71cf99-0744-4f5d-ada8-6a78d1df7b62");
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public bool Maximized { get; set; }
public MainWindowPositionSettingsRecord() : base(SettingsKey)
{
}
}
// ReSharper restore PropertyCanBeMadeInitOnly.Local
// ReSharper restore MemberCanBePrivate.Local
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.11" />
<PackageReference Include="LiteDB.Async" Version="0.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PettingZoo.Core\PettingZoo.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PettingZoo.Core\PettingZoo.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
using PettingZoo.Core.Generator;
namespace PettingZoo.Tapeti
{
public class TapetiClassLibraryExampleSource : IExampleSource
{
private readonly string classLibraryFilename;
private readonly IEnumerable<string> extraAssemblies;
private Lazy<AssemblySource> assemblySource;
public TapetiClassLibraryExampleSource(string classLibraryFilename, IEnumerable<string> extraAssemblies)
{
this.classLibraryFilename = classLibraryFilename;
this.extraAssemblies = extraAssemblies;
assemblySource = new Lazy<AssemblySource>(AssemblySourceFactory);
}
public void Dispose()
{
if (assemblySource.IsValueCreated)
assemblySource.Value.Dispose();
GC.SuppressFinalize(this);
}
public IExampleFolder GetRootFolder()
{
return assemblySource.Value.RootFolder;
}
private AssemblySource AssemblySourceFactory()
{
var runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");
var paths = runtimeAssemblies
.Concat(extraAssemblies)
.Append(classLibraryFilename);
// TODO can we use a custom resolver to detect missing references?
var resolver = new PathAssemblyResolver(paths);
var loadContext = new MetadataLoadContext(resolver);
try
{
var assembly = loadContext.LoadFromAssemblyPath(classLibraryFilename);
var rootFolder = new Folder(@"Root");
foreach (var assemblyType in assembly.GetTypes())
AddType(assemblyType, rootFolder);
return new AssemblySource
{
LoadContext = loadContext,
RootFolder = rootFolder
};
}
catch
{
loadContext.Dispose();
throw;
}
}
private void AddType(Type type, Folder rootFolder)
{
if (!type.IsClass)
return;
var assemblyName = type.Assembly.GetName().Name + ".";
var typeNamespace = type.Namespace ?? "";
if (typeNamespace.StartsWith(assemblyName))
typeNamespace = typeNamespace.Substring(assemblyName.Length);
var folder = CreateFolder(rootFolder, typeNamespace);
folder.AddMessage(new Message(type));
}
private static Folder CreateFolder(Folder rootFolder, string typeNamespace)
{
var parts = typeNamespace.Split('.');
if (parts.Length == 0)
return rootFolder;
var folder = rootFolder;
foreach (var part in parts)
folder = folder.CreateFolder(part);
return folder;
}
private class Folder : IExampleFolder
{
private readonly List<Folder> folders = new();
private readonly List<IExampleMessage> messages = new();
public string Name { get; }
public IReadOnlyList<IExampleFolder> Folders => folders;
public IReadOnlyList<IExampleMessage> Messages => messages;
public Folder(string name)
{
Name = name;
}
public Folder CreateFolder(string name)
{
var folder = folders.FirstOrDefault(f => f.Name == name);
if (folder != null)
return folder;
folder = new Folder(name);
folders.Add(folder);
return folder;
}
public void AddMessage(IExampleMessage message)
{
messages.Add(message);
}
}
private class Message : IExampleMessage
{
private readonly Type type;
public Message(Type type)
{
this.type = type;
}
public string Generate()
{
/*
We can't create an instance of the type to serialize easily, as most will depend on
assemblies not included in the NuGet package, so we'll parse the Type ourselves.
This is still much easier than using MetadataReader, as we can more easily check against
standard types like Nullable.
The only external dependencies should be the attributes, like [RequiredGuid]. The messaging models
themselves should not inherit from classes outside of their assembly, or include properties
with types from other assemblies. With that assumption, walking the class structure should be safe.
The extraAssemblies passed to TapetiClassLibraryExampleSource can also be used to give it a better chance.
*/
var serialized = TypeToJObjectConverter.Convert(type);
return serialized.ToString(Formatting.Indented);
}
}
private class AssemblySource : IDisposable
{
public MetadataLoadContext LoadContext { get; init; }
public IExampleFolder RootFolder { get; init; }
public void Dispose()
{
LoadContext.Dispose();
}
}
}
}

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq;
namespace PettingZoo.Tapeti
{
internal class TypeToJObjectConverter
{
public static JObject Convert(Type type)
{
var result = new JObject();
foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
// Note: unfortunately we can not call GetCustomAttributes here, as that would
// trigger assemblies not included in the package to be loaded
var value = PropertyToJToken(propertyInfo.PropertyType);
result.Add(propertyInfo.Name, value);
}
return result;
}
private static readonly Dictionary<Type, JToken> TypeMap = new()
{
{ typeof(short), 0 },
{ typeof(ushort), 0 },
{ typeof(int), 0 },
{ typeof(uint), 0 },
{ typeof(long), 0 },
{ typeof(ulong), 0 },
{ typeof(decimal), 0.0 },
{ typeof(float), 0.0 },
{ typeof(bool), false }
};
private static JToken PropertyToJToken(Type propertyType)
{
var actualType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
// String is also a class
if (actualType == typeof(string))
return "";
if (actualType.IsClass)
{
// IEnumerable<T>
var enumerableInterface = actualType.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (enumerableInterface != null)
return new JArray(Convert(enumerableInterface.GetGenericArguments()[0]));
return Convert(actualType);
}
if (actualType.IsArray)
return new JArray(Convert(actualType.GetElementType()));
if (actualType.IsEnum)
return Enum.GetNames(actualType).FirstOrDefault();
// Special cases for runtime generated values
if (actualType == typeof(DateTime))
{
// Strip the milliseconds for a cleaner result
var now = DateTime.UtcNow;
return new DateTime(now.Ticks - now.Ticks % TimeSpan.TicksPerSecond, now.Kind);
}
if (actualType == typeof(Guid))
return Guid.NewGuid().ToString();
return TypeMap.TryGetValue(actualType, out var mappedToken)
? mappedToken
: $"(unknown type: {actualType.Name})";
}
}
}

View File

@ -1,188 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{24819D09-C747-4356-B686-D9DE9CAA6F59}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PettingZoo</RootNamespace>
<AssemblyName>PettingZoo</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject>
</StartupObject>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>PettingZoo.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="AutoMapper, Version=4.2.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<HintPath>packages\AutoMapper.4.2.1\lib\net45\AutoMapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RabbitMQ.Client, Version=3.6.2.0, Culture=neutral, PublicKeyToken=89e7d7c5feba84ce, processorArchitecture=MSIL">
<HintPath>packages\RabbitMQ.Client.3.6.2\lib\net45\RabbitMQ.Client.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SimpleInjector, Version=3.1.5.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<HintPath>packages\SimpleInjector.3.1.5\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="Icons.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Style.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="View\ConnectionWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="View\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Infrastructure\DelegateCommand.cs" />
<Compile Include="Infrastructure\BaseViewModel.cs" />
<Compile Include="Infrastructure\GridLayout.cs" />
<Compile Include="Infrastructure\ListBoxAutoScroll.cs" />
<Compile Include="Infrastructure\PasswordBoxAssistant.cs" />
<Compile Include="Model\UserSettings.cs" />
<Compile Include="Model\IConnection.cs" />
<Compile Include="Model\IConnectionFactory.cs" />
<Compile Include="Model\IConnectionInfoBuilder.cs" />
<Compile Include="Model\ConnectionInfo.cs" />
<Compile Include="Model\MessageBodyRenderer.cs" />
<Compile Include="Model\MessageInfo.cs" />
<Compile Include="Model\RabbitMQClientConnection.cs" />
<Compile Include="Model\RabbitMQClientConnectionFactory.cs" />
<Compile Include="Model\RabbitMQProperties.cs" />
<Compile Include="ViewModel\ConnectionViewModel.cs" />
<Compile Include="ViewModel\MainViewModel.cs" />
<Compile Include="View\ConnectionWindow.xaml.cs">
<DependentUpon>ConnectionWindow.xaml</DependentUpon>
</Compile>
<Compile Include="View\MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" />
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Resource Include="PettingZoo.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -1,9 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.40629.0
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo", "PettingZoo.csproj", "{24819D09-C747-4356-B686-D9DE9CAA6F59}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo", "PettingZoo\PettingZoo.csproj", "{24819D09-C747-4356-B686-D9DE9CAA6F59}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A64E3FB8-7606-4A05-BF10-D83FD0E80D2D}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.Core", "PettingZoo.Core\PettingZoo.Core.csproj", "{AD20CA14-6272-4C50-819D-F9FE6A963DB1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.RabbitMQ", "PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj", "{220149F3-A8D6-44ED-B3B6-DFE506EB018A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.Tapeti", "PettingZoo.Tapeti\PettingZoo.Tapeti.csproj", "{1763AB04-59D9-4663-B207-D6302FFAACD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo.Settings.LiteDB", "PettingZoo.Settings.LiteDB\PettingZoo.Settings.LiteDB.csproj", "{7157B09C-FDD9-4928-B14D-C25B784CA865}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -15,8 +28,27 @@ Global
{24819D09-C747-4356-B686-D9DE9CAA6F59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24819D09-C747-4356-B686-D9DE9CAA6F59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24819D09-C747-4356-B686-D9DE9CAA6F59}.Release|Any CPU.Build.0 = Release|Any CPU
{AD20CA14-6272-4C50-819D-F9FE6A963DB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD20CA14-6272-4C50-819D-F9FE6A963DB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD20CA14-6272-4C50-819D-F9FE6A963DB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD20CA14-6272-4C50-819D-F9FE6A963DB1}.Release|Any CPU.Build.0 = Release|Any CPU
{220149F3-A8D6-44ED-B3B6-DFE506EB018A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{220149F3-A8D6-44ED-B3B6-DFE506EB018A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{220149F3-A8D6-44ED-B3B6-DFE506EB018A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{220149F3-A8D6-44ED-B3B6-DFE506EB018A}.Release|Any CPU.Build.0 = Release|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.Build.0 = Release|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07CE270E-9E57-49CD-8D5C-79C7B7A98517}
EndGlobalSection
EndGlobal

View File

@ -1,4 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=PettingZoo_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DB/@EntryIndexedValue">DB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DBUI/@EntryIndexedValue">DBUI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MQ/@EntryIndexedValue">MQ</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WPF/@EntryIndexedValue">WPF</s:String></wpf:ResourceDictionary>

View File

@ -1,12 +1,12 @@
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="PettingZoo.App"
Startup="ApplicationStartup">
ShutdownMode="OnMainWindowClose"
DispatcherUnhandledException="App_OnDispatcherUnhandledException">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Style.xaml"/>
<ResourceDictionary Source="Icons.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>

86
PettingZoo/App.xaml.cs Normal file
View File

@ -0,0 +1,86 @@
using System;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using PettingZoo.Core.Settings;
using PettingZoo.UI.Main;
using SimpleInjector;
using Point = System.Windows.Point;
namespace PettingZoo
{
public partial class App
{
private readonly Container container;
public App()
{
throw new InvalidOperationException("Default main should not be used");
}
public App(Container container)
{
this.container = container;
}
protected override async void OnStartup(StartupEventArgs e)
{
var uitSettingsRepository = container.GetInstance<IUISettingsRepository>();
var position = await uitSettingsRepository.GetMainWindowPosition();
var mainWindow = container.GetInstance<MainWindow>();
if (position != null)
{
var positionBounds = new Rect(
new Point(position.Left, position.Top),
new Point(position.Left + position.Width, position.Top + position.Height));
if (InScreenBounds(positionBounds))
{
mainWindow.WindowStartupLocation = WindowStartupLocation.Manual;
mainWindow.Top = positionBounds.Top;
mainWindow.Left = positionBounds.Left;
mainWindow.Width = positionBounds.Width;
mainWindow.Height = positionBounds.Height;
}
mainWindow.WindowState = position.Maximized ? WindowState.Maximized : WindowState.Normal;
}
mainWindow.Closing += (_, _) =>
{
var newPosition = new MainWindowPositionSettings(
(int)mainWindow.RestoreBounds.Top,
(int)mainWindow.RestoreBounds.Left,
(int)mainWindow.RestoreBounds.Width,
(int)mainWindow.RestoreBounds.Height,
mainWindow.WasMaximized);
Task.Run(() => uitSettingsRepository.StoreMainWindowPosition(newPosition));
};
mainWindow.Show();
}
private static bool InScreenBounds(Rect bounds)
{
var boundsRectangle = new Rectangle((int)bounds.Left, (int)bounds.Top, (int)bounds.Width, (int)bounds.Height);
// There doesn't appear to be any way to get this information other than from System.Windows.From/PInvoke at the time of writing
return System.Windows.Forms.Screen.AllScreens.Any(screen => screen.Bounds.IntersectsWith(boundsRectangle));
}
private void App_OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
_ = MessageBox.Show($"Unhandled exception: {e.Exception.Message}", "Petting Zoo - Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58 58" style="enable-background:new 0 0 58 58;" xml:space="preserve">
@ -23,34 +22,4 @@
s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414L45.914,46z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 59 59" style="enable-background:new 0 0 59 59;" xml:space="preserve">
@ -52,34 +51,4 @@
c0.045,0,0.091-0.003,0.137-0.009c0.276-0.039,0.524-0.19,0.684-0.419l6.214-8.929C54.136,42.118,54.024,41.495,53.571,41.179z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 22 26"
xml:space="preserve"
sodipodi:docname="Working.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs61">
</defs><sodipodi:namedview
id="namedview59"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="14.344828"
inkscape:cx="-7.0060096"
inkscape:cy="1.6382212"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<g
id="g24"
transform="translate(-36,-30)">
<path
style="fill:#d0e8f9"
d="m 48,43 h -2 v 0 c -3.633,-1.453 -6,-4.861 -6,-8.64 v -3.36 h 14 v 3.36 c 0,3.779 -2.367,7.187 -6,8.64 z"
id="path14" />
<path
style="fill:#d0e8f9"
d="m 46,43 h 2 v 0 c 3.633,1.453 6,4.861 6,8.64 v 3.36 h -14 v -3.36 c 0,-3.779 2.367,-7.187 6,-8.64 z"
id="path16" />
<g
id="g22">
<path
style="fill:#556080"
d="m 37,32 h 1 2 17 c 0.553,0 1,-0.448 1,-1 0,-0.552 -0.447,-1 -1,-1 h -17 -2 -1 c -0.553,0 -1,0.448 -1,1 0,0.552 0.447,1 1,1 z"
id="path18" />
<path
style="fill:#556080"
d="m 56.999,53.998 h -18 c -0.003,0 -0.006,0.002 -0.01,0.002 h -1.989 c -0.553,0 -1,0.448 -1,1 0,0.552 0.447,1 1,1 h 3 c 0.003,0 0.006,-0.002 0.01,-0.002 h 16.989 c 0.553,0 1,-0.448 1,-1 0,-0.552 -0.447,-1 -1,-1 z"
id="path20" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 59 59" style="enable-background:new 0 0 59 59;" xml:space="preserve">
@ -53,34 +52,4 @@
s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414L48.414,47z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58 58" style="enable-background:new 0 0 58 58;" xml:space="preserve">
<g>
<g>
<rect y="16" style="fill:#ECF0F1;" width="50" height="35"/>
<rect y="2" style="fill:#546A79;" width="50" height="14"/>
<circle style="fill:#ED7161;" cx="7" cy="9" r="3"/>
<circle style="fill:#EFC41A;" cx="16" cy="9" r="3"/>
<circle style="fill:#4FBA6E;" cx="25" cy="9" r="3"/>
</g>
<g>
<rect x="36" y="34" style="fill:#48A0DC;" width="22" height="22"/>
<rect x="46" y="40" style="fill:#FFFFFF;" width="2" height="16"/>
<polygon style="fill:#FFFFFF;" points="52.293,46.707 47,41.414 41.707,46.707 40.293,45.293 47,38.586 53.707,45.293 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 938 B

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 24 24"
xml:space="preserve"
sodipodi:docname="Error.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs130">
</defs><sodipodi:namedview
id="namedview128"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="14.344828"
inkscape:cx="-14.395433"
inkscape:cy="-2.9627404"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<g
id="g95"
transform="translate(-34,-32)">
<circle
style="fill:#ed7161"
cx="46"
cy="44"
r="12"
id="circle89" /><path
style="fill:#ffffff"
d="m 47.414,44 3.536,-3.536 c 0.391,-0.391 0.391,-1.023 0,-1.414 -0.391,-0.391 -1.023,-0.391 -1.414,0 l -3.536,3.536 -3.536,-3.536 c -0.391,-0.391 -1.023,-0.391 -1.414,0 -0.391,0.391 -0.391,1.023 0,1.414 l 3.536,3.536 -3.536,3.536 c -0.391,0.391 -0.391,1.023 0,1.414 0.195,0.195 0.451,0.293 0.707,0.293 0.256,0 0.512,-0.098 0.707,-0.293 l 3.536,-3.536 3.536,3.536 c 0.195,0.195 0.451,0.293 0.707,0.293 0.256,0 0.512,-0.098 0.707,-0.293 0.391,-0.391 0.391,-1.023 0,-1.414 z"
id="path91" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

50
PettingZoo/Images/Ok.svg Normal file
View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 24 24"
xml:space="preserve"
sodipodi:docname="Ok.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs13" /><sodipodi:namedview
id="namedview11"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="7.1724138"
inkscape:cx="-11.362981"
inkscape:cy="7.1802885"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<g
id="g8"
transform="translate(-34,-32)">
<g
id="g6">
<circle
style="fill:#26b999"
cx="46"
cy="44"
r="12"
id="circle2" />
<path
style="fill:#ffffff"
d="m 52.571,38.179 c -0.455,-0.316 -1.077,-0.204 -1.392,0.25 l -5.596,8.04 -3.949,-3.242 c -0.426,-0.351 -1.057,-0.288 -1.407,0.139 -0.351,0.427 -0.289,1.057 0.139,1.407 l 4.786,3.929 c 0.18,0.147 0.404,0.227 0.634,0.227 0.045,0 0.091,-0.003 0.137,-0.009 0.276,-0.039 0.524,-0.19 0.684,-0.419 l 6.214,-8.929 c 0.315,-0.454 0.203,-1.077 -0.25,-1.393 z"
id="path4" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58.707 58.707" style="enable-background:new 0 0 58.707 58.707;" xml:space="preserve">
<g>
<g>
<polygon style="fill:#EFEBDE;" points="46.072,14 32.072,0 1.072,0 1.072,58 46.072,58 "/>
<g>
<path style="fill:#D5D0BB;" d="M11.072,23h25c0.552,0,1-0.447,1-1s-0.448-1-1-1h-25c-0.552,0-1,0.447-1,1S10.52,23,11.072,23z"/>
<path style="fill:#D5D0BB;" d="M11.072,15h10c0.552,0,1-0.447,1-1s-0.448-1-1-1h-10c-0.552,0-1,0.447-1,1S10.52,15,11.072,15z"/>
<path style="fill:#D5D0BB;" d="M36.072,29h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S36.624,29,36.072,29z"/>
<path style="fill:#D5D0BB;" d="M36.072,37h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S36.624,37,36.072,37z"/>
<path style="fill:#D5D0BB;" d="M36.072,45h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S36.624,45,36.072,45z"/>
</g>
<polygon style="fill:#D5D0BB;" points="32.072,0 32.072,14 46.072,14 "/>
</g>
<g>
<polygon style="fill:#EDDCC7;" points="36.201,49.214 36.194,49.222 34.205,56.511 38.852,51.865 "/>
<path style="fill:#D75A4A;" d="M55.451,35.266l-1.247-1.247c-0.775-0.775-2.032-0.775-2.807,0L47.815,37.6l2.651,2.651
L55.451,35.266z"/>
<rect x="41.459" y="36.521" transform="matrix(0.7071 0.7071 -0.7071 0.7071 44.3228 -17.5395)" style="fill:#F29C21;" width="3.749" height="16.424"/>
<polygon style="fill:#D6C4B1;" points="41.85,54.879 41.858,54.871 38.852,51.865 34.205,56.511 34.072,57 "/>
<path style="fill:#A34740;" d="M53.472,43.257l3.582-3.582c0.775-0.775,0.775-2.032,0-2.807l-1.602-1.602l-4.985,4.985
L53.472,43.257z"/>
<rect x="44.036" y="39.349" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 45.1717 113.8333)" style="fill:#E18C25;" width="4.251" height="16.424"/>
<path style="fill:#5E5E5E;" d="M33.365,58.707c-0.256,0-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414l2.207-2.207
c0.391-0.391,1.023-0.391,1.414,0s0.391,1.023,0,1.414l-2.207,2.207C33.877,58.609,33.621,58.707,33.365,58.707z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58 58" style="enable-background:new 0 0 58 58;" xml:space="preserve">
<g>
<g>
<polygon style="fill:#DCD6CD;" points="0,5.5 0,44.5 28,44.5 56,44.5 56,5.5 "/>
<path style="fill:#E8E3D9;" d="M30.965,27.607c-1.637,1.462-4.292,1.462-5.93,0l-2.087-1.843C16.419,31.591,0,44.5,0,44.5h21.607
h12.787H56c0,0-16.419-12.909-22.948-18.736L30.965,27.607z"/>
<path style="fill:#EFEBDE;" d="M0,5.5l25.035,22.107c1.637,1.462,4.292,1.462,5.93,0L56,5.5H0z"/>
</g>
<g>
<rect x="36" y="30.5" style="fill:#48A0DC;" width="22" height="22"/>
<rect x="46" y="36.5" style="fill:#FFFFFF;" width="2" height="16"/>
<polygon style="fill:#FFFFFF;" points="52.293,43.207 47,37.914 41.707,43.207 40.293,41.793 47,35.086 53.707,41.793 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58.195 58.195" style="enable-background:new 0 0 58.195 58.195;" xml:space="preserve">
<g>
<g>
<polygon style="fill:#EFEBDE;" points="45,14.097 31,0.097 0,0.097 0,58.097 45,58.097 "/>
<g>
<path style="fill:#D5D0BB;" d="M10,23.097h25c0.552,0,1-0.447,1-1s-0.448-1-1-1H10c-0.552,0-1,0.447-1,1S9.448,23.097,10,23.097z
"/>
<path style="fill:#D5D0BB;" d="M10,15.097h10c0.552,0,1-0.447,1-1s-0.448-1-1-1H10c-0.552,0-1,0.447-1,1S9.448,15.097,10,15.097z
"/>
<path style="fill:#D5D0BB;" d="M35,29.097H10c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S35.552,29.097,35,29.097z
"/>
<path style="fill:#D5D0BB;" d="M35,37.097H10c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S35.552,37.097,35,37.097z
"/>
<path style="fill:#D5D0BB;" d="M35,45.097H10c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S35.552,45.097,35,45.097z
"/>
</g>
<polygon style="fill:#D5D0BB;" points="31,0.097 31,14.097 45,14.097 "/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M57,48.289l-0.107,0.163c-7.121,10.876-18.773,10.876-25.893,0l0,0l0.107-0.163
C38.227,37.412,49.879,37.412,57,48.289L57,48.289z"/>
<circle style="fill:#556080;" cx="43.764" cy="46.007" r="5.909"/>
<path style="fill:#8697CB;" d="M43.947,57.609c-5.254,0-10.148-3.058-13.783-8.609l-0.358-0.547l0.465-0.711
c3.635-5.552,8.53-8.609,13.784-8.609c5.253,0,10.148,3.057,13.783,8.609l0.358,0.547l-0.465,0.711
C54.095,54.552,49.2,57.609,43.947,57.609z M32.203,48.448c3.206,4.624,7.356,7.161,11.744,7.161c4.436,0,8.63-2.594,11.85-7.317
c-3.206-4.624-7.356-7.161-11.743-7.161C39.617,41.132,35.423,43.725,32.203,48.448z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58 58" style="enable-background:new 0 0 58 58;" xml:space="preserve">
<g>
<g>
<rect y="16" style="fill:#ECF0F1;" width="50" height="35"/>
<rect y="2" style="fill:#546A79;" width="50" height="14"/>
<circle style="fill:#ED7161;" cx="7" cy="9" r="3"/>
<circle style="fill:#EFC41A;" cx="16" cy="9" r="3"/>
<circle style="fill:#4FBA6E;" cx="25" cy="9" r="3"/>
</g>
<g>
<rect x="36" y="34" style="fill:#21AE5E;" width="22" height="22"/>
<rect x="46" y="35.586" style="fill:#FFFFFF;" width="2" height="16"/>
<polygon style="fill:#FFFFFF;" points="47,53 40,46.293 41.476,44.879 47,50.172 52.524,44.879 54,46.293 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 930 B

View File

@ -0,0 +1,190 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Version>0.1</Version>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<Authors>Mark van Renswoude</Authors>
<Product>Petting Zoo</Product>
<Description>Petting Zoo - a live RabbitMQ message viewer</Description>
<Copyright />
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<StartupObject>PettingZoo.Program</StartupObject>
<PackageIcon></PackageIcon>
<PackageIconUrl />
<ApplicationIcon>PettingZoo.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<None Remove="Images\Connecting.svg" />
<None Remove="Images\Dock.svg" />
<None Remove="Images\Error.svg" />
<None Remove="Images\Ok.svg" />
<None Remove="Images\PublishSend.svg" />
<None Remove="Images\Undock.svg" />
</ItemGroup>
<ItemGroup>
<Resource Include="Images\Clear.svg" />
<Resource Include="Images\Connect.svg" />
<Resource Include="Images\Disconnect.svg" />
<Resource Include="Images\Dock.svg" />
<Resource Include="Images\Error.svg" />
<Resource Include="Images\Ok.svg" />
<Resource Include="Images\Publish.svg" />
<Resource Include="Images\PublishSend.svg" />
<Resource Include="Images\Subscribe.svg" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SharpVectors" Version="1.7.7" />
<PackageReference Include="SimpleInjector" Version="5.3.2" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PettingZoo.Core\PettingZoo.Core.csproj" />
<ProjectReference Include="..\PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj" />
<ProjectReference Include="..\PettingZoo.Settings.LiteDB\PettingZoo.Settings.LiteDB.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Images\Undock.svg" />
<Resource Include="Images\Connecting.svg" />
</ItemGroup>
<ItemGroup>
<Compile Update="UI\Connection\ConnectionDisplayNameStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ConnectionDisplayNameStrings.resx</DependentUpon>
</Compile>
<Compile Update="UI\Connection\ConnectionWindowStrings.Designer.cs">
<DependentUpon>ConnectionWindowStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Example\ExamplePickerDialogStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ExamplePickerDialogStrings.resx</DependentUpon>
</Compile>
<Compile Update="UI\Main\MainWindowStrings.Designer.cs">
<DependentUpon>MainWindowStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Subscribe\SubscribeWindowStrings.Designer.cs">
<DependentUpon>SubscribeWindowStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Tab\Publisher\PayloadEditorStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>PayloadEditorStrings.resx</DependentUpon>
</Compile>
<Compile Update="UI\Tab\Publisher\TapetiPublisherViewStrings.Designer.cs">
<DependentUpon>TapetiPublisherViewStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Tab\Publisher\TapetiPublisherView.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="UI\Tab\Publisher\RawPublisherViewStrings.Designer.cs">
<DependentUpon>RawPublisherViewStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Tab\Publisher\PublisherViewStrings.Designer.cs">
<DependentUpon>PublisherViewStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Tab\Subscriber\SubscriberViewStrings.Designer.cs">
<DependentUpon>SubscriberViewStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Tab\Undocked\UndockedTabHostStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>UndockedTabHostStrings.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="UI\Connection\ConnectionDisplayNameStrings.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>ConnectionDisplayNameStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="UI\Connection\ConnectionWindowStrings.resx">
<LastGenOutput>ConnectionWindowStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Example\ExamplePickerDialogStrings.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>ExamplePickerDialogStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="UI\Main\MainWindowStrings.resx">
<LastGenOutput>MainWindowStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Subscribe\SubscribeWindowStrings.resx">
<LastGenOutput>SubscribeWindowStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Publisher\PayloadEditorStrings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>PayloadEditorStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Publisher\TapetiPublisherViewStrings.resx">
<LastGenOutput>TapetiPublisherViewStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Publisher\RawPublisherViewStrings.resx">
<LastGenOutput>RawPublisherViewStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Publisher\PublisherViewStrings.resx">
<LastGenOutput>PublisherViewStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Subscriber\SubscriberViewStrings.resx">
<LastGenOutput>SubscriberViewStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Undocked\UndockedTabHostStrings.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>UndockedTabHostStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Page Update="UI\Connection\ConnectionWindow.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="UI\Main\MainWindow.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="UI\Subscribe\SubscribeWindow.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="UI\Tab\Publisher\PublisherView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="UI\Tab\Publisher\TapetiPublisherView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="UI\Tab\Subscriber\SubscriberView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
</ItemGroup>
</Project>

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

78
PettingZoo/Program.cs Normal file
View File

@ -0,0 +1,78 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using PettingZoo.Core.Connection;
using PettingZoo.Core.Settings;
using PettingZoo.RabbitMQ;
using PettingZoo.Settings.LiteDB;
using PettingZoo.UI.Connection;
using PettingZoo.UI.Main;
using PettingZoo.UI.Subscribe;
using SimpleInjector;
namespace PettingZoo
{
public static class Program
{
[STAThread]
public static void Main()
{
// WPF defaults to US for date formatting in bindings, this fixes it
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
var container = Bootstrap();
RunApplication(container);
}
private static Container Bootstrap()
{
var container = new Container();
// See comments in RunApplication
container.Options.EnableAutoVerification = false;
container.Register<IConnectionFactory, RabbitMQClientConnectionFactory>();
container.Register<IConnectionDialog, WindowConnectionDialog>();
container.Register<ISubscribeDialog, WindowSubscribeDialog>();
container.Register<IConnectionSettingsRepository, LiteDBConnectionSettingsRepository>();
container.Register<IUISettingsRepository, LiteDBUISettingsRepository>();
container.Register<MainWindow>();
return container;
}
private static void RunApplication(Container container)
{
try
{
var app = new App(container);
app.InitializeComponent();
#if DEBUG
// Verify container after initialization to prevent issues loading the resource dictionaries
container.Verify();
// This causes the MainWindow and Windows properties to be populated however, which we don't want
// because then the app does not close properly when using OnMainWindowClose, so clean up the mess
app.MainWindow = null;
foreach (var window in app.Windows)
((Window)window).Close();
// All this is the reason we only perform verification in debug builds
#endif
app.Run();
}
catch (Exception e)
{
MessageBox.Show($"Fatal exception: {e.Message}", @"PettingZoo", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}

View File

@ -1,6 +1,6 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:infrastructure="clr-namespace:PettingZoo.Infrastructure">
xmlns:ui="clr-namespace:PettingZoo.UI">
<!-- Global styling -->
<Style x:Key="WindowStyle" TargetType="{x:Type Window}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
@ -10,6 +10,11 @@
<Setter Property="Padding" Value="8,4"/>
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="3" />
</Style>
<!-- Explicit styling -->
<Style x:Key="SidePanel" TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1"/>
@ -28,9 +33,13 @@
<Style x:Key="FooterButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="8,0,0,0" />
</Style>
<Style x:Key="Form" TargetType="{x:Type infrastructure:GridLayout}">
<Style x:Key="FooterButtonLeft" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="0,0,8,0" />
</Style>
<Style x:Key="Form" TargetType="{x:Type ui:GridLayout}">
<Setter Property="ChildMargin" Value="4"/>
</Style>
@ -59,6 +68,25 @@
</Style.Triggers>
</Style>
<Style x:Key="Routingkey" TargetType="{x:Type TextBlock}">
<Style x:Key="RoutingKey">
</Style>
<Style x:Key="TypeSelection" TargetType="{x:Type ToggleButton}">
<Setter Property="Margin" Value="0 0 8 0" />
<Setter Property="Padding" Value="8 4 8 4" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style x:Key="SectionLabel" TargetType="{x:Type Label}">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style x:Key="Payload" TargetType="{x:Type TextBox}">
<Setter Property="AcceptsReturn" Value="True" />
<Setter Property="AcceptsTab" Value="True" />
<Setter Property="VerticalScrollBarVisibility" Value="Visible" />
<Setter Property="FontFamily" Value="Consolas,Courier New" />
</Style>
</ResourceDictionary>

13
PettingZoo/TODO.md Normal file
View File

@ -0,0 +1,13 @@
Must-have
---------
Should-have
-----------
- Save / load publisher messages (either as templates or to disk)
- Export received messages to Tapeti JSON file / Tapeti.Cmd command-line
Nice-to-have
------------
- JSON syntax highlighting

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace PettingZoo.UI
{
public class BaseViewModel : INotifyPropertyChanged
{
private int commandsChangedDisabled;
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, IEqualityComparer<T>? comparer = null, [CallerMemberName] string? propertyName = null,
DelegateCommand[]? delegateCommandsChanged = null,
string[]? otherPropertiesChanged = null)
{
if ((comparer ?? EqualityComparer<T>.Default).Equals(field, value))
return false;
field = value;
RaisePropertyChanged(propertyName);
if (otherPropertiesChanged != null)
{
foreach (var otherProperty in otherPropertiesChanged)
RaisePropertyChanged(otherProperty);
}
// ReSharper disable once InvertIf
if (delegateCommandsChanged != null)
{
foreach (var delegateCommand in delegateCommandsChanged)
delegateCommand.RaiseCanExecuteChanged();
}
return true;
}
protected void DisableCommandsChanged(Action updateFields, params DelegateCommand[] delegateCommandsChangedAfter)
{
commandsChangedDisabled++;
try
{
updateFields();
}
finally
{
commandsChangedDisabled--;
if (commandsChangedDisabled == 0)
{
foreach (var delegateCommand in delegateCommandsChangedAfter)
delegateCommand.RaiseCanExecuteChanged();
}
}
}
}
}

View File

@ -0,0 +1,24 @@
using System.Windows;
namespace PettingZoo.UI
{
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get => GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}

View File

@ -0,0 +1,25 @@
<Window x:Class="PettingZoo.UI.Connection.ConnectionDisplayNameDialog"
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:local="clr-namespace:PettingZoo.UI.Connection"
mc:Ignorable="d"
Width="400"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStyle="ToolWindow"
WindowStartupLocation="CenterOwner"
Style="{StaticResource WindowStyle}"
Title="{x:Static local:ConnectionDisplayNameStrings.WindowTitle}"
FocusManager.FocusedElement="{Binding ElementName=DisplayNameTextBox}"
d:DataContext="{d:DesignInstance local:ConnectionDisplayNameViewModel}">
<StackPanel Margin="8">
<TextBox Name="DisplayNameTextBox" Text="{Binding DisplayName, UpdateSourceTrigger=PropertyChanged}" />
<UniformGrid HorizontalAlignment="Right" Rows="1" Columns="2" Style="{StaticResource FooterPanel}">
<Button IsDefault="True" Content="{x:Static local:ConnectionDisplayNameStrings.ButtonOK}" Style="{StaticResource FooterButton}" Command="{Binding OkCommand}"/>
<Button IsCancel="True" Content="{x:Static local:ConnectionDisplayNameStrings.ButtonCancel}" Style="{StaticResource FooterButton}"/>
</UniformGrid>
</StackPanel>
</Window>

View File

@ -0,0 +1,49 @@
using System.Linq;
using System.Windows;
namespace PettingZoo.UI.Connection
{
/// <summary>
/// Interaction logic for ConnectionDisplayNameDialog.xaml
/// </summary>
public partial class ConnectionDisplayNameDialog
{
public static bool Execute(ref string displayName)
{
var viewModel = new ConnectionDisplayNameViewModel
{
DisplayName = displayName
};
var activeWindow = Application.Current.Windows
.Cast<Window>()
.FirstOrDefault(applicationWindow => applicationWindow.IsActive);
var window = new ConnectionDisplayNameDialog(viewModel)
{
Owner = activeWindow ?? Application.Current.MainWindow
};
if (!window.ShowDialog().GetValueOrDefault())
return false;
displayName = viewModel.DisplayName;
return true;
}
public ConnectionDisplayNameDialog(ConnectionDisplayNameViewModel viewModel)
{
viewModel.OkClick += (_, _) =>
{
DialogResult = true;
};
DataContext = viewModel;
InitializeComponent();
DisplayNameTextBox.CaretIndex = DisplayNameTextBox.Text.Length;
}
}
}

View File

@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
// <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 PettingZoo.UI.Connection {
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", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ConnectionDisplayNameStrings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ConnectionDisplayNameStrings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Connection.ConnectionDisplayNameStrings", typeof(ConnectionDisplayNameStrings).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)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Cancel.
/// </summary>
public static string ButtonCancel {
get {
return ResourceManager.GetString("ButtonCancel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OK.
/// </summary>
public static string ButtonOK {
get {
return ResourceManager.GetString("ButtonOK", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Profile name.
/// </summary>
public static string WindowTitle {
get {
return ResourceManager.GetString("WindowTitle", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,129 @@
<?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>
<data name="ButtonCancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="ButtonOK" xml:space="preserve">
<value>OK</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>Profile name</value>
</data>
</root>

View File

@ -0,0 +1,41 @@
using System;
using System.Windows.Input;
namespace PettingZoo.UI.Connection
{
public class ConnectionDisplayNameViewModel : BaseViewModel
{
private string displayName = "";
private readonly DelegateCommand okCommand;
public string DisplayName
{
get => displayName;
set => SetField(ref displayName, value, delegateCommandsChanged: new [] { okCommand });
}
public ICommand OkCommand => okCommand;
public event EventHandler? OkClick;
public ConnectionDisplayNameViewModel()
{
okCommand = new DelegateCommand(OkExecute, OkCanExecute);
}
private void OkExecute()
{
OkClick?.Invoke(this, EventArgs.Empty);
}
private bool OkCanExecute()
{
return !string.IsNullOrWhiteSpace(DisplayName);
}
}
}

View File

@ -0,0 +1,321 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using PettingZoo.Core.Settings;
namespace PettingZoo.UI.Connection
{
public class ConnectionViewModel : BaseViewModel
{
private readonly IConnectionSettingsRepository connectionSettingsRepository;
private readonly StoredConnectionSettings defaultSettings;
private string host = null!;
private string virtualHost = null!;
private int port;
private string username = null!;
private string password = null!;
private bool subscribe;
private string exchange = null!;
private string routingKey = null!;
private bool storePassword;
private StoredConnectionSettings? selectedStoredConnection;
private readonly DelegateCommand okCommand;
private readonly DelegateCommand saveCommand;
private readonly DelegateCommand saveAsCommand;
private readonly DelegateCommand deleteCommand;
private readonly DelegateCommand[] connectionChangedCommands;
public string Host
{
get => host;
set => SetField(ref host, value, delegateCommandsChanged: connectionChangedCommands);
}
public string VirtualHost
{
get => virtualHost;
set => SetField(ref virtualHost, value, delegateCommandsChanged: connectionChangedCommands);
}
public int Port
{
get => port;
set => SetField(ref port, value, delegateCommandsChanged: connectionChangedCommands);
}
public string Username
{
get => username;
set => SetField(ref username, value, delegateCommandsChanged: connectionChangedCommands);
}
public string Password
{
get => password;
set => SetField(ref password, value, delegateCommandsChanged: connectionChangedCommands);
}
public bool Subscribe
{
get => subscribe;
set => SetField(ref subscribe, value, delegateCommandsChanged: connectionChangedCommands);
}
public string Exchange
{
get => exchange;
set
{
if (SetField(ref exchange, value, delegateCommandsChanged: connectionChangedCommands))
AutoToggleSubscribe();
}
}
public string RoutingKey
{
get => routingKey;
set
{
if (SetField(ref routingKey, value, delegateCommandsChanged: connectionChangedCommands))
AutoToggleSubscribe();
}
}
public bool StorePassword
{
get => storePassword;
set => SetField(ref storePassword, value, delegateCommandsChanged: connectionChangedCommands);
}
public ObservableCollection<StoredConnectionSettings> StoredConnections { get; } = new();
public StoredConnectionSettings? SelectedStoredConnection
{
get => selectedStoredConnection;
set
{
if (value == null)
return;
if (!SetField(ref selectedStoredConnection, value, delegateCommandsChanged: new [] { deleteCommand }))
return;
DisableCommandsChanged(
() =>
{
Host = value.Host;
VirtualHost = value.VirtualHost;
Port = value.Port;
Username = value.Username;
Password = value.Password;
StorePassword = value.StorePassword;
Exchange = value.Exchange;
RoutingKey = value.RoutingKey;
Subscribe = value.Subscribe;
},
connectionChangedCommands);
}
}
public ICommand OkCommand => okCommand;
public ICommand SaveCommand => saveCommand;
public ICommand SaveAsCommand => saveAsCommand;
public ICommand DeleteCommand => deleteCommand;
public event EventHandler? OkClick;
public ConnectionViewModel(IConnectionSettingsRepository connectionSettingsRepository, StoredConnectionSettings defaultSettings)
{
this.connectionSettingsRepository = connectionSettingsRepository;
this.defaultSettings = defaultSettings;
okCommand = new DelegateCommand(OkExecute, OkCanExecute);
saveCommand = new DelegateCommand(SaveExecute, SaveCanExecute);
saveAsCommand = new DelegateCommand(SaveAsExecute, SaveAsCanExecute);
deleteCommand = new DelegateCommand(DeleteExecute, DeleteCanExecute);
connectionChangedCommands = new[] { saveCommand, saveAsCommand, okCommand };
}
public async Task Initialize()
{
var defaultConnection = new StoredConnectionSettings(
Guid.Empty,
ConnectionWindowStrings.LastUsedDisplayName,
defaultSettings.StorePassword,
defaultSettings.Host,
defaultSettings.VirtualHost,
defaultSettings.Port,
defaultSettings.Username,
defaultSettings.Password,
defaultSettings.Subscribe,
defaultSettings.Exchange,
defaultSettings.RoutingKey);
var isStored = false;
foreach (var storedConnectionSettings in await connectionSettingsRepository.GetStored())
{
if (!isStored && storedConnectionSettings.SameParameters(defaultConnection, false))
{
SelectedStoredConnection = storedConnectionSettings;
isStored = true;
}
StoredConnections.Add(storedConnectionSettings);
}
if (isStored)
{
// The last used parameters match a stored connection, insert the "New connection" item with default parameters
StoredConnections.Insert(0, new StoredConnectionSettings(Guid.Empty, ConnectionWindowStrings.LastUsedDisplayName, true, ConnectionSettings.Default));
}
else
{
// No match, use the passed parameters
StoredConnections.Insert(0, defaultConnection);
SelectedStoredConnection = defaultConnection;
}
}
public ConnectionSettings ToModel()
{
return new ConnectionSettings(Host, VirtualHost, Port, Username, Password, Subscribe, Exchange, RoutingKey);
}
private bool ValidConnection(bool requirePassword)
{
return !string.IsNullOrWhiteSpace(Host) &&
!string.IsNullOrWhiteSpace(VirtualHost) &&
Port > 0 &&
!string.IsNullOrWhiteSpace(Username) &&
(!requirePassword || !string.IsNullOrWhiteSpace(Password)) &&
(!Subscribe || (
!string.IsNullOrWhiteSpace(Exchange) &&
!string.IsNullOrWhiteSpace(RoutingKey)
));
}
private void AutoToggleSubscribe()
{
Subscribe = !string.IsNullOrWhiteSpace(Exchange) && !string.IsNullOrWhiteSpace(RoutingKey);
}
private void OkExecute()
{
OkClick?.Invoke(this, EventArgs.Empty);
}
private bool OkCanExecute()
{
return ValidConnection(true);
}
private async void SaveExecute()
{
if (SelectedStoredConnection == null || SelectedStoredConnection.Id == Guid.Empty)
return;
var selectedIndex = StoredConnections.IndexOf(SelectedStoredConnection);
var updatedStoredConnection = await connectionSettingsRepository.Update(SelectedStoredConnection.Id, SelectedStoredConnection.DisplayName, StorePassword, ToModel());
StoredConnections[selectedIndex] = updatedStoredConnection;
SelectedStoredConnection = updatedStoredConnection;
}
private bool SaveCanExecute()
{
return SelectedStoredConnection != null &&
SelectedStoredConnection.Id != Guid.Empty &&
ValidConnection(false) &&
(
!ToModel().SameParameters(SelectedStoredConnection, StorePassword) ||
SelectedStoredConnection.StorePassword != StorePassword
);
}
private async void SaveAsExecute()
{
// TODO create and enforce unique name?
var displayName = SelectedStoredConnection != null && SelectedStoredConnection.Id != Guid.Empty ? SelectedStoredConnection.DisplayName : "";
if (!ConnectionDisplayNameDialog.Execute(ref displayName))
return;
var storedConnectionSettings = await connectionSettingsRepository.Add(displayName, StorePassword, ToModel());
StoredConnections.Add(storedConnectionSettings);
SelectedStoredConnection = storedConnectionSettings;
}
private bool SaveAsCanExecute()
{
return ValidConnection(false);
}
private async void DeleteExecute()
{
if (SelectedStoredConnection == null || SelectedStoredConnection.Id == Guid.Empty)
return;
var selectedIndex = StoredConnections.IndexOf(SelectedStoredConnection);
if (MessageBox.Show(
string.Format(ConnectionWindowStrings.DeleteConfirm, SelectedStoredConnection.DisplayName),
ConnectionWindowStrings.DeleteConfirmTitle,
MessageBoxButton.YesNo,
MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
await connectionSettingsRepository.Delete(SelectedStoredConnection.Id);
StoredConnections.Remove(SelectedStoredConnection);
if (selectedIndex >= StoredConnections.Count)
selectedIndex--;
SelectedStoredConnection = StoredConnections[selectedIndex];
}
private bool DeleteCanExecute()
{
return SelectedStoredConnection != null && SelectedStoredConnection.Id != Guid.Empty;
}
}
public class DesignTimeConnectionViewModel : ConnectionViewModel
{
public DesignTimeConnectionViewModel() : base(null!, null!)
{
StoredConnections.Add(new StoredConnectionSettings(Guid.Empty, "Dummy", true, ConnectionSettings.Default));
}
}
}

View File

@ -0,0 +1,104 @@
<Window x:Class="PettingZoo.UI.Connection.ConnectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:PettingZoo.UI"
xmlns:connection="clr-namespace:PettingZoo.UI.Connection"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance connection:DesignTimeConnectionViewModel, IsDesignTimeCreatable = True}"
Width="700"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStyle="ToolWindow"
Style="{StaticResource WindowStyle}"
Title="{x:Static connection:ConnectionWindowStrings.WindowTitle}"
FocusManager.FocusedElement="{Binding ElementName=HostTextBox}"
WindowStartupLocation="CenterOwner">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<UniformGrid Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" Rows="1" Columns="1" Style="{StaticResource FooterPanel}">
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonDelete}" Style="{StaticResource FooterButtonLeft}" Command="{Binding DeleteCommand}"/>
</UniformGrid>
<UniformGrid Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Rows="1" Columns="3" Style="{StaticResource FooterPanel}">
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonSave}" Style="{StaticResource FooterButtonLeft}" Command="{Binding SaveCommand}"/>
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonSaveAs}" Style="{StaticResource FooterButtonLeft}" Command="{Binding SaveAsCommand}"/>
</UniformGrid>
<UniformGrid Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" Rows="1" Columns="2" Style="{StaticResource FooterPanel}">
<Button IsDefault="True" Content="{x:Static connection:ConnectionWindowStrings.ButtonOK}" Style="{StaticResource FooterButton}" Command="{Binding OkCommand}"/>
<Button IsCancel="True" Content="{x:Static connection:ConnectionWindowStrings.ButtonCancel}" Style="{StaticResource FooterButton}"/>
</UniformGrid>
<ListBox Grid.Row="0" Grid.Column="0" Margin="0 0 8 0" Width="250" ItemsSource="{Binding StoredConnections}" SelectedValue="{Binding SelectedStoredConnection}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.OkCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ui:GridLayout Style="{StaticResource Form}" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="24"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static connection:ConnectionWindowStrings.LabelHost}"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Host, UpdateSourceTrigger=PropertyChanged}" Name="HostTextBox" GotFocus="CaretToEnd" />
<Label Grid.Column="0" Grid.Row="1" Content="{x:Static connection:ConnectionWindowStrings.LabelPort}"/>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}" Width="100" HorizontalAlignment="Left" PreviewTextInput="NumericPreviewTextInput" GotFocus="CaretToEnd" />
<Label Grid.Column="0" Grid.Row="2" Content="{x:Static connection:ConnectionWindowStrings.LabelVirtualHost}"/>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding VirtualHost, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
<Label Grid.Column="0" Grid.Row="3" Content="{x:Static connection:ConnectionWindowStrings.LabelUsername}"/>
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
<Label Grid.Column="0" Grid.Row="4" Content="{x:Static connection:ConnectionWindowStrings.LabelPassword}"/>
<PasswordBox Grid.Column="1" Grid.Row="4" ui:PasswordBoxAssistant.BindPassword="true" ui:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
<CheckBox Grid.Column="1" Grid.Row="5" Content="{x:Static connection:ConnectionWindowStrings.LabelStorePassword}" IsChecked="{Binding StorePassword}"/>
<CheckBox Grid.Column="1" Grid.Row="7" Content="{x:Static connection:ConnectionWindowStrings.LabelSubscribe}" IsChecked="{Binding Subscribe}"/>
<Label Grid.Column="0" Grid.Row="8" Content="{x:Static connection:ConnectionWindowStrings.LabelExchange}"/>
<TextBox Grid.Column="1" Grid.Row="8" Text="{Binding Exchange, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
<Label Grid.Column="0" Grid.Row="9" Content="{x:Static connection:ConnectionWindowStrings.LabelRoutingKey}"/>
<TextBox Grid.Column="1" Grid.Row="9" Text="{Binding RoutingKey, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
</ui:GridLayout>
</Grid>
</Window>

View File

@ -0,0 +1,72 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using PettingZoo.Core.Settings;
namespace PettingZoo.UI.Connection
{
public class WindowConnectionDialog : IConnectionDialog
{
private readonly IConnectionSettingsRepository connectionSettingsRepository;
public WindowConnectionDialog(IConnectionSettingsRepository connectionSettingsRepository)
{
this.connectionSettingsRepository = connectionSettingsRepository;
}
public async Task<ConnectionSettings?> Show()
{
var lastUsed = await connectionSettingsRepository.GetLastUsed();
var viewModel = new ConnectionViewModel(connectionSettingsRepository, lastUsed);
await viewModel.Initialize();
var window = new ConnectionWindow(viewModel)
{
Owner = Application.Current.MainWindow
};
viewModel.OkClick += (_, _) =>
{
window.DialogResult = true;
};
if (!window.ShowDialog().GetValueOrDefault())
return null;
var newSettings = viewModel.ToModel();
await connectionSettingsRepository.StoreLastUsed(viewModel.StorePassword, newSettings);
return newSettings;
}
}
public partial class ConnectionWindow
{
public ConnectionWindow(ConnectionViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
private void NumericPreviewTextInput(object sender, TextCompositionEventArgs args)
{
if (!char.IsDigit(args.Text, args.Text.Length - 1))
args.Handled = true;
}
private void CaretToEnd(object sender, RoutedEventArgs e)
{
if (sender is not TextBox textBox)
return;
textBox.CaretIndex = textBox.Text.Length;
}
}
}

View File

@ -0,0 +1,225 @@
//------------------------------------------------------------------------------
// <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 PettingZoo.UI.Connection {
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", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ConnectionWindowStrings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ConnectionWindowStrings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Connection.ConnectionWindowStrings", typeof(ConnectionWindowStrings).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)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Cancel.
/// </summary>
public static string ButtonCancel {
get {
return ResourceManager.GetString("ButtonCancel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete.
/// </summary>
public static string ButtonDelete {
get {
return ResourceManager.GetString("ButtonDelete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connect.
/// </summary>
public static string ButtonOK {
get {
return ResourceManager.GetString("ButtonOK", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save.
/// </summary>
public static string ButtonSave {
get {
return ResourceManager.GetString("ButtonSave", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save as....
/// </summary>
public static string ButtonSaveAs {
get {
return ResourceManager.GetString("ButtonSaveAs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Do you want to delete the connection settings &quot;{0}&quot;?.
/// </summary>
public static string DeleteConfirm {
get {
return ResourceManager.GetString("DeleteConfirm", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete connection.
/// </summary>
public static string DeleteConfirmTitle {
get {
return ResourceManager.GetString("DeleteConfirmTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exchange.
/// </summary>
public static string LabelExchange {
get {
return ResourceManager.GetString("LabelExchange", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Host.
/// </summary>
public static string LabelHost {
get {
return ResourceManager.GetString("LabelHost", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password.
/// </summary>
public static string LabelPassword {
get {
return ResourceManager.GetString("LabelPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port.
/// </summary>
public static string LabelPort {
get {
return ResourceManager.GetString("LabelPort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Routing key.
/// </summary>
public static string LabelRoutingKey {
get {
return ResourceManager.GetString("LabelRoutingKey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Store password.
/// </summary>
public static string LabelStorePassword {
get {
return ResourceManager.GetString("LabelStorePassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Subscribe.
/// </summary>
public static string LabelSubscribe {
get {
return ResourceManager.GetString("LabelSubscribe", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Username.
/// </summary>
public static string LabelUsername {
get {
return ResourceManager.GetString("LabelUsername", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Virtual host.
/// </summary>
public static string LabelVirtualHost {
get {
return ResourceManager.GetString("LabelVirtualHost", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &lt;New connection&gt;.
/// </summary>
public static string LastUsedDisplayName {
get {
return ResourceManager.GetString("LastUsedDisplayName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connection parameters.
/// </summary>
public static string WindowTitle {
get {
return ResourceManager.GetString("WindowTitle", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,174 @@
<?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>
<data name="ButtonCancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="ButtonDelete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="ButtonOK" xml:space="preserve">
<value>Connect</value>
</data>
<data name="ButtonSave" xml:space="preserve">
<value>Save</value>
</data>
<data name="ButtonSaveAs" xml:space="preserve">
<value>Save as...</value>
</data>
<data name="DeleteConfirm" xml:space="preserve">
<value>Do you want to delete the connection settings "{0}"?</value>
</data>
<data name="DeleteConfirmTitle" xml:space="preserve">
<value>Delete connection</value>
</data>
<data name="LabelExchange" xml:space="preserve">
<value>Exchange</value>
</data>
<data name="LabelHost" xml:space="preserve">
<value>Host</value>
</data>
<data name="LabelPassword" xml:space="preserve">
<value>Password</value>
</data>
<data name="LabelPort" xml:space="preserve">
<value>Port</value>
</data>
<data name="LabelRoutingKey" xml:space="preserve">
<value>Routing key</value>
</data>
<data name="LabelStorePassword" xml:space="preserve">
<value>Store password</value>
</data>
<data name="LabelSubscribe" xml:space="preserve">
<value>Subscribe</value>
</data>
<data name="LabelUsername" xml:space="preserve">
<value>Username</value>
</data>
<data name="LabelVirtualHost" xml:space="preserve">
<value>Virtual host</value>
</data>
<data name="LastUsedDisplayName" xml:space="preserve">
<value>&lt;New connection&gt;</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>Connection parameters</value>
</data>
</root>

View File

@ -0,0 +1,11 @@
using System;
using System.Threading.Tasks;
using PettingZoo.Core.Settings;
namespace PettingZoo.UI.Connection
{
public interface IConnectionDialog
{
Task<ConnectionSettings?> Show();
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.Windows.Input;
namespace PettingZoo.UI
{
public class DelegateCommand<T> : ICommand where T : class?
{
private readonly Func<T?, bool>? canExecute;
private readonly Action<T?> execute;
public event EventHandler? CanExecuteChanged;
public DelegateCommand(Action<T?> execute) : this(execute, null)
{
}
public DelegateCommand(Action<T?> execute, Func<T?, bool>? canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object? parameter)
{
return canExecute == null || canExecute((T?)parameter);
}
public void Execute(object? parameter)
{
execute((T?)parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
public class DelegateCommand : ICommand
{
private readonly Func<bool>? canExecute;
private readonly Action execute;
public event EventHandler? CanExecuteChanged;
public DelegateCommand(Action execute) : this(execute, null) { }
public DelegateCommand(Action execute, Func<bool>? canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object? parameter)
{
return canExecute == null || canExecute();
}
public void Execute(object? parameter)
{
execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
namespace PettingZoo.UI
{
public static class DependencyObjectExtensions
{
public static IObservable<T> OnPropertyChanges<T>(this DependencyObject source, DependencyProperty property)
{
return Observable.Create<T>(o =>
{
var dpd = DependencyPropertyDescriptor.FromProperty(property, property.OwnerType);
if (dpd == null)
o.OnError(new InvalidOperationException("Can not register change handler for this dependency property."));
void Handler(object? sender, EventArgs e)
{
o.OnNext((T)source.GetValue(property));
}
dpd?.AddValueChanged(source, Handler);
return Disposable.Create(() => dpd?.RemoveValueChanged(source, Handler));
});
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace PettingZoo.UI
{
public class EnumBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value) ? parameter : Binding.DoNothing;
}
}
}

View File

@ -0,0 +1,12 @@
<Window x:Class="PettingZoo.UI.Example.ExamplePickerDialog"
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:example="clr-namespace:PettingZoo.UI.Example"
mc:Ignorable="d"
Title="{x:Static example:ExamplePickerDialogStrings.WindowTitle}" Height="800" Width="600">
<Grid>
</Grid>
</Window>

View File

@ -0,0 +1,15 @@
using System.Windows;
namespace PettingZoo.UI.Example
{
/// <summary>
/// Interaction logic for ExamplePickerDialog.xaml
/// </summary>
public partial class ExamplePickerDialog : Window
{
public ExamplePickerDialog()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
// <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 PettingZoo.UI.Example {
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", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ExamplePickerDialogStrings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ExamplePickerDialogStrings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Example.ExamplePickerDialogStrings", typeof(ExamplePickerDialogStrings).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)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Select example.
/// </summary>
public static string WindowTitle {
get {
return ResourceManager.GetString("WindowTitle", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,123 @@
<?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>
<data name="WindowTitle" xml:space="preserve">
<value>Select example</value>
</data>
</root>

View File

@ -0,0 +1,6 @@
namespace PettingZoo.UI.Example
{
public class ExamplePickerDialogViewModel
{
}
}

View File

@ -1,7 +1,7 @@
using System.Windows;
using System.Windows.Controls;
namespace PettingZoo.Infrastructure
namespace PettingZoo.UI
{
// Source: http://daniel-albuschat.blogspot.nl/2011/07/gridlayout-for-wpf-escape-margin-hell.html
@ -32,7 +32,7 @@ namespace PettingZoo.Infrastructure
public Thickness ChildMargin
{
get { return (Thickness) GetValue(ChildMarginProperty); }
get => (Thickness) GetValue(ChildMarginProperty);
set
{
SetValue(ChildMarginProperty, value);
@ -45,12 +45,13 @@ namespace PettingZoo.Infrastructure
public void UpdateChildMargins()
{
int maxColumn = 0;
int maxRow = 0;
var maxColumn = 0;
var maxRow = 0;
foreach (UIElement element in InternalChildren)
{
int row = GetRow(element);
int column = GetColumn(element);
var row = GetRow(element);
var column = GetColumn(element);
if (row > maxRow)
maxRow = row;
if (column > maxColumn)
@ -58,32 +59,31 @@ namespace PettingZoo.Infrastructure
}
foreach (UIElement element in InternalChildren)
{
var fe = element as FrameworkElement;
if (null != fe)
{
int row = GetRow(fe);
int column = GetColumn(fe);
double factorLeft = 0.5;
double factorTop = 0.5;
double factorRight = 0.5;
double factorBottom = 0.5;
// Top row - no top margin
if (row == 0)
factorTop = 0;
// Bottom row - no bottom margin
if (row == maxRow)
factorBottom = 0;
// Leftmost column = no left margin
if (column == 0)
factorLeft = 0;
// Rightmost column - no right margin
if (column == maxColumn)
factorRight = 0;
fe.Margin = new Thickness(ChildMargin.Left*factorLeft,
ChildMargin.Top*factorTop,
ChildMargin.Right*factorRight,
ChildMargin.Bottom*factorBottom);
}
if (element is not FrameworkElement fe)
continue;
var row = GetRow(fe);
var column = GetColumn(fe);
var factorLeft = 0.5;
var factorTop = 0.5;
var factorRight = 0.5;
var factorBottom = 0.5;
// Top row - no top margin
if (row == 0)
factorTop = 0;
// Bottom row - no bottom margin
if (row == maxRow)
factorBottom = 0;
// Leftmost column = no left margin
if (column == 0)
factorLeft = 0;
// Rightmost column - no right margin
if (column == maxColumn)
factorRight = 0;
fe.Margin = new Thickness(ChildMargin.Left*factorLeft,
ChildMargin.Top*factorTop,
ChildMargin.Right*factorRight,
ChildMargin.Bottom*factorBottom);
}
}

View File

@ -2,11 +2,11 @@
using System.Collections;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Data;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace PettingZoo.Infrastructure
namespace PettingZoo.UI
{
// Source: https://social.msdn.microsoft.com/Forums/vstudio/en-US/0f524459-b14e-4f9a-8264-267953418a2d/trivial-listboxlistview-autoscroll?forum=wpf
//
@ -29,7 +29,7 @@ namespace PettingZoo.Infrastructure
public static void SetAutoScroll(System.Windows.Controls.ListBox instance, bool value)
{
var oldHandler = (AutoScrollHandler) instance.GetValue(AutoScrollHandlerProperty);
var oldHandler = (AutoScrollHandler?)instance.GetValue(AutoScrollHandlerProperty);
if (oldHandler != null)
{
oldHandler.Dispose();
@ -51,7 +51,7 @@ namespace PettingZoo.Infrastructure
ItemsSourcePropertyChanged));
private readonly System.Windows.Controls.ListBox target;
private ScrollViewer scrollViewer;
private ScrollViewer? scrollViewer;
public AutoScrollHandler(System.Windows.Controls.ListBox target)
{
@ -74,8 +74,8 @@ namespace PettingZoo.Infrastructure
public IEnumerable ItemsSource
{
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
get => (IEnumerable) GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
@ -87,16 +87,14 @@ namespace PettingZoo.Infrastructure
private void ItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
var collection = oldValue as INotifyCollectionChanged;
if (collection != null)
collection.CollectionChanged -= Collection_CollectionChanged;
if (oldValue is INotifyCollectionChanged oldCollection)
oldCollection.CollectionChanged -= Collection_CollectionChanged;
collection = newValue as INotifyCollectionChanged;
if (collection != null)
collection.CollectionChanged += Collection_CollectionChanged;
if (newValue is INotifyCollectionChanged newCollection)
newCollection.CollectionChanged += Collection_CollectionChanged;
}
private void Collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
private void Collection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null || e.NewItems.Count < 1)
return;
@ -109,24 +107,31 @@ namespace PettingZoo.Infrastructure
return;
}
if (Math.Abs(scrollViewer.VerticalOffset - scrollViewer.ScrollableHeight) < 1)
target.ScrollIntoView(e.NewItems[e.NewItems.Count - 1]);
if (e.NewItems.Count == 0)
return;
// If not already at the bottom, keep the position stable
if (Math.Abs(scrollViewer.VerticalOffset - scrollViewer.ScrollableHeight) > 0)
return;
var item = e.NewItems[^1];
if (item != null)
target.ScrollIntoView(item);
}
private static ScrollViewer FindScrollViewer(DependencyObject parent)
private static ScrollViewer? FindScrollViewer(DependencyObject parent)
{
var childCount = VisualTreeHelper.GetChildrenCount(parent);
for (var childIndex = 0; childIndex < childCount; childIndex++)
{
var child = VisualTreeHelper.GetChild(parent, childIndex);
var scrollViewer = (child as ScrollViewer);
if (scrollViewer != null)
if (child is ScrollViewer scrollViewer)
return scrollViewer;
scrollViewer = FindScrollViewer(child);
if (scrollViewer != null)
return scrollViewer;
var childScrollViewer = FindScrollViewer(child);
if (childScrollViewer != null)
return childScrollViewer;
}
return null;

View File

@ -0,0 +1,8 @@
namespace PettingZoo.UI.Main
{
public interface ITabContainer
{
public double TabWidth { get; }
public double TabHeight { get; }
}
}

View File

@ -0,0 +1,124 @@
<Window x:Class="PettingZoo.UI.Main.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:main="clr-namespace:PettingZoo.UI.Main"
xmlns:tab="clr-namespace:PettingZoo.UI.Tab"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:ui="clr-namespace:PettingZoo.UI"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance main:DesignTimeMainWindowViewModel, IsDesignTimeCreatable=True}"
Width="800"
Height="800"
ResizeMode="CanResizeWithGrip"
Style="{StaticResource WindowStyle}"
Title="{x:Static main:MainWindowStrings.WindowTitle}"
Loaded="MainWindow_OnLoaded"
Closed="MainWindow_OnClosed">
<Window.InputBindings>
<KeyBinding Modifiers="Control" Key="W" Command="{Binding CloseTabCommand}" />
<KeyBinding Modifiers="Control" Key="U" Command="{Binding UndockTabCommand}" />
</Window.InputBindings>
<Window.Resources>
<ui:BindingProxy x:Key="ContextMenuProxy" Data="{Binding}" />
</Window.Resources>
<DockPanel>
<ToolBar DockPanel.Dock="Top" ToolBarTray.IsLocked="True" Loaded="Toolbar_Loaded">
<Button Command="{Binding ConnectCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="{svgc:SvgImage Source=/Images/Connect.svg, AppName=PettingZoo}" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0" Text="{x:Static main:MainWindowStrings.CommandConnect}" />
</StackPanel>
</Button>
<Button Command="{Binding DisconnectCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="{svgc:SvgImage Source=/Images/Disconnect.svg, AppName=PettingZoo}" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0" Text="{x:Static main:MainWindowStrings.CommandDisconnect}" />
</StackPanel>
</Button>
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" />
<Button Command="{Binding SubscribeCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="{svgc:SvgImage Source=/Images/Subscribe.svg, AppName=PettingZoo}" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0" Text="{x:Static main:MainWindowStrings.CommandSubscribe}" />
</StackPanel>
</Button>
<Button Command="{Binding PublishCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="{svgc:SvgImage Source=/Images/Publish.svg, AppName=PettingZoo}" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0" Text="{x:Static main:MainWindowStrings.CommandPublish}" />
</StackPanel>
</Button>
<Button Command="{Binding UndockTabCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="{svgc:SvgImage Source=/Images/Undock.svg, AppName=PettingZoo}" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0" Text="{x:Static main:MainWindowStrings.CommandUndock}" />
</StackPanel>
</Button>
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" Visibility="{Binding ToolbarCommandsSeparatorVisibility}" />
<ItemsControl ItemsSource="{Binding ToolbarCommands}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Command}" Style="{DynamicResource {x:Static ToolBar.ButtonStyleKey}}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0" Text="{Binding Caption}" />
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToolBar>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<StackPanel Orientation="Horizontal">
<Image Source="{svgc:SvgImage Source=/Images/Connecting.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ConnectionStatusConnecting}" />
<Image Source="{svgc:SvgImage Source=/Images/Ok.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ConnectionStatusOk}" />
<Image Source="{svgc:SvgImage Source=/Images/Error.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ConnectionStatusError}" />
<TextBlock Text="{Binding ConnectionStatus}" VerticalAlignment="Center"/>
</StackPanel>
</StatusBarItem>
</StatusBar>
<Grid>
<TabControl
Name="SubscriberTabs"
ItemsSource="{Binding Tabs}"
SelectedValue="{Binding ActiveTab}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<!-- ReSharper disable once Xaml.BindingWithContextNotResolved - valid property for ITab, not sure how to specify the DataContext here so just ignore the warning for now -->
<Setter Property="Header" Value="{Binding Title}" />
<Setter Property="AllowDrop" Value="True" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<!-- ReSharper disable Xaml.BindingWithContextNotResolved - binding is correct, just weird because of the required proxy -->
<MenuItem Header="{x:Static main:MainWindowStrings.ContextMenuUndockTab}" Command="{Binding Data.UndockTabCommand, Source={StaticResource ContextMenuProxy}}" InputGestureText="Ctrl+U" />
<MenuItem Header="{x:Static main:MainWindowStrings.ContextMenuCloseTab}" Command="{Binding Data.CloseTabCommand, Source={StaticResource ContextMenuProxy}}" InputGestureText="Ctrl+W" />
<!-- ReSharper restore Xaml.BindingWithContextNotResolved -->
</ContextMenu>
</Setter.Value>
</Setter>
<EventSetter Event="PreviewMouseRightButtonDown" Handler="TabItem_PreviewRightMouseDown" />
<EventSetter Event="PreviewMouseMove" Handler="TabItem_PreviewMouseMove" />
<EventSetter Event="Drop" Handler="TabItem_Drop" />
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type tab:ITab}">
<ContentControl Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<TextBlock Text="{x:Static main:MainWindowStrings.TabsEmptyText}" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding NoTabsVisibility}" />
</Grid>
</DockPanel>
</Window>

View File

@ -0,0 +1,147 @@
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using PettingZoo.Core.Connection;
using PettingZoo.UI.Connection;
using PettingZoo.UI.Subscribe;
using PettingZoo.UI.Tab;
namespace PettingZoo.UI.Main
{
#pragma warning disable CA1001 // MainWindow can't be IDisposable, handled instead in OnDispatcherShutDownStarted
public partial class MainWindow : ITabContainer
{
private readonly MainWindowViewModel viewModel;
public bool WasMaximized;
public MainWindow(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog, ISubscribeDialog subscribeDialog)
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog, this);
DataContext = viewModel;
InitializeComponent();
Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted;
// If the WindowState is Minimized, we can't tell if it was maximized before. To properly store
// the last window position, keep track of it.
this.OnPropertyChanges<WindowState>(WindowStateProperty)
.Subscribe(newState =>
{
WasMaximized = newState switch
{
WindowState.Maximized => true,
WindowState.Normal => false,
_ => WasMaximized
};
});
}
private async void OnDispatcherShutDownStarted(object? sender, EventArgs e)
{
if (DataContext is IAsyncDisposable disposable)
await disposable.DisposeAsync();
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
viewModel.ConnectCommand.Execute(null);
}
private void MainWindow_OnClosed(object? sender, EventArgs e)
{
var _ = Application.Current.Windows;
}
private void TabItem_PreviewRightMouseDown(object sender, MouseButtonEventArgs e)
{
var tabItem = GetParent<TabItem>(e.OriginalSource);
if (tabItem == null)
return;
var tabControl = GetParent<TabControl>(tabItem);
if (tabControl == null)
return;
tabControl.SelectedItem = tabItem.DataContext;
}
private void TabItem_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.Source is not TabItem tabItem)
return;
if (Mouse.PrimaryDevice.LeftButton == MouseButtonState.Pressed)
DragDrop.DoDragDrop(tabItem, tabItem, DragDropEffects.All);
}
private void TabItem_Drop(object sender, DragEventArgs e)
{
var targetTab = GetParent<TabItem>(e.OriginalSource);
if (targetTab == null)
return;
var sourceTab = (TabItem?)e.Data.GetData(typeof(TabItem));
if (sourceTab == null || sourceTab == targetTab)
return;
var tabControl = GetParent<TabControl>(targetTab);
if (tabControl?.ItemsSource is not ObservableCollection<ITab> dataCollection)
return;
if (sourceTab.DataContext is not ITab sourceData || targetTab.DataContext is not ITab targetData)
return;
var sourceIndex = dataCollection.IndexOf(sourceData);
var targetIndex = dataCollection.IndexOf(targetData);
dataCollection.Move(sourceIndex, targetIndex);
}
private static T? GetParent<T>(object originalSource) where T : DependencyObject
{
var current = originalSource as DependencyObject;
while (current != null)
{
if (current is T targetType)
return targetType;
current = VisualTreeHelper.GetParent(current);
}
return null;
}
public double TabWidth => SubscriberTabs.ActualWidth;
public double TabHeight => SubscriberTabs.ActualHeight;
private void Toolbar_Loaded(object sender, RoutedEventArgs e)
{
// Hide arrow on the right side of the toolbar
var toolBar = sender as ToolBar;
if (toolBar?.Template.FindName("OverflowGrid", toolBar) is FrameworkElement overflowGrid)
overflowGrid.Visibility = Visibility.Collapsed;
if (toolBar?.Template.FindName("MainPanelBorder", toolBar) is FrameworkElement mainPanelBorder)
mainPanelBorder.Margin = new Thickness(0);
}
}
#pragma warning restore CA1001
}

View File

@ -8,7 +8,10 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace PettingZoo.Properties {
namespace PettingZoo.UI.Main {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@ -16,17 +19,17 @@ namespace PettingZoo.Properties {
// 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", "4.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
public class MainWindowStrings {
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() {
internal MainWindowStrings() {
}
/// <summary>
@ -36,7 +39,7 @@ namespace PettingZoo.Properties {
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.Properties.Resources", typeof(Resources).Assembly);
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Main.MainWindowStrings", typeof(MainWindowStrings).Assembly);
resourceMan = temp;
}
return resourceMan;
@ -58,92 +61,65 @@ namespace PettingZoo.Properties {
}
/// <summary>
/// Looks up a localized string similar to Cancel.
/// Looks up a localized string similar to Connect.
/// </summary>
public static string ButtonCancel {
public static string CommandConnect {
get {
return ResourceManager.GetString("ButtonCancel", resourceCulture);
return ResourceManager.GetString("CommandConnect", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OK.
/// Looks up a localized string similar to Disconnect.
/// </summary>
public static string ButtonOK {
public static string CommandDisconnect {
get {
return ResourceManager.GetString("ButtonOK", resourceCulture);
return ResourceManager.GetString("CommandDisconnect", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exchange:.
/// Looks up a localized string similar to New Publisher.
/// </summary>
public static string ConnectionExchange {
public static string CommandPublish {
get {
return ResourceManager.GetString("ConnectionExchange", resourceCulture);
return ResourceManager.GetString("CommandPublish", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Host:.
/// Looks up a localized string similar to New Subscriber....
/// </summary>
public static string ConnectionHost {
public static string CommandSubscribe {
get {
return ResourceManager.GetString("ConnectionHost", resourceCulture);
return ResourceManager.GetString("CommandSubscribe", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password:.
/// Looks up a localized string similar to Undock tab.
/// </summary>
public static string ConnectionPassword {
public static string CommandUndock {
get {
return ResourceManager.GetString("ConnectionPassword", resourceCulture);
return ResourceManager.GetString("CommandUndock", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port:.
/// Looks up a localized string similar to Close tab.
/// </summary>
public static string ConnectionPort {
public static string ContextMenuCloseTab {
get {
return ResourceManager.GetString("ConnectionPort", resourceCulture);
return ResourceManager.GetString("ContextMenuCloseTab", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Routing key:.
/// Looks up a localized string similar to Undock.
/// </summary>
public static string ConnectionRoutingKey {
public static string ContextMenuUndockTab {
get {
return ResourceManager.GetString("ConnectionRoutingKey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Username:.
/// </summary>
public static string ConnectionUsername {
get {
return ResourceManager.GetString("ConnectionUsername", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Virtual host:.
/// </summary>
public static string ConnectionVirtualHost {
get {
return ResourceManager.GetString("ConnectionVirtualHost", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connection parameters.
/// </summary>
public static string ConnectionWindowTitle {
get {
return ResourceManager.GetString("ConnectionWindowTitle", resourceCulture);
return ResourceManager.GetString("ContextMenuUndockTab", resourceCulture);
}
}
@ -165,51 +141,6 @@ namespace PettingZoo.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Petting Zoo - a RabbitMQ live message viewer.
/// </summary>
public static string MainWindowTitle {
get {
return ResourceManager.GetString("MainWindowTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Body.
/// </summary>
public static string PanelTitleBody {
get {
return ResourceManager.GetString("PanelTitleBody", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Properties.
/// </summary>
public static string PanelTitleProperties {
get {
return ResourceManager.GetString("PanelTitleProperties", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name.
/// </summary>
public static string PropertyName {
get {
return ResourceManager.GetString("PropertyName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value.
/// </summary>
public static string PropertyValue {
get {
return ResourceManager.GetString("PropertyValue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connected.
/// </summary>
@ -245,5 +176,23 @@ namespace PettingZoo.Properties {
return ResourceManager.GetString("StatusError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No open tabs. Click on New Publisher or New Subscriber to open a new tab..
/// </summary>
public static string TabsEmptyText {
get {
return ResourceManager.GetString("TabsEmptyText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Petting Zoo - a RabbitMQ live message viewer.
/// </summary>
public static string WindowTitle {
get {
return ResourceManager.GetString("WindowTitle", resourceCulture);
}
}
}
}

View File

@ -117,35 +117,26 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ButtonCancel" xml:space="preserve">
<value>Cancel</value>
<data name="CommandConnect" xml:space="preserve">
<value>Connect</value>
</data>
<data name="ButtonOK" xml:space="preserve">
<value>OK</value>
<data name="CommandDisconnect" xml:space="preserve">
<value>Disconnect</value>
</data>
<data name="ConnectionExchange" xml:space="preserve">
<value>Exchange:</value>
<data name="CommandPublish" xml:space="preserve">
<value>New Publisher</value>
</data>
<data name="ConnectionHost" xml:space="preserve">
<value>Host:</value>
<data name="CommandSubscribe" xml:space="preserve">
<value>New Subscriber...</value>
</data>
<data name="ConnectionPassword" xml:space="preserve">
<value>Password:</value>
<data name="CommandUndock" xml:space="preserve">
<value>Undock tab</value>
</data>
<data name="ConnectionPort" xml:space="preserve">
<value>Port:</value>
<data name="ContextMenuCloseTab" xml:space="preserve">
<value>Close tab</value>
</data>
<data name="ConnectionRoutingKey" xml:space="preserve">
<value>Routing key:</value>
</data>
<data name="ConnectionUsername" xml:space="preserve">
<value>Username:</value>
</data>
<data name="ConnectionVirtualHost" xml:space="preserve">
<value>Virtual host:</value>
</data>
<data name="ConnectionWindowTitle" xml:space="preserve">
<value>Connection parameters</value>
<data name="ContextMenuUndockTab" xml:space="preserve">
<value>Undock</value>
</data>
<data name="DeliveryModeNonPersistent" xml:space="preserve">
<value>Non-persistent</value>
@ -153,21 +144,6 @@
<data name="DeliveryModePersistent" xml:space="preserve">
<value>Persistent</value>
</data>
<data name="MainWindowTitle" xml:space="preserve">
<value>Petting Zoo - a RabbitMQ live message viewer</value>
</data>
<data name="PanelTitleBody" xml:space="preserve">
<value>Body</value>
</data>
<data name="PanelTitleProperties" xml:space="preserve">
<value>Properties</value>
</data>
<data name="PropertyName" xml:space="preserve">
<value>Name</value>
</data>
<data name="PropertyValue" xml:space="preserve">
<value>Value</value>
</data>
<data name="StatusConnected" xml:space="preserve">
<value>Connected</value>
</data>
@ -180,4 +156,10 @@
<data name="StatusError" xml:space="preserve">
<value>Error: {0}</value>
</data>
<data name="TabsEmptyText" xml:space="preserve">
<value>No open tabs. Click on New Publisher or New Subscriber to open a new tab.</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>Petting Zoo - a RabbitMQ live message viewer</value>
</data>
</root>

View File

@ -0,0 +1,327 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using PettingZoo.Core.Connection;
using PettingZoo.UI.Connection;
using PettingZoo.UI.Subscribe;
using PettingZoo.UI.Tab;
using PettingZoo.UI.Tab.Undocked;
namespace PettingZoo.UI.Main
{
public enum ConnectionStatusType
{
Connecting,
Ok,
Error
}
public class MainWindowViewModel : BaseViewModel, IAsyncDisposable, ITabHost
{
private readonly IConnectionFactory connectionFactory;
private readonly IConnectionDialog connectionDialog;
private readonly ISubscribeDialog subscribeDialog;
private readonly ITabContainer tabContainer;
private readonly ITabFactory tabFactory;
private SubscribeDialogParams? subscribeDialogParams;
private IConnection? connection;
private string connectionStatus;
private ITab? activeTab;
private readonly Dictionary<ITab, Window> undockedTabs = new();
private readonly DelegateCommand connectCommand;
private readonly DelegateCommand disconnectCommand;
private readonly DelegateCommand publishCommand;
private readonly DelegateCommand subscribeCommand;
private readonly DelegateCommand closeTabCommand;
private readonly DelegateCommand undockTabCommand;
private ConnectionStatusType connectionStatusType;
public string ConnectionStatus
{
get => connectionStatus;
private set => SetField(ref connectionStatus, value);
}
public ConnectionStatusType ConnectionStatusType
{
get => connectionStatusType;
set => SetField(ref connectionStatusType, value, otherPropertiesChanged: new [] { nameof(ConnectionStatusOk), nameof(ConnectionStatusError), nameof(ConnectionStatusConnecting) });
}
public Visibility ConnectionStatusOk => ConnectionStatusType == ConnectionStatusType.Ok ? Visibility.Visible : Visibility.Collapsed;
public Visibility ConnectionStatusError => ConnectionStatusType == ConnectionStatusType.Error ? Visibility.Visible : Visibility.Collapsed;
public Visibility ConnectionStatusConnecting => ConnectionStatusType == ConnectionStatusType.Connecting ? Visibility.Visible : Visibility.Collapsed;
public ObservableCollection<ITab> Tabs { get; }
public ITab? ActiveTab
{
get => activeTab;
set
{
var currentTab = activeTab;
if (!SetField(ref activeTab, value, otherPropertiesChanged: new[] { nameof(ToolbarCommands), nameof(ToolbarCommandsSeparatorVisibility) }))
return;
currentTab?.Deactivate();
activeTab?.Activate();
}
}
public ICommand ConnectCommand => connectCommand;
public ICommand DisconnectCommand => disconnectCommand;
public ICommand PublishCommand => publishCommand;
public ICommand SubscribeCommand => subscribeCommand;
public ICommand CloseTabCommand => closeTabCommand;
public ICommand UndockTabCommand => undockTabCommand;
public IEnumerable<TabToolbarCommand> ToolbarCommands => ActiveTab is ITabToolbarCommands tabToolbarCommands
? tabToolbarCommands.ToolbarCommands
: Enumerable.Empty<TabToolbarCommand>();
public Visibility ToolbarCommandsSeparatorVisibility =>
ToolbarCommands.Any() ? Visibility.Visible : Visibility.Collapsed;
public Visibility NoTabsVisibility =>
Tabs.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
public MainWindowViewModel(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog,
ISubscribeDialog subscribeDialog, ITabContainer tabContainer)
{
this.connectionFactory = connectionFactory;
this.connectionDialog = connectionDialog;
this.subscribeDialog = subscribeDialog;
this.tabContainer = tabContainer;
connectionStatus = GetConnectionStatus(null);
connectionStatusType = ConnectionStatusType.Error;
Tabs = new ObservableCollection<ITab>();
connectCommand = new DelegateCommand(ConnectExecute);
disconnectCommand = new DelegateCommand(DisconnectExecute, IsConnectedCanExecute);
publishCommand = new DelegateCommand(PublishExecute, IsConnectedCanExecute);
subscribeCommand = new DelegateCommand(SubscribeExecute, IsConnectedCanExecute);
closeTabCommand = new DelegateCommand(CloseTabExecute, HasActiveTabCanExecute);
undockTabCommand = new DelegateCommand(UndockTabExecute, HasActiveTabCanExecute);
tabFactory = new ViewTabFactory(this);
}
public async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
if (connection != null)
await connection.DisposeAsync();
}
private async void ConnectExecute()
{
var connectionSettings = await connectionDialog.Show();
if (connectionSettings == null)
return;
if (connection != null)
await connection.DisposeAsync();
connection = connectionFactory.CreateConnection(new ConnectionParams(
connectionSettings.Host, connectionSettings.VirtualHost, connectionSettings.Port,
connectionSettings.Username, connectionSettings.Password));
connection.StatusChanged += ConnectionStatusChanged;
if (connectionSettings.Subscribe)
{
var subscriber = connection.Subscribe(connectionSettings.Exchange, connectionSettings.RoutingKey);
AddTab(tabFactory.CreateSubscriberTab(connection, subscriber));
}
ConnectionChanged();
}
private async void DisconnectExecute()
{
Tabs.Clear();
var capturedUndockedTabs = undockedTabs.ToList();
undockedTabs.Clear();
foreach (var undockedTab in capturedUndockedTabs)
undockedTab.Value.Close();
RaisePropertyChanged(nameof(NoTabsVisibility));
undockTabCommand.RaiseCanExecuteChanged();
if (connection != null)
{
await connection.DisposeAsync();
connection = null;
}
ConnectionStatus = GetConnectionStatus(null);
ConnectionStatusType = ConnectionStatusType.Error;
ConnectionChanged();
}
private void SubscribeExecute()
{
if (connection == null)
return;
var newParams = subscribeDialog.Show(subscribeDialogParams);
if (newParams == null)
return;
subscribeDialogParams = newParams;
var subscriber = connection.Subscribe(subscribeDialogParams.Exchange, subscribeDialogParams.RoutingKey);
AddTab(tabFactory.CreateSubscriberTab(connection, subscriber));
}
private void PublishExecute()
{
if (connection == null)
return;
AddTab(tabFactory.CreatePublisherTab(connection));
}
private bool IsConnectedCanExecute()
{
return connection != null;
}
private void CloseTabExecute()
{
RemoveActiveTab();
}
private void UndockTabExecute()
{
var tab = RemoveActiveTab();
if (tab == null)
return;
var tabHostWindow = UndockedTabHostWindow.Create(this, tab, tabContainer.TabWidth, tabContainer.TabHeight);
undockedTabs.Add(tab, tabHostWindow);
tabHostWindow.Show();
}
private ITab? RemoveActiveTab()
{
if (ActiveTab == null)
return null;
var activeTabIndex = Tabs.IndexOf(ActiveTab);
if (activeTabIndex == -1)
return null;
var tab = Tabs[activeTabIndex];
Tabs.RemoveAt(activeTabIndex);
if (activeTabIndex == Tabs.Count)
activeTabIndex--;
ActiveTab = activeTabIndex >= 0 ? Tabs[activeTabIndex] : null;
closeTabCommand.RaiseCanExecuteChanged();
undockTabCommand.RaiseCanExecuteChanged();
RaisePropertyChanged(nameof(NoTabsVisibility));
return tab;
}
private bool HasActiveTabCanExecute()
{
return ActiveTab != null;
}
public void AddTab(ITab tab)
{
Tabs.Add(tab);
ActiveTab = tab;
closeTabCommand.RaiseCanExecuteChanged();
undockTabCommand.RaiseCanExecuteChanged();
RaisePropertyChanged(nameof(NoTabsVisibility));
}
public void DockTab(ITab tab)
{
if (undockedTabs.Remove(tab, out var tabHostWindow))
tabHostWindow.Close();
AddTab(tab);
}
public void UndockedTabClosed(ITab tab)
{
undockedTabs.Remove(tab);
}
private void ConnectionChanged()
{
disconnectCommand.RaiseCanExecuteChanged();
subscribeCommand.RaiseCanExecuteChanged();
publishCommand.RaiseCanExecuteChanged();
}
private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs args)
{
ConnectionStatus = GetConnectionStatus(args);
ConnectionStatusType = args.Status switch
{
Core.Connection.ConnectionStatus.Connected => ConnectionStatusType.Ok,
Core.Connection.ConnectionStatus.Connecting => ConnectionStatusType.Connecting,
_ => ConnectionStatusType.Error
};
}
private static string GetConnectionStatus(StatusChangedEventArgs? args)
{
return args?.Status switch
{
Core.Connection.ConnectionStatus.Connecting => string.Format(MainWindowStrings.StatusConnecting, args.Context),
Core.Connection.ConnectionStatus.Connected => string.Format(MainWindowStrings.StatusConnected, args.Context),
Core.Connection.ConnectionStatus.Error => string.Format(MainWindowStrings.StatusError, args.Context),
Core.Connection.ConnectionStatus.Disconnected => MainWindowStrings.StatusDisconnected,
_ => MainWindowStrings.StatusDisconnected
};
}
}
public class DesignTimeMainWindowViewModel : MainWindowViewModel
{
public DesignTimeMainWindowViewModel() : base(null!, null!, null!, null!)
{
}
}
}

View File

@ -1,7 +1,7 @@
using System.Windows;
using System.Windows.Controls;
namespace PettingZoo.Infrastructure
namespace PettingZoo.UI
{
// Source: http://blog.functionalfun.net/2008/06/wpf-passwordbox-and-data-binding.html
public static class PasswordBoxAssistant
@ -35,9 +35,7 @@ namespace PettingZoo.Infrastructure
var newPassword = (string) e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
@ -46,26 +44,19 @@ namespace PettingZoo.Infrastructure
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
var box = dp as PasswordBox;
if (box == null)
if (dp is not PasswordBox box)
{
return;
}
var wasBound = (bool) (e.OldValue);
var needToBind = (bool) (e.NewValue);
var wasBound = (bool)e.OldValue;
var needToBind = (bool)e.NewValue;
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)

View File

@ -0,0 +1,26 @@
using System;
namespace PettingZoo.UI.Subscribe
{
public interface ISubscribeDialog
{
SubscribeDialogParams? Show(SubscribeDialogParams? defaultParams = null);
}
public class SubscribeDialogParams
{
public string Exchange { get; }
public string RoutingKey { get; }
public static SubscribeDialogParams Default { get; } = new("", "#");
public SubscribeDialogParams(string exchange, string routingKey)
{
Exchange = exchange;
RoutingKey = routingKey;
}
}
}

Some files were not shown because too many files have changed in this diff Show More