diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0eeeaa4
--- /dev/null
+++ b/.editorconfig
@@ -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
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 7964536..75ac72d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
\ No newline at end of file
+bin
+obj
\ No newline at end of file
diff --git a/App.config b/App.config
deleted file mode 100644
index 8e15646..0000000
--- a/App.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Icons.xaml b/Icons.xaml
deleted file mode 100644
index 848e067..0000000
--- a/Icons.xaml
+++ /dev/null
@@ -1,483 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Images/Clear.xaml b/Images/Clear.xaml
deleted file mode 100644
index 85ad8ee..0000000
--- a/Images/Clear.xaml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Images/Connect.xaml b/Images/Connect.xaml
deleted file mode 100644
index a31ac6f..0000000
--- a/Images/Connect.xaml
+++ /dev/null
@@ -1,203 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Images/Disconnect.xaml b/Images/Disconnect.xaml
deleted file mode 100644
index 66d8822..0000000
--- a/Images/Disconnect.xaml
+++ /dev/null
@@ -1,203 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Infrastructure/BaseViewModel.cs b/Infrastructure/BaseViewModel.cs
deleted file mode 100644
index 152d71b..0000000
--- a/Infrastructure/BaseViewModel.cs
+++ /dev/null
@@ -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));
- }
- }
-}
diff --git a/Infrastructure/DelegateCommand.cs b/Infrastructure/DelegateCommand.cs
deleted file mode 100644
index f4eb492..0000000
--- a/Infrastructure/DelegateCommand.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System;
-using System.Windows.Input;
-
-namespace PettingZoo.Infrastructure
-{
- public class DelegateCommand : ICommand
- {
- private readonly Func canExecute;
- private readonly Action execute;
-
- public event EventHandler CanExecuteChanged;
-
-
- public DelegateCommand(Action execute) : this(execute, null)
- {
- }
-
- public DelegateCommand(Action execute, Func 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 canExecute;
- private readonly Action execute;
-
- public event EventHandler CanExecuteChanged;
-
-
- public DelegateCommand(Action execute) : this(execute, null) { }
-
- public DelegateCommand(Action execute, Func 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);
- }
- }
-}
diff --git a/Model/ConnectionInfo.cs b/Model/ConnectionInfo.cs
deleted file mode 100644
index 8ba3ba4..0000000
--- a/Model/ConnectionInfo.cs
+++ /dev/null
@@ -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; }
- }
-}
diff --git a/Model/IConnection.cs b/Model/IConnection.cs
deleted file mode 100644
index 394a721..0000000
--- a/Model/IConnection.cs
+++ /dev/null
@@ -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 StatusChanged;
- event EventHandler MessageReceived;
- }
-}
diff --git a/Model/IConnectionFactory.cs b/Model/IConnectionFactory.cs
deleted file mode 100644
index e1b6bd5..0000000
--- a/Model/IConnectionFactory.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace PettingZoo.Model
-{
- public interface IConnectionFactory
- {
- IConnection CreateConnection(ConnectionInfo connectionInfo);
- }
-}
diff --git a/Model/IConnectionInfoBuilder.cs b/Model/IConnectionInfoBuilder.cs
deleted file mode 100644
index bdd683c..0000000
--- a/Model/IConnectionInfoBuilder.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace PettingZoo.Model
-{
- public interface IConnectionInfoBuilder
- {
- ConnectionInfo Build();
- }
-}
diff --git a/Model/MessageInfo.cs b/Model/MessageInfo.cs
deleted file mode 100644
index 7a29779..0000000
--- a/Model/MessageInfo.cs
+++ /dev/null
@@ -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 Properties;
-
- public string ContentType
- {
- get
- {
- return Properties != null && Properties.ContainsKey(RabbitMQProperties.ContentType)
- ? Properties[RabbitMQProperties.ContentType]
- : "";
- }
- }
-
-
- public MessageInfo()
- {
- Timestamp = DateTime.Now;
- }
- }
-}
diff --git a/Model/RabbitMQClientConnection.cs b/Model/RabbitMQClientConnection.cs
deleted file mode 100644
index 0cd573d..0000000
--- a/Model/RabbitMQClientConnection.cs
+++ /dev/null
@@ -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 StatusChanged;
- public event EventHandler 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 ConvertProperties(IBasicProperties basicProperties)
- {
- var properties = new Dictionary();
-
- 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;
- }
- }
-}
diff --git a/Model/RabbitMQClientConnectionFactory.cs b/Model/RabbitMQClientConnectionFactory.cs
deleted file mode 100644
index d61aa3e..0000000
--- a/Model/RabbitMQClientConnectionFactory.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace PettingZoo.Model
-{
- public class RabbitMQClientConnectionFactory : IConnectionFactory
- {
- public IConnection CreateConnection(ConnectionInfo connectionInfo)
- {
- return new RabbitMQClientConnection(connectionInfo);
- }
- }
-}
diff --git a/PettingZoo.Core/Connection/ConnectionParams.cs b/PettingZoo.Core/Connection/ConnectionParams.cs
new file mode 100644
index 0000000..fb63f5e
--- /dev/null
+++ b/PettingZoo.Core/Connection/ConnectionParams.cs
@@ -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;
+ }
+ }
+}
diff --git a/PettingZoo.Core/Connection/IConnection.cs b/PettingZoo.Core/Connection/IConnection.cs
new file mode 100644
index 0000000..f29014c
--- /dev/null
+++ b/PettingZoo.Core/Connection/IConnection.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Threading.Tasks;
+
+namespace PettingZoo.Core.Connection
+{
+ public interface IConnection : IAsyncDisposable
+ {
+ event EventHandler StatusChanged;
+
+ ISubscriber Subscribe(string exchange, string routingKey);
+ Task Publish(MessageInfo 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;
+ }
+ }
+}
diff --git a/PettingZoo.Core/Connection/IConnectionFactory.cs b/PettingZoo.Core/Connection/IConnectionFactory.cs
new file mode 100644
index 0000000..a2f7170
--- /dev/null
+++ b/PettingZoo.Core/Connection/IConnectionFactory.cs
@@ -0,0 +1,7 @@
+namespace PettingZoo.Core.Connection
+{
+ public interface IConnectionFactory
+ {
+ IConnection CreateConnection(ConnectionParams connectionInfo);
+ }
+}
diff --git a/PettingZoo.Core/Connection/ISubscriber.cs b/PettingZoo.Core/Connection/ISubscriber.cs
new file mode 100644
index 0000000..3ec09c8
--- /dev/null
+++ b/PettingZoo.Core/Connection/ISubscriber.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace PettingZoo.Core.Connection
+{
+ public interface ISubscriber : IAsyncDisposable
+ {
+ string Exchange {get; }
+ string RoutingKey { get; }
+
+ event EventHandler? MessageReceived;
+
+ void Start();
+ }
+
+
+ public class MessageReceivedEventArgs : EventArgs
+ {
+ public MessageInfo MessageInfo { get; }
+
+
+ public MessageReceivedEventArgs(MessageInfo messageInfo)
+ {
+ MessageInfo = messageInfo;
+ }
+ }
+}
diff --git a/PettingZoo.Core/Connection/MessageInfo.cs b/PettingZoo.Core/Connection/MessageInfo.cs
new file mode 100644
index 0000000..f70abd6
--- /dev/null
+++ b/PettingZoo.Core/Connection/MessageInfo.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+
+namespace PettingZoo.Core.Connection
+{
+ public class MessageInfo
+ {
+ public DateTime Timestamp { get; }
+ public string Exchange { get; }
+ public string RoutingKey { get; }
+ public byte[] Body { get; }
+ public IDictionary Properties { get; }
+
+ public MessageInfo(string exchange, string routingKey, byte[] body, IDictionary properties, DateTime timestamp)
+ {
+ Exchange = exchange;
+ RoutingKey = routingKey;
+ Body = body;
+ Properties = properties;
+ Timestamp = timestamp;
+ }
+ }
+}
diff --git a/PettingZoo.Core/PettingZoo.Core.csproj b/PettingZoo.Core/PettingZoo.Core.csproj
new file mode 100644
index 0000000..951e5aa
--- /dev/null
+++ b/PettingZoo.Core/PettingZoo.Core.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net5.0
+ enable
+
+
+
+
+
+
+
diff --git a/Model/MessageBodyRenderer.cs b/PettingZoo.Core/Rendering/MessageBodyRenderer.cs
similarity index 73%
rename from Model/MessageBodyRenderer.cs
rename to PettingZoo.Core/Rendering/MessageBodyRenderer.cs
index 1ba2e62..acdc033 100644
--- a/Model/MessageBodyRenderer.cs
+++ b/PettingZoo.Core/Rendering/MessageBodyRenderer.cs
@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
-namespace PettingZoo.Model
+namespace PettingZoo.Core.Rendering
{
public class MessageBodyRenderer
{
- public static Dictionary> ContentTypeHandlers = new Dictionary>
+ public static Dictionary> ContentTypeHandlers = new()
{
{ "application/json", RenderJson }
};
@@ -15,13 +15,11 @@ namespace PettingZoo.Model
public static string Render(byte[] body, string contentType = "")
{
- Func handler;
-
- if (ContentTypeHandlers.TryGetValue(contentType, out handler))
- return handler(body);
+ return ContentTypeHandlers.TryGetValue(contentType, out var handler)
+ ? handler(body)
+ : Encoding.UTF8.GetString(body);
// ToDo hex output if required
- return Encoding.UTF8.GetString(body);
}
diff --git a/PettingZoo.RabbitMQ/PettingZoo.RabbitMQ.csproj b/PettingZoo.RabbitMQ/PettingZoo.RabbitMQ.csproj
new file mode 100644
index 0000000..38e4451
--- /dev/null
+++ b/PettingZoo.RabbitMQ/PettingZoo.RabbitMQ.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net5.0
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PettingZoo.RabbitMQ/RabbitMQClientConnection.cs b/PettingZoo.RabbitMQ/RabbitMQClientConnection.cs
new file mode 100644
index 0000000..9682070
--- /dev/null
+++ b/PettingZoo.RabbitMQ/RabbitMQClientConnection.cs
@@ -0,0 +1,142 @@
+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? 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;
+ }
+ }
+ }
+
+
+ public ISubscriber Subscribe(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(MessageInfo 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));
+ }
+ }
+}
diff --git a/PettingZoo.RabbitMQ/RabbitMQClientConnectionFactory.cs b/PettingZoo.RabbitMQ/RabbitMQClientConnectionFactory.cs
new file mode 100644
index 0000000..4850b3f
--- /dev/null
+++ b/PettingZoo.RabbitMQ/RabbitMQClientConnectionFactory.cs
@@ -0,0 +1,12 @@
+using PettingZoo.Core.Connection;
+
+namespace PettingZoo.RabbitMQ
+{
+ public class RabbitMQClientConnectionFactory : IConnectionFactory
+ {
+ public IConnection CreateConnection(ConnectionParams connectionParams)
+ {
+ return new RabbitMQClientConnection(connectionParams);
+ }
+ }
+}
diff --git a/PettingZoo.RabbitMQ/RabbitMQClientPropertiesConverter.cs b/PettingZoo.RabbitMQ/RabbitMQClientPropertiesConverter.cs
new file mode 100644
index 0000000..967413f
--- /dev/null
+++ b/PettingZoo.RabbitMQ/RabbitMQClientPropertiesConverter.cs
@@ -0,0 +1,138 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using RabbitMQ.Client;
+
+namespace PettingZoo.RabbitMQ
+{
+ public static class RabbitMQClientPropertiesConverter
+ {
+ public static IDictionary Convert(IBasicProperties basicProperties)
+ {
+ var properties = new Dictionary();
+
+ if (basicProperties.IsDeliveryModePresent())
+ properties.Add(RabbitMQProperties.DeliveryMode, basicProperties.DeliveryMode.ToString(CultureInfo.InvariantCulture));
+
+ 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);
+
+ // ReSharper disable once InvertIf
+ if (basicProperties.Headers != null)
+ {
+ foreach (var (key, value) in basicProperties.Headers)
+ properties.Add(key, Encoding.UTF8.GetString((byte[]) value));
+ }
+
+ return properties;
+ }
+
+
+ public static IBasicProperties Convert(IDictionary properties, IBasicProperties targetProperties)
+ {
+ foreach (var (key, value) in properties)
+ {
+ switch (key)
+ {
+ case RabbitMQProperties.DeliveryMode:
+ if (byte.TryParse(value, out var deliveryMode))
+ targetProperties.DeliveryMode = deliveryMode;
+
+ break;
+
+ case RabbitMQProperties.ContentType:
+ targetProperties.ContentType = value;
+ break;
+
+ case RabbitMQProperties.ContentEncoding:
+ targetProperties.ContentEncoding = value;
+ break;
+
+ case RabbitMQProperties.Priority:
+ if (byte.TryParse(value, out var priority))
+ targetProperties.Priority = priority;
+
+ break;
+
+ case RabbitMQProperties.CorrelationId:
+ targetProperties.CorrelationId = value;
+ break;
+
+ case RabbitMQProperties.ReplyTo:
+ targetProperties.ReplyTo = value;
+ break;
+
+ case RabbitMQProperties.Expiration:
+ targetProperties.Expiration = value;
+ break;
+
+ case RabbitMQProperties.MessageId:
+ targetProperties.MessageId = value;
+ break;
+
+ case RabbitMQProperties.Timestamp:
+ if (long.TryParse(value, out var timestamp))
+ targetProperties.Timestamp = new AmqpTimestamp(timestamp);
+
+ break;
+
+ case RabbitMQProperties.Type:
+ targetProperties.Type = value;
+ break;
+
+ case RabbitMQProperties.UserId:
+ targetProperties.UserId = value;
+ break;
+
+ case RabbitMQProperties.AppId:
+ targetProperties.AppId = value;
+ break;
+
+ case RabbitMQProperties.ClusterId:
+ targetProperties.ClusterId = value;
+ break;
+
+ default:
+ targetProperties.Headers ??= new Dictionary();
+ targetProperties.Headers.Add(key, Encoding.UTF8.GetBytes(value));
+ break;
+ }
+ }
+
+ return targetProperties;
+ }
+ }
+}
diff --git a/PettingZoo.RabbitMQ/RabbitMQClientSubscriber.cs b/PettingZoo.RabbitMQ/RabbitMQClientSubscriber.cs
new file mode 100644
index 0000000..7f7dd26
--- /dev/null
+++ b/PettingZoo.RabbitMQ/RabbitMQClientSubscriber.cs
@@ -0,0 +1,79 @@
+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 Exchange { get; }
+ public string RoutingKey { get; }
+ public event EventHandler? MessageReceived;
+
+
+ public RabbitMQClientSubscriber(IModel? model, string exchange, string routingKey)
+ {
+ this.model = model;
+ Exchange = exchange;
+ RoutingKey = routingKey;
+ }
+
+
+ public ValueTask DisposeAsync()
+ {
+ if (model != null && consumerTag != null && model.IsOpen)
+ model.BasicCancelNoWait(consumerTag);
+
+ return default;
+ }
+
+
+ public void Start()
+ {
+ started = true;
+ if (model == null)
+ return;
+
+ var queueName = model.QueueDeclare().QueueName;
+ 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 MessageInfo(
+ 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
+ )
+ ));
+ }
+
+ }
+}
diff --git a/Model/RabbitMQProperties.cs b/PettingZoo.RabbitMQ/RabbitMQProperties.cs
similarity index 90%
rename from Model/RabbitMQProperties.cs
rename to PettingZoo.RabbitMQ/RabbitMQProperties.cs
index a8378b5..5cab9d6 100644
--- a/Model/RabbitMQProperties.cs
+++ b/PettingZoo.RabbitMQ/RabbitMQProperties.cs
@@ -1,6 +1,6 @@
-namespace PettingZoo.Model
+namespace PettingZoo.RabbitMQ
{
- static class RabbitMQProperties
+ public static class RabbitMQProperties
{
public const string ContentType = "content-type";
public const string ContentEncoding = "content-encoding";
diff --git a/PettingZoo.RabbitMQ/RabbitMQPropertiesExtensions.cs b/PettingZoo.RabbitMQ/RabbitMQPropertiesExtensions.cs
new file mode 100644
index 0000000..41cd78f
--- /dev/null
+++ b/PettingZoo.RabbitMQ/RabbitMQPropertiesExtensions.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace PettingZoo.RabbitMQ
+{
+ public static class RabbitMQPropertiesExtensions
+ {
+ public static string ContentType(this IDictionary properties)
+ {
+ return properties.TryGetValue(RabbitMQProperties.ContentType, out var value)
+ ? value
+ : "";
+ }
+ }
+}
diff --git a/PettingZoo.csproj b/PettingZoo.csproj
deleted file mode 100644
index 6b2b336..0000000
--- a/PettingZoo.csproj
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {24819D09-C747-4356-B686-D9DE9CAA6F59}
- WinExe
- Properties
- PettingZoo
- PettingZoo
- v4.5
- 512
- {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 4
- publish\
- true
- Disk
- false
- Foreground
- 7
- Days
- false
- false
- true
- 0
- 1.0.0.%2a
- false
- false
- true
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- false
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
- PettingZoo.ico
-
-
-
- packages\AutoMapper.4.2.1\lib\net45\AutoMapper.dll
- True
-
-
- packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll
- True
-
-
- packages\RabbitMQ.Client.3.6.2\lib\net45\RabbitMQ.Client.dll
- True
-
-
- packages\SimpleInjector.3.1.5\lib\net45\SimpleInjector.dll
- True
-
-
-
-
-
-
-
-
-
-
- 4.0
-
-
-
-
-
-
-
- MSBuild:Compile
- Designer
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- MSBuild:Compile
- Designer
-
-
- App.xaml
- Code
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ConnectionWindow.xaml
-
-
- MainWindow.xaml
- Code
-
-
-
-
- Code
-
-
- True
- True
- Resources.resx
-
-
- PublicResXFileCodeGenerator
- Resources.Designer.cs
-
-
-
-
-
-
-
-
-
- False
- Microsoft .NET Framework 4.5 %28x86 and x64%29
- true
-
-
- False
- .NET Framework 3.5 SP1 Client Profile
- false
-
-
- False
- .NET Framework 3.5 SP1
- false
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/PettingZoo.sln b/PettingZoo.sln
index 125cd4c..642c3a1 100644
--- a/PettingZoo.sln
+++ b/PettingZoo.sln
@@ -1,9 +1,18 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.40629.0
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31911.196
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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo.Core", "PettingZoo.Core\PettingZoo.Core.csproj", "{AD20CA14-6272-4C50-819D-F9FE6A963DB1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo.RabbitMQ", "PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj", "{220149F3-A8D6-44ED-B3B6-DFE506EB018A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,8 +24,19 @@ 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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {07CE270E-9E57-49CD-8D5C-79C7B7A98517}
+ EndGlobalSection
EndGlobal
diff --git a/App.xaml b/PettingZoo/App.xaml
similarity index 79%
rename from App.xaml
rename to PettingZoo/App.xaml
index a9025ab..8fa298e 100644
--- a/App.xaml
+++ b/PettingZoo/App.xaml
@@ -1,12 +1,12 @@
+ ShutdownMode="OnMainWindowClose"
+ DispatcherUnhandledException="App_OnDispatcherUnhandledException">
-
diff --git a/PettingZoo/App.xaml.cs b/PettingZoo/App.xaml.cs
new file mode 100644
index 0000000..0a82edf
--- /dev/null
+++ b/PettingZoo/App.xaml.cs
@@ -0,0 +1,13 @@
+using System.Windows;
+using System.Windows.Threading;
+
+namespace PettingZoo
+{
+ public partial class App
+ {
+ private void App_OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
+ {
+ _ = MessageBox.Show($"Unhandled exception: {e.Exception.Message}", "Petting Zoo - Exception", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+}
diff --git a/Images/Clear.svg b/PettingZoo/Images/Clear.svg
similarity index 100%
rename from Images/Clear.svg
rename to PettingZoo/Images/Clear.svg
diff --git a/Images/Connect.svg b/PettingZoo/Images/Connect.svg
similarity index 100%
rename from Images/Connect.svg
rename to PettingZoo/Images/Connect.svg
diff --git a/Images/Disconnect.svg b/PettingZoo/Images/Disconnect.svg
similarity index 100%
rename from Images/Disconnect.svg
rename to PettingZoo/Images/Disconnect.svg
diff --git a/Images/PettingZoo-48.png b/PettingZoo/Images/PettingZoo-48.png
similarity index 100%
rename from Images/PettingZoo-48.png
rename to PettingZoo/Images/PettingZoo-48.png
diff --git a/Images/PettingZoo.ai b/PettingZoo/Images/PettingZoo.ai
similarity index 100%
rename from Images/PettingZoo.ai
rename to PettingZoo/Images/PettingZoo.ai
diff --git a/Images/PettingZoo.ico b/PettingZoo/Images/PettingZoo.ico
similarity index 100%
rename from Images/PettingZoo.ico
rename to PettingZoo/Images/PettingZoo.ico
diff --git a/PettingZoo/Images/Publish.svg b/PettingZoo/Images/Publish.svg
new file mode 100644
index 0000000..f83e762
--- /dev/null
+++ b/PettingZoo/Images/Publish.svg
@@ -0,0 +1,63 @@
+
+
+
+
diff --git a/PettingZoo/Images/PublishSend.svg b/PettingZoo/Images/PublishSend.svg
new file mode 100644
index 0000000..a564250
--- /dev/null
+++ b/PettingZoo/Images/PublishSend.svg
@@ -0,0 +1,49 @@
+
+
+
+
diff --git a/PettingZoo/Images/Subscribe.svg b/PettingZoo/Images/Subscribe.svg
new file mode 100644
index 0000000..72ad0cb
--- /dev/null
+++ b/PettingZoo/Images/Subscribe.svg
@@ -0,0 +1,63 @@
+
+
+
+
diff --git a/PettingZoo/PettingZoo.csproj b/PettingZoo/PettingZoo.csproj
new file mode 100644
index 0000000..20c88b9
--- /dev/null
+++ b/PettingZoo/PettingZoo.csproj
@@ -0,0 +1,112 @@
+
+
+
+ WinExe
+ net5.0-windows
+ true
+ Mark van Renswoude
+ Petting Zoo
+ Petting Zoo - a live RabbitMQ message viewer
+
+ enable
+ true
+ PettingZoo.Program
+
+
+ PettingZoo.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ConnectionWindowStrings.resx
+ True
+ True
+
+
+ MainWindowStrings.resx
+ True
+ True
+
+
+ SubscribeWindowStrings.resx
+ True
+ True
+
+
+ PublisherViewStrings.resx
+ True
+ True
+
+
+ SubscriberViewStrings.resx
+ True
+ True
+
+
+
+
+
+ ConnectionWindowStrings.Designer.cs
+ PublicResXFileCodeGenerator
+
+
+ MainWindowStrings.Designer.cs
+ PublicResXFileCodeGenerator
+
+
+ SubscribeWindowStrings.Designer.cs
+ PublicResXFileCodeGenerator
+
+
+ PublisherViewStrings.Designer.cs
+ PublicResXFileCodeGenerator
+
+
+ SubscriberViewStrings.Designer.cs
+ PublicResXFileCodeGenerator
+
+
+
+
+
+ $(DefaultXamlRuntime)
+
+
+ $(DefaultXamlRuntime)
+
+
+ $(DefaultXamlRuntime)
+
+
+ $(DefaultXamlRuntime)
+
+
+ $(DefaultXamlRuntime)
+
+
+
+
diff --git a/PettingZoo.ico b/PettingZoo/PettingZoo.ico
similarity index 100%
rename from PettingZoo.ico
rename to PettingZoo/PettingZoo.ico
diff --git a/App.xaml.cs b/PettingZoo/Program.cs
similarity index 51%
rename from App.xaml.cs
rename to PettingZoo/Program.cs
index 4089151..60752d2 100644
--- a/App.xaml.cs
+++ b/PettingZoo/Program.cs
@@ -5,16 +5,22 @@ using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using Newtonsoft.Json;
-using PettingZoo.Model;
-using PettingZoo.View;
-using PettingZoo.ViewModel;
+using PettingZoo.Core.Connection;
+using PettingZoo.RabbitMQ;
+using PettingZoo.Settings;
+using PettingZoo.UI.Connection;
+using PettingZoo.UI.Main;
+using PettingZoo.UI.Subscribe;
+using PettingZoo.UI.Tab;
+using PettingZoo.UI.Tab.Subscriber;
using SimpleInjector;
namespace PettingZoo
{
- public partial class App
+ public static class Program
{
- public void ApplicationStartup(object sender, StartupEventArgs e)
+ [STAThread]
+ public static void Main()
{
// WPF defaults to US for date formatting in bindings, this fixes it
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(
@@ -28,28 +34,50 @@ namespace PettingZoo
private static Container Bootstrap()
{
var container = new Container();
+
+ // See comments in RunApplication
+ container.Options.EnableAutoVerification = false;
container.RegisterSingleton(() => new UserSettings(new AppDataSettingsSerializer("Settings.json")));
container.Register();
- container.Register();
+ container.Register();
+ container.Register();
+ container.Register();
container.Register();
- container.Register();
-
- // 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)
+ private static void RunApplication(Container container)
{
- var mainWindow = container.GetInstance();
- mainWindow.Closed += (sender, args) => container.Dispose();
+ try
+ {
+ var app = new App();
+ app.InitializeComponent();
- mainWindow.Show();
+ #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
+
+ var mainWindow = container.GetInstance();
+ _ = app.Run(mainWindow);
+ }
+ catch (Exception)
+ {
+ // TODO Log the exception and exit
+ }
}
@@ -64,8 +92,8 @@ namespace PettingZoo
var companyName = GetProductInfo().Company;
var productName = GetProductInfo().Product;
- path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- companyName, productName);
+ path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ companyName, productName);
fullPath = Path.Combine(path, filename);
}
@@ -79,7 +107,7 @@ namespace PettingZoo
public void Write(UserSettings settings)
{
- Directory.CreateDirectory(path);
+ _ = Directory.CreateDirectory(path);
File.WriteAllText(fullPath, JsonConvert.SerializeObject(settings, Formatting.Indented));
}
@@ -87,11 +115,10 @@ namespace PettingZoo
private T GetProductInfo()
{
var attributes = GetType().Assembly.GetCustomAttributes(typeof(T), true);
- if (attributes.Length == 0)
- throw new Exception("Missing product information in assembly");
-
- return (T)attributes[0];
+ return attributes.Length == 0
+ ? throw new Exception("Missing product information in assembly")
+ : (T) attributes[0];
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Model/UserSettings.cs b/PettingZoo/Settings/UserSettings.cs
similarity index 91%
rename from Model/UserSettings.cs
rename to PettingZoo/Settings/UserSettings.cs
index cefe00b..0d48a40 100644
--- a/Model/UserSettings.cs
+++ b/PettingZoo/Settings/UserSettings.cs
@@ -1,4 +1,4 @@
-namespace PettingZoo.Model
+namespace PettingZoo.Settings
{
public interface IUserSettingsSerializer
{
@@ -15,6 +15,7 @@
public string LastUsername { get; set; }
public string LastPassword { get; set; }
+ //public bool LastSubscribe { get; set; }
public string LastExchange { get; set; }
public string LastRoutingKey { get; set; }
@@ -27,7 +28,7 @@
LastUsername = "guest";
LastPassword = "guest";
- LastExchange = "amqp";
+ LastExchange = "";
LastRoutingKey = "#";
}
}
@@ -35,7 +36,7 @@
public class UserSettings
{
- public ConnectionWindowSettings ConnectionWindow { get; private set; }
+ public ConnectionWindowSettings ConnectionWindow { get; }
private readonly IUserSettingsSerializer serializer;
diff --git a/Style.xaml b/PettingZoo/Style.xaml
similarity index 89%
rename from Style.xaml
rename to PettingZoo/Style.xaml
index f5896b0..bc8459b 100644
--- a/Style.xaml
+++ b/PettingZoo/Style.xaml
@@ -1,6 +1,6 @@
+ xmlns:ui="clr-namespace:PettingZoo.UI">
-
@@ -59,6 +59,8 @@
-
\ No newline at end of file
diff --git a/PettingZoo/UI/BaseViewModel.cs b/PettingZoo/UI/BaseViewModel.cs
new file mode 100644
index 0000000..33b7909
--- /dev/null
+++ b/PettingZoo/UI/BaseViewModel.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace PettingZoo.UI
+{
+ public class BaseViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected virtual void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+
+ protected virtual void RaiseOtherPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+
+ protected bool SetField(ref T field, T value, IEqualityComparer? comparer = null, [CallerMemberName] string? propertyName = null,
+ params string[]? otherPropertiesChanged)
+ {
+ if ((comparer ?? EqualityComparer.Default).Equals(field, value))
+ return false;
+
+ field = value;
+ RaisePropertyChanged(propertyName);
+
+ if (otherPropertiesChanged == null)
+ return true;
+
+ foreach (var otherProperty in otherPropertiesChanged)
+ RaisePropertyChanged(otherProperty);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PettingZoo/UI/Connection/ConnectionViewModel.cs b/PettingZoo/UI/Connection/ConnectionViewModel.cs
new file mode 100644
index 0000000..b1eacb6
--- /dev/null
+++ b/PettingZoo/UI/Connection/ConnectionViewModel.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Windows.Input;
+
+// TODO validate input
+
+namespace PettingZoo.UI.Connection
+{
+ public class ConnectionViewModel : BaseViewModel
+ {
+ private string host;
+ private string virtualHost;
+ private int port;
+ private string username;
+ private string password;
+
+ private bool subscribe;
+ private string exchange;
+ private string routingKey;
+
+
+ public string Host
+ {
+ get => host;
+ set => SetField(ref host, value);
+ }
+
+ public string VirtualHost
+ {
+ get => virtualHost;
+ set => SetField(ref virtualHost, value);
+ }
+
+ public int Port
+ {
+ get => port;
+ set => SetField(ref port, value);
+ }
+
+ public string Username
+ {
+ get => username;
+ set => SetField(ref username, value);
+ }
+
+ public string Password
+ {
+ get => password;
+ set => SetField(ref password, value);
+ }
+
+
+ public bool Subscribe
+ {
+ get => subscribe;
+ set => SetField(ref subscribe, value);
+ }
+
+ public string Exchange
+ {
+ get => exchange;
+ set => SetField(ref exchange, value);
+ }
+
+ public string RoutingKey
+ {
+ get => routingKey;
+ set => SetField(ref routingKey, value);
+ }
+
+
+ public ICommand OkCommand { get; }
+
+ public event EventHandler? OkClick;
+
+
+ public ConnectionViewModel(ConnectionDialogParams model)
+ {
+ OkCommand = new DelegateCommand(OkExecute, OkCanExecute);
+
+ host = model.Host;
+ virtualHost = model.VirtualHost;
+ port = model.Port;
+ username = model.Username;
+ password = model.Password;
+
+ subscribe = model.Subscribe;
+ exchange = model.Exchange;
+ routingKey = model.RoutingKey;
+ }
+
+
+ public ConnectionDialogParams ToModel()
+ {
+ return new(Host, VirtualHost, Port, Username, Password, Subscribe, Exchange, RoutingKey);
+ }
+
+
+ private void OkExecute()
+ {
+ OkClick?.Invoke(this, EventArgs.Empty);
+ }
+
+
+ private static bool OkCanExecute()
+ {
+ return true;
+ }
+ }
+
+
+ public class DesignTimeConnectionViewModel : ConnectionViewModel
+ {
+ public DesignTimeConnectionViewModel() : base(ConnectionDialogParams.Default)
+ {
+ }
+ }
+}
diff --git a/View/ConnectionWindow.xaml b/PettingZoo/UI/Connection/ConnectionWindow.xaml
similarity index 57%
rename from View/ConnectionWindow.xaml
rename to PettingZoo/UI/Connection/ConnectionWindow.xaml
index 700f928..0c5afd8 100644
--- a/View/ConnectionWindow.xaml
+++ b/PettingZoo/UI/Connection/ConnectionWindow.xaml
@@ -1,27 +1,26 @@
-
-
-
+
+
-
+
@@ -35,28 +34,31 @@
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
-
-
-
+
+
+
+
+
+
diff --git a/PettingZoo/UI/Connection/ConnectionWindow.xaml.cs b/PettingZoo/UI/Connection/ConnectionWindow.xaml.cs
new file mode 100644
index 0000000..6a71fb8
--- /dev/null
+++ b/PettingZoo/UI/Connection/ConnectionWindow.xaml.cs
@@ -0,0 +1,45 @@
+using System.Windows;
+using System.Windows.Input;
+
+namespace PettingZoo.UI.Connection
+{
+ public class WindowConnectionDialog : IConnectionDialog
+ {
+ public ConnectionDialogParams? Show(ConnectionDialogParams? defaultParams = null)
+ {
+ var viewModel = new ConnectionViewModel(defaultParams ?? ConnectionDialogParams.Default);
+ var window = new ConnectionWindow(viewModel)
+ {
+ Owner = Application.Current.MainWindow
+ };
+
+ viewModel.OkClick += (_, _) =>
+ {
+ window.DialogResult = true;
+ };
+
+ return window.ShowDialog().GetValueOrDefault()
+ ? viewModel.ToModel()
+ : null;
+ }
+ }
+
+
+ public partial class ConnectionWindow
+ {
+ public ConnectionWindow(ConnectionViewModel viewModel)
+ {
+ WindowStartupLocation = WindowStartupLocation.CenterOwner;
+
+ InitializeComponent();
+ DataContext = viewModel;
+ }
+
+
+ private void NumericPreviewTextInput(object sender, TextCompositionEventArgs args)
+ {
+ if (!char.IsDigit(args.Text, args.Text.Length - 1))
+ args.Handled = true;
+ }
+ }
+}
diff --git a/PettingZoo/UI/Connection/ConnectionWindowStrings.Designer.cs b/PettingZoo/UI/Connection/ConnectionWindowStrings.Designer.cs
new file mode 100644
index 0000000..f689fb1
--- /dev/null
+++ b/PettingZoo/UI/Connection/ConnectionWindowStrings.Designer.cs
@@ -0,0 +1,162 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace PettingZoo.UI.Connection {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // 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", "16.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() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [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;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cancel.
+ ///
+ public static string ButtonCancel {
+ get {
+ return ResourceManager.GetString("ButtonCancel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to OK.
+ ///
+ public static string ButtonOK {
+ get {
+ return ResourceManager.GetString("ButtonOK", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Exchange:.
+ ///
+ public static string LabelExchange {
+ get {
+ return ResourceManager.GetString("LabelExchange", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Host:.
+ ///
+ public static string LabelHost {
+ get {
+ return ResourceManager.GetString("LabelHost", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Password:.
+ ///
+ public static string LabelPassword {
+ get {
+ return ResourceManager.GetString("LabelPassword", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Port:.
+ ///
+ public static string LabelPort {
+ get {
+ return ResourceManager.GetString("LabelPort", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Routing key:.
+ ///
+ public static string LabelRoutingKey {
+ get {
+ return ResourceManager.GetString("LabelRoutingKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Subscribe.
+ ///
+ public static string LabelSubscribe {
+ get {
+ return ResourceManager.GetString("LabelSubscribe", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Username:.
+ ///
+ public static string LabelUsername {
+ get {
+ return ResourceManager.GetString("LabelUsername", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Virtual host:.
+ ///
+ public static string LabelVirtualHost {
+ get {
+ return ResourceManager.GetString("LabelVirtualHost", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Connection parameters.
+ ///
+ public static string WindowTitle {
+ get {
+ return ResourceManager.GetString("WindowTitle", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/PettingZoo/UI/Connection/ConnectionWindowStrings.resx b/PettingZoo/UI/Connection/ConnectionWindowStrings.resx
new file mode 100644
index 0000000..aaac15d
--- /dev/null
+++ b/PettingZoo/UI/Connection/ConnectionWindowStrings.resx
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Cancel
+
+
+ OK
+
+
+ Exchange:
+
+
+ Host:
+
+
+ Password:
+
+
+ Port:
+
+
+ Routing key:
+
+
+ Subscribe
+
+
+ Username:
+
+
+ Virtual host:
+
+
+ Connection parameters
+
+
\ No newline at end of file
diff --git a/PettingZoo/UI/Connection/IConnectionDialog.cs b/PettingZoo/UI/Connection/IConnectionDialog.cs
new file mode 100644
index 0000000..ba63224
--- /dev/null
+++ b/PettingZoo/UI/Connection/IConnectionDialog.cs
@@ -0,0 +1,43 @@
+using System;
+
+namespace PettingZoo.UI.Connection
+{
+ public interface IConnectionDialog
+ {
+ ConnectionDialogParams? Show(ConnectionDialogParams? defaultParams = null);
+ }
+
+
+ public class ConnectionDialogParams
+ {
+ 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 ConnectionDialogParams Default { get; } = new("localhost", "/", 5672, "guest", "guest", false, "", "#");
+
+
+ public ConnectionDialogParams(string host, string virtualHost, int port, string username, string password, bool subscribe, string exchange, string routingKey)
+ {
+ if (subscribe && (string.IsNullOrEmpty(exchange) || string.IsNullOrEmpty(routingKey)))
+ throw new ArgumentException(@"Exchange and RoutingKey must be provided when Subscribe is true", nameof(subscribe));
+
+ Host = host;
+ VirtualHost = virtualHost;
+ Port = port;
+ Username = username;
+ Password = password;
+
+ Subscribe = subscribe;
+ Exchange = exchange;
+ RoutingKey = routingKey;
+ }
+ }
+}
diff --git a/PettingZoo/UI/DelegateCommand.cs b/PettingZoo/UI/DelegateCommand.cs
new file mode 100644
index 0000000..8404c02
--- /dev/null
+++ b/PettingZoo/UI/DelegateCommand.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Windows.Input;
+
+namespace PettingZoo.UI
+{
+ public class DelegateCommand : ICommand where T : class?
+ {
+ private readonly Func? canExecute;
+ private readonly Action execute;
+
+ public event EventHandler? CanExecuteChanged;
+
+
+ public DelegateCommand(Action execute) : this(execute, null)
+ {
+ }
+
+ public DelegateCommand(Action execute, Func? 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? canExecute;
+ private readonly Action execute;
+
+ public event EventHandler? CanExecuteChanged;
+
+
+ public DelegateCommand(Action execute) : this(execute, null) { }
+
+ public DelegateCommand(Action execute, Func? 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);
+ }
+ }
+}
diff --git a/Infrastructure/GridLayout.cs b/PettingZoo/UI/GridLayout.cs
similarity index 67%
rename from Infrastructure/GridLayout.cs
rename to PettingZoo/UI/GridLayout.cs
index d4ed843..a1f577f 100644
--- a/Infrastructure/GridLayout.cs
+++ b/PettingZoo/UI/GridLayout.cs
@@ -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);
}
}
diff --git a/Infrastructure/ListBoxAutoScroll.cs b/PettingZoo/UI/ListBoxAutoScroll.cs
similarity index 75%
rename from Infrastructure/ListBoxAutoScroll.cs
rename to PettingZoo/UI/ListBoxAutoScroll.cs
index 4d2415c..b378e3b 100644
--- a/Infrastructure/ListBoxAutoScroll.cs
+++ b/PettingZoo/UI/ListBoxAutoScroll.cs
@@ -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;
diff --git a/PettingZoo/UI/Main/MainWindow.xaml b/PettingZoo/UI/Main/MainWindow.xaml
new file mode 100644
index 0000000..566aa8d
--- /dev/null
+++ b/PettingZoo/UI/Main/MainWindow.xaml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PettingZoo/UI/Main/MainWindow.xaml.cs b/PettingZoo/UI/Main/MainWindow.xaml.cs
new file mode 100644
index 0000000..c41d85e
--- /dev/null
+++ b/PettingZoo/UI/Main/MainWindow.xaml.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Windows;
+using PettingZoo.Core.Connection;
+using PettingZoo.UI.Connection;
+using PettingZoo.UI.Subscribe;
+using PettingZoo.UI.Tab;
+
+namespace PettingZoo.UI.Main
+{
+ // TODO support undocking tabs (and redocking afterwards)
+ // TODO allow tab reordering
+
+ #pragma warning disable CA1001 // MainWindow can't be IDisposable, handled instead in OnDispatcherShutDownStarted
+ public partial class MainWindow
+ {
+ private readonly MainWindowViewModel viewModel;
+
+
+ public MainWindow(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog, ISubscribeDialog subscribeDialog, ITabFactory tabFactory)
+ {
+ WindowStartupLocation = WindowStartupLocation.CenterScreen;
+
+ InitializeComponent();
+ viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog, tabFactory);
+ DataContext = viewModel;
+
+ Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted;
+ }
+
+
+ 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;
+ }
+ }
+ #pragma warning restore CA1001
+}
diff --git a/Properties/Resources.Designer.cs b/PettingZoo/UI/Main/MainWindowStrings.Designer.cs
similarity index 55%
rename from Properties/Resources.Designer.cs
rename to PettingZoo/UI/Main/MainWindowStrings.Designer.cs
index 4b0bc29..8b3f52f 100644
--- a/Properties/Resources.Designer.cs
+++ b/PettingZoo/UI/Main/MainWindowStrings.Designer.cs
@@ -8,7 +8,10 @@
//
//------------------------------------------------------------------------------
-namespace PettingZoo.Properties {
+namespace PettingZoo.UI.Main {
+ using System;
+
+
///
/// A strongly-typed resource class, for looking up localized strings, etc.
///
@@ -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", "16.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() {
}
///
@@ -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,47 @@ namespace PettingZoo.Properties {
}
///
- /// Looks up a localized string similar to Cancel.
+ /// Looks up a localized string similar to Connect.
///
- public static string ButtonCancel {
+ public static string CommandConnect {
get {
- return ResourceManager.GetString("ButtonCancel", resourceCulture);
+ return ResourceManager.GetString("CommandConnect", resourceCulture);
}
}
///
- /// Looks up a localized string similar to OK.
+ /// Looks up a localized string similar to Disconnect.
///
- public static string ButtonOK {
+ public static string CommandDisconnect {
get {
- return ResourceManager.GetString("ButtonOK", resourceCulture);
+ return ResourceManager.GetString("CommandDisconnect", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Exchange:.
+ /// Looks up a localized string similar to New Publisher.
///
- public static string ConnectionExchange {
+ public static string CommandPublish {
get {
- return ResourceManager.GetString("ConnectionExchange", resourceCulture);
+ return ResourceManager.GetString("CommandPublish", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Host:.
+ /// Looks up a localized string similar to New Subscriber....
///
- public static string ConnectionHost {
+ public static string CommandSubscribe {
get {
- return ResourceManager.GetString("ConnectionHost", resourceCulture);
+ return ResourceManager.GetString("CommandSubscribe", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Password:.
+ /// Looks up a localized string similar to Close tab.
///
- public static string ConnectionPassword {
+ public static string ContextMenuCloseTab {
get {
- return ResourceManager.GetString("ConnectionPassword", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Port:.
- ///
- public static string ConnectionPort {
- get {
- return ResourceManager.GetString("ConnectionPort", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Routing key:.
- ///
- public static string ConnectionRoutingKey {
- get {
- return ResourceManager.GetString("ConnectionRoutingKey", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Username:.
- ///
- public static string ConnectionUsername {
- get {
- return ResourceManager.GetString("ConnectionUsername", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Virtual host:.
- ///
- public static string ConnectionVirtualHost {
- get {
- return ResourceManager.GetString("ConnectionVirtualHost", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Connection parameters.
- ///
- public static string ConnectionWindowTitle {
- get {
- return ResourceManager.GetString("ConnectionWindowTitle", resourceCulture);
+ return ResourceManager.GetString("ContextMenuCloseTab", resourceCulture);
}
}
@@ -165,51 +123,6 @@ namespace PettingZoo.Properties {
}
}
- ///
- /// Looks up a localized string similar to Petting Zoo - a RabbitMQ live message viewer.
- ///
- public static string MainWindowTitle {
- get {
- return ResourceManager.GetString("MainWindowTitle", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Body.
- ///
- public static string PanelTitleBody {
- get {
- return ResourceManager.GetString("PanelTitleBody", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Properties.
- ///
- public static string PanelTitleProperties {
- get {
- return ResourceManager.GetString("PanelTitleProperties", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Name.
- ///
- public static string PropertyName {
- get {
- return ResourceManager.GetString("PropertyName", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Value.
- ///
- public static string PropertyValue {
- get {
- return ResourceManager.GetString("PropertyValue", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to Connected.
///
@@ -245,5 +158,14 @@ namespace PettingZoo.Properties {
return ResourceManager.GetString("StatusError", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Petting Zoo - a RabbitMQ live message viewer.
+ ///
+ public static string WindowTitle {
+ get {
+ return ResourceManager.GetString("WindowTitle", resourceCulture);
+ }
+ }
}
}
diff --git a/Properties/Resources.resx b/PettingZoo/UI/Main/MainWindowStrings.resx
similarity index 83%
rename from Properties/Resources.resx
rename to PettingZoo/UI/Main/MainWindowStrings.resx
index e9c49e4..6df2e25 100644
--- a/Properties/Resources.resx
+++ b/PettingZoo/UI/Main/MainWindowStrings.resx
@@ -117,35 +117,20 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- Cancel
+
+ Connect
-
- OK
+
+ Disconnect
-
- Exchange:
+
+ New Publisher
-
- Host:
+
+ New Subscriber...
-
- Password:
-
-
- Port:
-
-
- Routing key:
-
-
- Username:
-
-
- Virtual host:
-
-
- Connection parameters
+
+ Close tab
Non-persistent
@@ -153,21 +138,6 @@
Persistent
-
- Petting Zoo - a RabbitMQ live message viewer
-
-
- Body
-
-
- Properties
-
-
- Name
-
-
- Value
-
Connected
@@ -180,4 +150,7 @@
Error: {0}
+
+ Petting Zoo - a RabbitMQ live message viewer
+
\ No newline at end of file
diff --git a/PettingZoo/UI/Main/MainWindowViewModel.cs b/PettingZoo/UI/Main/MainWindowViewModel.cs
new file mode 100644
index 0000000..a1b594e
--- /dev/null
+++ b/PettingZoo/UI/Main/MainWindowViewModel.cs
@@ -0,0 +1,237 @@
+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;
+
+namespace PettingZoo.UI.Main
+{
+ public class MainWindowViewModel : BaseViewModel, IAsyncDisposable
+ {
+ private readonly IConnectionFactory connectionFactory;
+ private readonly IConnectionDialog connectionDialog;
+ private readonly ISubscribeDialog subscribeDialog;
+ private readonly ITabFactory tabFactory;
+
+ private ConnectionDialogParams? connectionDialogParams;
+ private SubscribeDialogParams? subscribeDialogParams;
+ private IConnection? connection;
+ private string connectionStatus;
+ private ITab? activeTab;
+
+ private readonly DelegateCommand connectCommand;
+ private readonly DelegateCommand disconnectCommand;
+ private readonly DelegateCommand publishCommand;
+ private readonly DelegateCommand subscribeCommand;
+ private readonly DelegateCommand closeTabCommand;
+
+
+ public string ConnectionStatus
+ {
+ get => connectionStatus;
+ private set => SetField(ref connectionStatus, value);
+ }
+
+
+ public ObservableCollection Tabs { get; }
+
+ public ITab? ActiveTab
+ {
+ get => activeTab;
+ set => SetField(ref activeTab, value, otherPropertiesChanged: new []
+ {
+ nameof(ToolbarCommands),
+ nameof(ToolbarCommandsSeparatorVisibility)
+ });
+ }
+
+ public ICommand ConnectCommand => connectCommand;
+ public ICommand DisconnectCommand => disconnectCommand;
+ public ICommand PublishCommand => publishCommand;
+ public ICommand SubscribeCommand => subscribeCommand;
+ public ICommand CloseTabCommand => closeTabCommand;
+
+ public IEnumerable ToolbarCommands => ActiveTab is ITabToolbarCommands tabToolbarCommands
+ ? tabToolbarCommands.ToolbarCommands
+ : Enumerable.Empty();
+
+ public Visibility ToolbarCommandsSeparatorVisibility =>
+ ToolbarCommands.Any() ? Visibility.Visible : Visibility.Collapsed;
+
+
+ public MainWindowViewModel(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog,
+ ISubscribeDialog subscribeDialog, ITabFactory tabFactory)
+ {
+ this.connectionFactory = connectionFactory;
+ this.connectionDialog = connectionDialog;
+ this.subscribeDialog = subscribeDialog;
+ this.tabFactory = tabFactory;
+
+ connectionStatus = GetConnectionStatus(null);
+
+ Tabs = new ObservableCollection();
+ connectCommand = new DelegateCommand(ConnectExecute);
+ disconnectCommand = new DelegateCommand(DisconnectExecute, IsConnectedCanExecute);
+ publishCommand = new DelegateCommand(PublishExecute, IsConnectedCanExecute);
+ subscribeCommand = new DelegateCommand(SubscribeExecute, IsConnectedCanExecute);
+ closeTabCommand = new DelegateCommand(CloseTabExecute, CloseTabCanExecute);
+ }
+
+
+ public async ValueTask DisposeAsync()
+ {
+ if (connection != null)
+ await connection.DisposeAsync();
+ }
+
+
+ private async void ConnectExecute()
+ {
+ //var newParams = connectionDialog.Show(connectionDialogParams);
+ var newParams = new ConnectionDialogParams("localhost", "/", 5672, "guest", "guest", true, "lef", "#");
+ if (newParams == null)
+ return;
+
+ if (connection != null)
+ await connection.DisposeAsync();
+
+ connectionDialogParams = newParams;
+ connection = connectionFactory.CreateConnection(new ConnectionParams(
+ connectionDialogParams.Host, connectionDialogParams.VirtualHost, connectionDialogParams.Port,
+ connectionDialogParams.Username, connectionDialogParams.Password));
+ connection.StatusChanged += ConnectionStatusChanged;
+
+ if (connectionDialogParams.Subscribe)
+ {
+ var subscriber = connection.Subscribe(connectionDialogParams.Exchange, connectionDialogParams.RoutingKey);
+ AddTab(tabFactory.CreateSubscriberTab(CloseTabCommand, subscriber));
+
+ }
+
+ ConnectionChanged();
+ }
+
+
+ private async void DisconnectExecute()
+ {
+ Tabs.Clear();
+
+ if (connection != null)
+ {
+ await connection.DisposeAsync();
+ connection = null;
+ }
+
+ connectionDialogParams = null;
+ ConnectionStatus = GetConnectionStatus(null);
+
+ 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(CloseTabCommand, subscriber));
+ }
+
+
+ private void PublishExecute()
+ {
+ if (connection == null)
+ return;
+
+ AddTab(tabFactory.CreatePublisherTab(CloseTabCommand, connection));
+ }
+
+
+ private bool IsConnectedCanExecute()
+ {
+ return connection != null;
+ }
+
+
+ private void CloseTabExecute()
+ {
+ if (ActiveTab == null)
+ return;
+
+ var activeTabIndex = Tabs.IndexOf(ActiveTab);
+ if (activeTabIndex == -1)
+ return;
+
+ Tabs.RemoveAt(activeTabIndex);
+
+ if (activeTabIndex == Tabs.Count)
+ activeTabIndex--;
+
+ ActiveTab = activeTabIndex >= 0 ? Tabs[activeTabIndex] : null;
+ closeTabCommand.RaiseCanExecuteChanged();
+ }
+
+
+ private bool CloseTabCanExecute()
+ {
+ return ActiveTab != null;
+ }
+
+
+ private void AddTab(ITab tab)
+ {
+ Tabs.Add(tab);
+ ActiveTab = tab;
+
+ closeTabCommand.RaiseCanExecuteChanged();
+ }
+
+
+ private void ConnectionChanged()
+ {
+ disconnectCommand.RaiseCanExecuteChanged();
+ subscribeCommand.RaiseCanExecuteChanged();
+ publishCommand.RaiseCanExecuteChanged();
+ }
+
+ private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs args)
+ {
+ ConnectionStatus = GetConnectionStatus(args);
+ }
+
+
+
+ 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!)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/PasswordBoxAssistant.cs b/PettingZoo/UI/PasswordBoxAssistant.cs
similarity index 92%
rename from Infrastructure/PasswordBoxAssistant.cs
rename to PettingZoo/UI/PasswordBoxAssistant.cs
index 3ca477d..b27587b 100644
--- a/Infrastructure/PasswordBoxAssistant.cs
+++ b/PettingZoo/UI/PasswordBoxAssistant.cs
@@ -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)
diff --git a/PettingZoo/UI/Subscribe/ISubscribeDialog.cs b/PettingZoo/UI/Subscribe/ISubscribeDialog.cs
new file mode 100644
index 0000000..19c65e2
--- /dev/null
+++ b/PettingZoo/UI/Subscribe/ISubscribeDialog.cs
@@ -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;
+ }
+ }
+}
diff --git a/PettingZoo/UI/Subscribe/SubscribeViewModel.cs b/PettingZoo/UI/Subscribe/SubscribeViewModel.cs
new file mode 100644
index 0000000..8750367
--- /dev/null
+++ b/PettingZoo/UI/Subscribe/SubscribeViewModel.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Windows.Input;
+
+// TODO validate input
+
+namespace PettingZoo.UI.Subscribe
+{
+ public class SubscribeViewModel : BaseViewModel
+ {
+ private string exchange;
+ private string routingKey;
+
+
+ public string Exchange
+ {
+ get => exchange;
+ set => SetField(ref exchange, value);
+ }
+
+ public string RoutingKey
+ {
+ get => routingKey;
+ set => SetField(ref routingKey, value);
+ }
+
+
+ public ICommand OkCommand { get; }
+
+ public event EventHandler? OkClick;
+
+
+ public SubscribeViewModel(SubscribeDialogParams subscribeParams)
+ {
+ OkCommand = new DelegateCommand(OkExecute, OkCanExecute);
+
+ exchange = subscribeParams.Exchange;
+ routingKey = subscribeParams.RoutingKey;
+ }
+
+
+ public SubscribeDialogParams ToModel()
+ {
+ return new(Exchange, RoutingKey);
+ }
+
+
+ private void OkExecute()
+ {
+ OkClick?.Invoke(this, EventArgs.Empty);
+ }
+
+
+ private static bool OkCanExecute()
+ {
+ return true;
+ }
+ }
+
+
+ public class DesignTimeSubscribeViewModel : SubscribeViewModel
+ {
+ public DesignTimeSubscribeViewModel() : base(SubscribeDialogParams.Default)
+ {
+ }
+ }
+}
diff --git a/PettingZoo/UI/Subscribe/SubscribeWindow.xaml b/PettingZoo/UI/Subscribe/SubscribeWindow.xaml
new file mode 100644
index 0000000..7d8010a
--- /dev/null
+++ b/PettingZoo/UI/Subscribe/SubscribeWindow.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PettingZoo/UI/Subscribe/SubscribeWindow.xaml.cs b/PettingZoo/UI/Subscribe/SubscribeWindow.xaml.cs
new file mode 100644
index 0000000..605fbf6
--- /dev/null
+++ b/PettingZoo/UI/Subscribe/SubscribeWindow.xaml.cs
@@ -0,0 +1,37 @@
+using System.Windows;
+
+namespace PettingZoo.UI.Subscribe
+{
+ public class WindowSubscribeDialog : ISubscribeDialog
+ {
+ public SubscribeDialogParams? Show(SubscribeDialogParams? defaultParams = null)
+ {
+ var viewModel = new SubscribeViewModel(defaultParams ?? SubscribeDialogParams.Default);
+ var window = new SubscribeWindow(viewModel)
+ {
+ Owner = Application.Current.MainWindow
+ };
+
+ viewModel.OkClick += (_, _) =>
+ {
+ window.DialogResult = true;
+ };
+
+ return window.ShowDialog().GetValueOrDefault()
+ ? viewModel.ToModel()
+ : null;
+ }
+ }
+
+
+ public partial class SubscribeWindow
+ {
+ public SubscribeWindow(SubscribeViewModel viewModel)
+ {
+ WindowStartupLocation = WindowStartupLocation.CenterOwner;
+
+ InitializeComponent();
+ DataContext = viewModel;
+ }
+ }
+}
diff --git a/PettingZoo/UI/Subscribe/SubscribeWindowStrings.Designer.cs b/PettingZoo/UI/Subscribe/SubscribeWindowStrings.Designer.cs
new file mode 100644
index 0000000..8ed6465
--- /dev/null
+++ b/PettingZoo/UI/Subscribe/SubscribeWindowStrings.Designer.cs
@@ -0,0 +1,108 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace PettingZoo.UI.Subscribe {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // 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", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class SubscribeWindowStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal SubscribeWindowStrings() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [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.Subscribe.SubscribeWindowStrings", typeof(SubscribeWindowStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cancel.
+ ///
+ public static string ButtonCancel {
+ get {
+ return ResourceManager.GetString("ButtonCancel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to OK.
+ ///
+ public static string ButtonOK {
+ get {
+ return ResourceManager.GetString("ButtonOK", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Exchange:.
+ ///
+ public static string LabelExchange {
+ get {
+ return ResourceManager.GetString("LabelExchange", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Routing key:.
+ ///
+ public static string LabelRoutingKey {
+ get {
+ return ResourceManager.GetString("LabelRoutingKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Subscribe parameters.
+ ///
+ public static string WindowTitle {
+ get {
+ return ResourceManager.GetString("WindowTitle", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/PettingZoo/UI/Subscribe/SubscribeWindowStrings.resx b/PettingZoo/UI/Subscribe/SubscribeWindowStrings.resx
new file mode 100644
index 0000000..e0c4346
--- /dev/null
+++ b/PettingZoo/UI/Subscribe/SubscribeWindowStrings.resx
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Cancel
+
+
+ OK
+
+
+ Exchange:
+
+
+ Routing key:
+
+
+ Subscribe parameters
+
+
\ No newline at end of file
diff --git a/PettingZoo/UI/SvgIconHelper.cs b/PettingZoo/UI/SvgIconHelper.cs
new file mode 100644
index 0000000..3948dde
--- /dev/null
+++ b/PettingZoo/UI/SvgIconHelper.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Windows;
+using System.Windows.Media;
+using SharpVectors.Dom.Svg;
+using SharpVectors.Renderers.Utils;
+using SharpVectors.Renderers.Wpf;
+
+namespace PettingZoo.UI
+{
+ public static class SvgIconHelper
+ {
+ public static ImageSource LoadFromResource(string resourceName)
+ {
+ var streamInfo = Application.GetResourceStream(new Uri($"/PettingZoo;component{resourceName}", UriKind.Relative));
+ if (streamInfo == null)
+ throw new ArgumentException($"Resource '{resourceName}' not found");
+
+ // Basically the code used in FileSvgConverter, but that only supports outputting to a file not returning the Drawing
+ var wpfDrawingSettings = new WpfDrawingSettings
+ {
+ IncludeRuntime = true,
+ TextAsGeometry = true
+ };
+ var wpfRenderer = new WpfDrawingRenderer(wpfDrawingSettings);
+ var wpfWindow = new WpfSvgWindow(0, 0, wpfRenderer);
+
+ wpfWindow.LoadDocument(streamInfo.Stream, wpfDrawingSettings);
+ wpfRenderer.InvalidRect = SvgRectF.Empty;
+ wpfRenderer.Render(wpfWindow.Document);
+
+ if (wpfRenderer.Drawing == null)
+ throw new ArgumentException($"Resource '{resourceName}' is not a valid SVG");
+
+ return new DrawingImage(wpfRenderer.Drawing);
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/ITab.cs b/PettingZoo/UI/Tab/ITab.cs
new file mode 100644
index 0000000..b3bc34e
--- /dev/null
+++ b/PettingZoo/UI/Tab/ITab.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace PettingZoo.UI.Tab
+{
+ public interface ITabToolbarCommands : INotifyPropertyChanged
+ {
+ IEnumerable ToolbarCommands { get; }
+ }
+
+
+ public interface ITab : ITabToolbarCommands
+ {
+ string Title { get; }
+ ContentControl Content { get; }
+ ICommand CloseTabCommand { get; }
+ }
+
+
+ public readonly struct TabToolbarCommand
+ {
+ public ICommand Command { get; }
+ public string Caption { get; }
+ public ImageSource Icon { get; }
+
+
+ public TabToolbarCommand(ICommand command, string caption, ImageSource icon)
+ {
+ Command = command;
+ Caption = caption;
+ Icon = icon;
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/ITabFactory.cs b/PettingZoo/UI/Tab/ITabFactory.cs
new file mode 100644
index 0000000..ca290a7
--- /dev/null
+++ b/PettingZoo/UI/Tab/ITabFactory.cs
@@ -0,0 +1,15 @@
+using System.Windows.Input;
+using PettingZoo.Core.Connection;
+
+namespace PettingZoo.UI.Tab
+{
+ // Passing the closeTabCommand is necessary because I haven't figured out how to bind the main window's
+ // context menu items for the tab to the main window's datacontext yet. RelativeSource doesn't seem to work
+ // because the popup is it's own window. Refactor if a better solution is found.
+
+ public interface ITabFactory
+ {
+ ITab CreateSubscriberTab(ICommand closeTabCommand, ISubscriber subscriber);
+ ITab CreatePublisherTab(ICommand closeTabCommand, IConnection connection);
+ }
+}
diff --git a/PettingZoo/UI/Tab/Publisher/PublisherView.xaml b/PettingZoo/UI/Tab/Publisher/PublisherView.xaml
new file mode 100644
index 0000000..7167b3e
--- /dev/null
+++ b/PettingZoo/UI/Tab/Publisher/PublisherView.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ TODO: implement publish forms
+
+
diff --git a/PettingZoo/UI/Tab/Publisher/PublisherView.xaml.cs b/PettingZoo/UI/Tab/Publisher/PublisherView.xaml.cs
new file mode 100644
index 0000000..b6e3727
--- /dev/null
+++ b/PettingZoo/UI/Tab/Publisher/PublisherView.xaml.cs
@@ -0,0 +1,19 @@
+using System.Windows.Media;
+
+namespace PettingZoo.UI.Tab.Publisher
+{
+ ///
+ /// Interaction logic for PublisherView.xaml
+ ///
+ public partial class PublisherView
+ {
+ public PublisherView(PublisherViewModel viewModel)
+ {
+ InitializeComponent();
+ DataContext = viewModel;
+
+ if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
+ Background = Brushes.Transparent;
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs b/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs
new file mode 100644
index 0000000..a3542e2
--- /dev/null
+++ b/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs
@@ -0,0 +1,90 @@
+using System.Collections.Generic;
+using System.Windows.Input;
+using PettingZoo.Core.Connection;
+
+namespace PettingZoo.UI.Tab.Publisher
+{
+ public enum MessageType
+ {
+ Raw,
+ Tapeti
+ }
+
+
+ public class PublisherViewModel : BaseViewModel, ITabToolbarCommands
+ {
+ private readonly IConnection connection;
+
+ private MessageType messageType;
+
+ private readonly DelegateCommand publishCommand;
+ private readonly TabToolbarCommand[] toolbarCommands;
+
+
+ public MessageType MessageType
+ {
+ get => messageType;
+ set => SetField(ref messageType, value,
+ otherPropertiesChanged: new[]
+ {
+ nameof(MessageTypeRaw),
+ nameof(MessageTypeTapeti)
+ });
+
+ }
+
+ public bool MessageTypeRaw
+ {
+ get => MessageType == MessageType.Raw;
+ set { if (value) MessageType = MessageType.Raw; }
+ }
+
+ public bool MessageTypeTapeti
+ {
+ get => MessageType == MessageType.Tapeti;
+ set { if (value) MessageType = MessageType.Tapeti; }
+ }
+
+
+ public ICommand PublishCommand => publishCommand;
+
+
+ // TODO make more dynamic, include entered routing key for example
+ public string Title => "Publish";
+ public IEnumerable ToolbarCommands => toolbarCommands;
+
+
+ public PublisherViewModel(IConnection connection)
+ {
+ this.connection = connection;
+
+ publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute);
+
+ toolbarCommands = new[]
+ {
+ new TabToolbarCommand(PublishCommand, PublisherViewStrings.CommandPublish, SvgIconHelper.LoadFromResource("/Images/PublishSend.svg"))
+ };
+ }
+
+
+ private void PublishExecute()
+ {
+ // TODO
+ }
+
+
+ private bool PublishCanExecute()
+ {
+ // TODO validate input
+ return true;
+ }
+ }
+
+
+ public class DesignTimePublisherViewModel : PublisherViewModel
+ {
+ public DesignTimePublisherViewModel() : base(null!)
+ {
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.Designer.cs b/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.Designer.cs
new file mode 100644
index 0000000..83d136a
--- /dev/null
+++ b/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.Designer.cs
@@ -0,0 +1,99 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace PettingZoo.UI.Tab.Publisher {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // 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", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class PublisherViewStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal PublisherViewStrings() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [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.Tab.Publisher.PublisherViewStrings", typeof(PublisherViewStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Publish.
+ ///
+ public static string CommandPublish {
+ get {
+ return ResourceManager.GetString("CommandPublish", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Message type: .
+ ///
+ public static string LabelMessageType {
+ get {
+ return ResourceManager.GetString("LabelMessageType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Raw.
+ ///
+ public static string OptionMessageTypeRaw {
+ get {
+ return ResourceManager.GetString("OptionMessageTypeRaw", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Tapeti.
+ ///
+ public static string OptionMessageTypeTapeti {
+ get {
+ return ResourceManager.GetString("OptionMessageTypeTapeti", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.resx b/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.resx
new file mode 100644
index 0000000..4bddf8b
--- /dev/null
+++ b/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.resx
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Publish
+
+
+ Message type:
+
+
+ Raw
+
+
+ Tapeti
+
+
\ No newline at end of file
diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml b/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml
new file mode 100644
index 0000000..c619885
--- /dev/null
+++ b/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml.cs b/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml.cs
new file mode 100644
index 0000000..e0f6ac8
--- /dev/null
+++ b/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using System.Windows.Media;
+
+namespace PettingZoo.UI.Tab.Subscriber
+{
+ ///
+ /// Interaction logic for SubscriberView.xaml
+ ///
+ public partial class SubscriberView
+ {
+ public SubscriberView(SubscriberViewModel viewModel)
+ {
+ InitializeComponent();
+ DataContext = viewModel;
+
+
+ if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
+ Background = Brushes.Transparent;
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberViewModel.cs b/PettingZoo/UI/Tab/Subscriber/SubscriberViewModel.cs
new file mode 100644
index 0000000..1f695ba
--- /dev/null
+++ b/PettingZoo/UI/Tab/Subscriber/SubscriberViewModel.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using PettingZoo.Core.Connection;
+using PettingZoo.Core.Rendering;
+using PettingZoo.RabbitMQ;
+
+// TODO update title with unread message count if tab is not active
+
+namespace PettingZoo.UI.Tab.Subscriber
+{
+ public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands
+ {
+ private readonly ISubscriber subscriber;
+ private readonly TaskScheduler uiScheduler;
+ private MessageInfo? selectedMessage;
+ private readonly DelegateCommand clearCommand;
+ private readonly TabToolbarCommand[] toolbarCommands;
+
+
+ public ICommand ClearCommand => clearCommand;
+
+ public ObservableCollection Messages { get; }
+
+ public MessageInfo? SelectedMessage
+ {
+ get => selectedMessage;
+ set
+ {
+ if (value == selectedMessage)
+ return;
+
+ selectedMessage = value;
+ RaisePropertyChanged();
+ RaiseOtherPropertyChanged(nameof(SelectedMessageBody));
+ RaiseOtherPropertyChanged(nameof(SelectedMessageProperties));
+ }
+ }
+
+ public string SelectedMessageBody =>
+ SelectedMessage != null
+ ? MessageBodyRenderer.Render(SelectedMessage.Body, SelectedMessage.Properties.ContentType())
+ : "";
+
+ public IDictionary? SelectedMessageProperties => SelectedMessage?.Properties;
+
+ public string Title => $"{subscriber.Exchange} - {subscriber.RoutingKey}";
+ public IEnumerable ToolbarCommands => toolbarCommands;
+
+
+ public SubscriberViewModel(ISubscriber subscriber)
+ {
+ this.subscriber = subscriber;
+
+ uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
+
+ Messages = new ObservableCollection();
+ clearCommand = new DelegateCommand(ClearExecute, ClearCanExecute);
+
+ toolbarCommands = new[]
+ {
+ new TabToolbarCommand(ClearCommand, SubscriberViewStrings.CommandClear, SvgIconHelper.LoadFromResource("/Images/Clear.svg"))
+ };
+
+ subscriber.MessageReceived += SubscriberMessageReceived;
+ subscriber.Start();
+ }
+
+
+ private void ClearExecute()
+ {
+ Messages.Clear();
+ clearCommand.RaiseCanExecuteChanged();
+ }
+
+
+ private bool ClearCanExecute()
+ {
+ return Messages.Count > 0;
+ }
+
+
+ private void SubscriberMessageReceived(object? sender, MessageReceivedEventArgs args)
+ {
+ RunFromUiScheduler(() =>
+ {
+ Messages.Add(args.MessageInfo);
+ clearCommand.RaiseCanExecuteChanged();
+ });
+ }
+
+
+ private void RunFromUiScheduler(Action action)
+ {
+ _ = Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
+ }
+
+ }
+
+
+ public class DesignTimeSubscriberViewModel : SubscriberViewModel
+ {
+ public DesignTimeSubscriberViewModel() : base(new DesignTimeSubscriber())
+ {
+ }
+
+
+ private class DesignTimeSubscriber : ISubscriber
+ {
+ public ValueTask DisposeAsync()
+ {
+ return default;
+ }
+
+
+ public string Exchange { get; } = "dummy";
+ public string RoutingKey { get; } = "dummy";
+
+ #pragma warning disable CS0067
+ public event EventHandler? MessageReceived;
+ #pragma warning restore CS0067
+
+ public void Start()
+ {
+ }
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.Designer.cs b/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.Designer.cs
new file mode 100644
index 0000000..0514a9b
--- /dev/null
+++ b/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.Designer.cs
@@ -0,0 +1,126 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace PettingZoo.UI.Tab.Subscriber {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // 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", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class SubscriberViewStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal SubscriberViewStrings() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [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.Tab.Subscriber.SubscriberViewStrings", typeof(SubscriberViewStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Clear.
+ ///
+ public static string CommandClear {
+ get {
+ return ResourceManager.GetString("CommandClear", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Non-persistent.
+ ///
+ public static string DeliveryModeNonPersistent {
+ get {
+ return ResourceManager.GetString("DeliveryModeNonPersistent", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Persistent.
+ ///
+ public static string DeliveryModePersistent {
+ get {
+ return ResourceManager.GetString("DeliveryModePersistent", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Body.
+ ///
+ public static string PanelTitleBody {
+ get {
+ return ResourceManager.GetString("PanelTitleBody", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Properties.
+ ///
+ public static string PanelTitleProperties {
+ get {
+ return ResourceManager.GetString("PanelTitleProperties", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Name.
+ ///
+ public static string PropertyName {
+ get {
+ return ResourceManager.GetString("PropertyName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Value.
+ ///
+ public static string PropertyValue {
+ get {
+ return ResourceManager.GetString("PropertyValue", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.resx b/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.resx
new file mode 100644
index 0000000..f056b06
--- /dev/null
+++ b/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.resx
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Clear
+
+
+ Non-persistent
+
+
+ Persistent
+
+
+ Body
+
+
+ Properties
+
+
+ Name
+
+
+ Value
+
+
\ No newline at end of file
diff --git a/PettingZoo/UI/Tab/ViewTab.cs b/PettingZoo/UI/Tab/ViewTab.cs
new file mode 100644
index 0000000..470d16d
--- /dev/null
+++ b/PettingZoo/UI/Tab/ViewTab.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace PettingZoo.UI.Tab
+{
+ public class ViewTab : ITab where TView : ContentControl where TViewModel : INotifyPropertyChanged
+ {
+ public string Title => getTitle(viewModel);
+ public ContentControl Content { get; }
+ public ICommand CloseTabCommand { get; }
+
+ public IEnumerable ToolbarCommands => viewModel is ITabToolbarCommands tabToolbarCommands
+ ? tabToolbarCommands.ToolbarCommands
+ : Enumerable.Empty();
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+
+ private readonly TViewModel viewModel;
+ private readonly Func getTitle;
+
+
+ public ViewTab(ICommand closeTabCommand, TView view, TViewModel viewModel, Expression> title)
+ {
+ if (title.Body is not MemberExpression titleMemberExpression)
+ throw new ArgumentException(@"Invalid expression type, expected viewModel => viewModel.TitlePropertyName", nameof(title));
+
+ var titlePropertyName = titleMemberExpression.Member.Name;
+
+ CloseTabCommand = closeTabCommand;
+ this.viewModel = viewModel;
+ getTitle = title.Compile();
+ Content = view;
+
+
+ viewModel.PropertyChanged += (_, args) =>
+ {
+ if (args.PropertyName == titlePropertyName)
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
+
+ else if (args.PropertyName == nameof(ToolbarCommands))
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ToolbarCommands)));
+ };
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/ViewTabFactory.cs b/PettingZoo/UI/Tab/ViewTabFactory.cs
new file mode 100644
index 0000000..6f39ccc
--- /dev/null
+++ b/PettingZoo/UI/Tab/ViewTabFactory.cs
@@ -0,0 +1,31 @@
+using System.Windows.Input;
+using PettingZoo.Core.Connection;
+using PettingZoo.UI.Tab.Publisher;
+using PettingZoo.UI.Tab.Subscriber;
+
+namespace PettingZoo.UI.Tab
+{
+ public class ViewTabFactory : ITabFactory
+ {
+ public ITab CreateSubscriberTab(ICommand closeTabCommand, ISubscriber subscriber)
+ {
+ var viewModel = new SubscriberViewModel(subscriber);
+ return new ViewTab(
+ closeTabCommand,
+ new SubscriberView(viewModel),
+ viewModel,
+ vm => vm.Title);
+ }
+
+
+ public ITab CreatePublisherTab(ICommand closeTabCommand, IConnection connection)
+ {
+ var viewModel = new PublisherViewModel(connection);
+ return new ViewTab(
+ closeTabCommand,
+ new PublisherView(viewModel),
+ viewModel,
+ vm => vm.Title);
+ }
+ }
+}
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
deleted file mode 100644
index 0eebe89..0000000
--- a/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Windows;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Petting Zoo")]
-[assembly: AssemblyDescription("Petting Zoo - a RabbitMQ message viewer")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("X²Software")]
-[assembly: AssemblyProduct("Petting Zoo")]
-[assembly: AssemblyCopyright("Copyright © 2016")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-//In order to begin building localizable applications, set
-//CultureYouAreCodingWith in your .csproj file
-//inside a . For example, if you are using US english
-//in your source files, set the to en-US. Then uncomment
-//the NeutralResourceLanguage attribute below. Update the "en-US" in
-//the line below to match the UICulture setting in the project file.
-
-//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
-
-
-[assembly: ThemeInfo(
- ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
- //(used if a resource is not found in the page,
- // or application resource dictionaries)
- ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
- //(used if a resource is not found in the page,
- // app, or any theme specific resource dictionaries)
-)]
-
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
-[assembly: GuidAttribute("1739c968-ca2a-4293-b487-bfa193a7caf3")]
diff --git a/View/ConnectionWindow.xaml.cs b/View/ConnectionWindow.xaml.cs
deleted file mode 100644
index d0e94a0..0000000
--- a/View/ConnectionWindow.xaml.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using System.Windows;
-using System.Windows.Input;
-using AutoMapper;
-using PettingZoo.Model;
-using PettingZoo.ViewModel;
-
-namespace PettingZoo.View
-{
- public class WindowConnectionInfoBuilder : IConnectionInfoBuilder
- {
- private readonly UserSettings userSettings;
-
- private static readonly IMapper ConnectionInfoMapper = new MapperConfiguration(cfg =>
- {
- cfg.RecognizeDestinationPrefixes("Last");
- cfg.RecognizePrefixes("Last");
-
- cfg.CreateMap().ReverseMap();
- }).CreateMapper();
-
-
- public WindowConnectionInfoBuilder(UserSettings userSettings)
- {
- this.userSettings = userSettings;
- }
-
-
- public ConnectionInfo Build()
- {
- var connectionInfo = ConnectionInfoMapper.Map(userSettings.ConnectionWindow);
- var viewModel = new ConnectionViewModel(connectionInfo);
-
- var dialog = new ConnectionWindow(viewModel)
- {
- Owner = Application.Current.MainWindow
- };
-
- viewModel.CloseWindow += (sender, args) =>
- {
- dialog.DialogResult = true;
- };
-
- connectionInfo = dialog.ShowDialog().GetValueOrDefault() ? viewModel.ToModel() : null;
- if (connectionInfo != null)
- {
- ConnectionInfoMapper.Map(connectionInfo, userSettings.ConnectionWindow);
- userSettings.Save();
- }
-
- return connectionInfo;
- }
- }
-
-
- public partial class ConnectionWindow
- {
- public ConnectionWindow(ConnectionViewModel viewModel)
- {
- WindowStartupLocation = WindowStartupLocation.CenterOwner;
-
- InitializeComponent();
- DataContext = viewModel;
- }
-
-
- private void NumericPreviewTextInput(object sender, TextCompositionEventArgs args)
- {
- if (!char.IsDigit(args.Text, args.Text.Length - 1))
- args.Handled = true;
- }
- }
-}
diff --git a/View/MainWindow.xaml b/View/MainWindow.xaml
deleted file mode 100644
index 1f907a8..0000000
--- a/View/MainWindow.xaml
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/View/MainWindow.xaml.cs b/View/MainWindow.xaml.cs
deleted file mode 100644
index 9cce7cd..0000000
--- a/View/MainWindow.xaml.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Windows;
-using PettingZoo.ViewModel;
-
-namespace PettingZoo.View
-{
- public partial class MainWindow
- {
- public MainWindow(MainViewModel viewModel)
- {
- WindowStartupLocation = WindowStartupLocation.CenterScreen;
-
- InitializeComponent();
- DataContext = viewModel;
-
- Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted;
- }
-
-
- private void OnDispatcherShutDownStarted(object sender, EventArgs e)
- {
- var disposable = DataContext as IDisposable;
- if (!ReferenceEquals(null, disposable))
- disposable.Dispose();
- }
- }
-}
diff --git a/ViewModel/ConnectionViewModel.cs b/ViewModel/ConnectionViewModel.cs
deleted file mode 100644
index 367eb50..0000000
--- a/ViewModel/ConnectionViewModel.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System;
-using System.Windows.Input;
-using AutoMapper;
-using PettingZoo.Infrastructure;
-using PettingZoo.Model;
-
-namespace PettingZoo.ViewModel
-{
- public class ConnectionViewModel : BaseViewModel
- {
- private static readonly IMapper ModelMapper = new MapperConfiguration(cfg =>
- cfg.CreateMap().ReverseMap()
- ).CreateMapper();
-
-
- private readonly DelegateCommand okCommand;
-
-
- 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; }
-
-
- public ICommand OkCommand { get { return okCommand; } }
-
- public event EventHandler CloseWindow;
-
-
- public ConnectionViewModel()
- {
- okCommand = new DelegateCommand(OkExecute, OkCanExecute);
- }
-
-
- public ConnectionViewModel(ConnectionInfo model) : this()
- {
- ModelMapper.Map(model, this);
- }
-
-
- public ConnectionInfo ToModel()
- {
- return ModelMapper.Map(this);
- }
-
-
- private void OkExecute()
- {
- if (CloseWindow != null)
- CloseWindow(this, EventArgs.Empty);
- }
-
-
- private bool OkCanExecute()
- {
- return true;
- }
- }
-}
diff --git a/ViewModel/MainViewModel.cs b/ViewModel/MainViewModel.cs
deleted file mode 100644
index e392b36..0000000
--- a/ViewModel/MainViewModel.cs
+++ /dev/null
@@ -1,210 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows.Input;
-using PettingZoo.Infrastructure;
-using PettingZoo.Model;
-using PettingZoo.Properties;
-
-namespace PettingZoo.ViewModel
-{
- public class MainViewModel : BaseViewModel, IDisposable
- {
- private readonly TaskScheduler uiScheduler;
- private readonly IConnectionInfoBuilder connectionInfoBuilder;
- private readonly IConnectionFactory connectionFactory;
-
- private ConnectionInfo connectionInfo;
- private IConnection connection;
- private string connectionStatus;
- private readonly ObservableCollection messages;
- private MessageInfo selectedMessage;
-
- private readonly DelegateCommand connectCommand;
- private readonly DelegateCommand disconnectCommand;
- private readonly DelegateCommand clearCommand;
-
-
- public ConnectionInfo ConnectionInfo {
- get { return connectionInfo; }
- private set
- {
- if (value == connectionInfo)
- return;
-
- connectionInfo = value;
- RaisePropertyChanged();
- }
- }
-
- public string ConnectionStatus
- {
- get { return connectionStatus; }
- private set
- {
- if (value == connectionStatus)
- return;
-
- connectionStatus = value;
- RaisePropertyChanged();
- }
- }
-
- public ObservableCollection Messages { get { return messages; } }
-
- public MessageInfo SelectedMessage
- {
- get { return selectedMessage; }
- set
- {
- if (value == selectedMessage)
- return;
-
- selectedMessage = value;
- RaisePropertyChanged();
- RaiseOtherPropertyChanged("SelectedMessageBody");
- RaiseOtherPropertyChanged("SelectedMessageProperties");
- }
- }
-
- public string SelectedMessageBody
- {
- get
- {
- return SelectedMessage != null
- ? MessageBodyRenderer.Render(SelectedMessage.Body, SelectedMessage.ContentType)
- : "";
- }
- }
-
- public Dictionary SelectedMessageProperties
- {
- get { return SelectedMessage != null ? SelectedMessage.Properties : null; }
- }
-
- public ICommand ConnectCommand { get { return connectCommand; } }
- public ICommand DisconnectCommand { get { return disconnectCommand; } }
- public ICommand ClearCommand { get { return clearCommand; } }
-
-
- public MainViewModel(IConnectionInfoBuilder connectionInfoBuilder, IConnectionFactory connectionFactory)
- {
- uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
-
- this.connectionInfoBuilder = connectionInfoBuilder;
- this.connectionFactory = connectionFactory;
-
- connectionStatus = GetConnectionStatus(null);
- messages = new ObservableCollection();
-
- connectCommand = new DelegateCommand(ConnectExecute);
- disconnectCommand = new DelegateCommand(DisconnectExecute, DisconnectCanExecute);
- clearCommand = new DelegateCommand(ClearExecute, ClearCanExecute);
- }
-
-
- public void Dispose()
- {
- if (connection != null)
- {
- connection.Dispose();
- connection = null;
- }
- }
-
-
- private void ConnectExecute()
- {
- var newInfo = connectionInfoBuilder.Build();
- if (newInfo == null)
- return;
-
- if (connection != null)
- connection.Dispose();
-
- ConnectionInfo = newInfo;
- connection = connectionFactory.CreateConnection(connectionInfo);
- connection.MessageReceived += ConnectionMessageReceived;
- connection.StatusChanged += ConnectionStatusChanged;
-
- disconnectCommand.RaiseCanExecuteChanged();
- }
-
-
- private void DisconnectExecute()
- {
- if (connection != null)
- {
- connection.Dispose();
- connection = null;
- }
-
- ConnectionInfo = null;
- ConnectionStatus = GetConnectionStatus(null);
-
- disconnectCommand.RaiseCanExecuteChanged();
- }
-
-
- private bool DisconnectCanExecute()
- {
- return connection != null;
- }
-
-
- private void ClearExecute()
- {
- messages.Clear();
- clearCommand.RaiseCanExecuteChanged();
- }
-
-
- private bool ClearCanExecute()
- {
- return messages.Count > 0;
- }
-
-
- private void ConnectionStatusChanged(object sender, StatusChangedEventArgs args)
- {
- ConnectionStatus = GetConnectionStatus(args);
- }
-
-
- private void ConnectionMessageReceived(object sender, MessageReceivedEventArgs args)
- {
- RunFromUiScheduler(() =>
- {
- messages.Add(args.MessageInfo);
- clearCommand.RaiseCanExecuteChanged();
- });
- }
-
-
- private string GetConnectionStatus(StatusChangedEventArgs args)
- {
- if (args != null)
- switch (args.Status)
- {
- case Model.ConnectionStatus.Connecting:
- return String.Format(Resources.StatusConnecting, args.Context);
-
- case Model.ConnectionStatus.Connected:
- return String.Format(Resources.StatusConnected, args.Context);
-
- case Model.ConnectionStatus.Error:
- return String.Format(Resources.StatusError, args.Context);
- }
-
- return Resources.StatusDisconnected;
- }
-
-
- private void RunFromUiScheduler(Action action)
- {
- Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
- }
- }
-}
\ No newline at end of file
diff --git a/packages.config b/packages.config
deleted file mode 100644
index 7458215..0000000
--- a/packages.config
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file