1
0
mirror of synced 2024-11-24 19:53:09 +01:00

Implemented connection profiles

Added unread messages counter to deactivated subscriber tabs
Added feature to reply to a new subscriber tab
This commit is contained in:
Mark van Renswoude 2021-12-18 12:18:35 +01:00
parent 4817b1806a
commit 2e6524f3b9
51 changed files with 1483 additions and 675 deletions

View File

@ -8,6 +8,8 @@ namespace PettingZoo.Core.Connection
event EventHandler<StatusChangedEventArgs> StatusChanged; event EventHandler<StatusChangedEventArgs> StatusChanged;
ISubscriber Subscribe(string exchange, string routingKey); ISubscriber Subscribe(string exchange, string routingKey);
ISubscriber Subscribe();
Task Publish(PublishMessageInfo messageInfo); Task Publish(PublishMessageInfo messageInfo);
} }

View File

@ -4,8 +4,9 @@ namespace PettingZoo.Core.Connection
{ {
public interface ISubscriber : IAsyncDisposable public interface ISubscriber : IAsyncDisposable
{ {
string Exchange {get; } string? QueueName { get; }
string RoutingKey { get; } string? Exchange {get; }
string? RoutingKey { get; }
event EventHandler<MessageReceivedEventArgs>? MessageReceived; event EventHandler<MessageReceivedEventArgs>? MessageReceived;

View File

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

View File

@ -46,18 +46,32 @@ namespace PettingZoo.RabbitMQ
connection = null; connection = null;
} }
} }
GC.SuppressFinalize(this);
} }
public ISubscriber Subscribe(string exchange, string routingKey) public ISubscriber Subscribe(string exchange, string routingKey)
{
return CreateSubscriber(exchange, routingKey);
}
public ISubscriber Subscribe()
{
return CreateSubscriber(null, null);
}
private ISubscriber CreateSubscriber(string? exchange, string? routingKey)
{ {
lock (connectionLock) lock (connectionLock)
{ {
var subscriber = new RabbitMQClientSubscriber(model, exchange, routingKey); var subscriber = new RabbitMQClientSubscriber(model, exchange, routingKey);
if (model != null) if (model != null)
return subscriber; return subscriber;
void ConnectSubscriber(object? sender, StatusChangedEventArgs args) void ConnectSubscriber(object? sender, StatusChangedEventArgs args)
{ {
if (args.Status != ConnectionStatus.Connected) if (args.Status != ConnectionStatus.Connected)
@ -67,20 +81,20 @@ namespace PettingZoo.RabbitMQ
{ {
if (model == null) if (model == null)
return; return;
subscriber.Connected(model); subscriber.Connected(model);
} }
StatusChanged -= ConnectSubscriber; StatusChanged -= ConnectSubscriber;
} }
StatusChanged += ConnectSubscriber; StatusChanged += ConnectSubscriber;
return subscriber; return subscriber;
} }
} }
public Task Publish(PublishMessageInfo messageInfo) public Task Publish(PublishMessageInfo messageInfo)
{ {
if (model == null) if (model == null)

View File

@ -13,12 +13,13 @@ namespace PettingZoo.RabbitMQ
private string? consumerTag; private string? consumerTag;
private bool started; private bool started;
public string Exchange { get; } public string? QueueName { get; private set; }
public string RoutingKey { get; } public string? Exchange { get; }
public string? RoutingKey { get; }
public event EventHandler<MessageReceivedEventArgs>? MessageReceived; public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public RabbitMQClientSubscriber(IModel? model, string exchange, string routingKey) public RabbitMQClientSubscriber(IModel? model, string? exchange, string? routingKey)
{ {
this.model = model; this.model = model;
Exchange = exchange; Exchange = exchange;
@ -28,6 +29,8 @@ namespace PettingZoo.RabbitMQ
public ValueTask DisposeAsync() public ValueTask DisposeAsync()
{ {
GC.SuppressFinalize(this);
if (model != null && consumerTag != null && model.IsOpen) if (model != null && consumerTag != null && model.IsOpen)
model.BasicCancelNoWait(consumerTag); model.BasicCancelNoWait(consumerTag);
@ -41,13 +44,14 @@ namespace PettingZoo.RabbitMQ
if (model == null) if (model == null)
return; return;
var queueName = model.QueueDeclare().QueueName; QueueName = model.QueueDeclare().QueueName;
model.QueueBind(queueName, Exchange, RoutingKey); if (Exchange != null && RoutingKey != null)
model.QueueBind(QueueName, Exchange, RoutingKey);
var consumer = new EventingBasicConsumer(model); var consumer = new EventingBasicConsumer(model);
consumer.Received += ClientReceived; consumer.Received += ClientReceived;
consumerTag = model.BasicConsume(queueName, true, consumer); consumerTag = model.BasicConsume(QueueName, true, consumer);
} }

View File

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

View File

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

View File

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

View File

@ -14,7 +14,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.Core", "PettingZ
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.RabbitMQ", "PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj", "{220149F3-A8D6-44ED-B3B6-DFE506EB018A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.RabbitMQ", "PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj", "{220149F3-A8D6-44ED-B3B6-DFE506EB018A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo.Tapeti", "PettingZoo.Tapeti\PettingZoo.Tapeti.csproj", "{1763AB04-59D9-4663-B207-D6302FFAACD5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.Tapeti", "PettingZoo.Tapeti\PettingZoo.Tapeti.csproj", "{1763AB04-59D9-4663-B207-D6302FFAACD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo.Settings.LiteDB", "PettingZoo.Settings.LiteDB\PettingZoo.Settings.LiteDB.csproj", "{7157B09C-FDD9-4928-B14D-C25B784CA865}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -38,6 +40,10 @@ Global
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {1763AB04-59D9-4663-B207-D6302FFAACD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.ActiveCfg = Release|Any CPU {1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.Build.0 = Release|Any CPU {1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.Build.0 = Release|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

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

View File

@ -39,9 +39,15 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PettingZoo.Core\PettingZoo.Core.csproj" /> <ProjectReference Include="..\PettingZoo.Core\PettingZoo.Core.csproj" />
<ProjectReference Include="..\PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj" /> <ProjectReference Include="..\PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj" />
<ProjectReference Include="..\PettingZoo.Settings.LiteDB\PettingZoo.Settings.LiteDB.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="UI\Connection\ConnectionDisplayNameStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ConnectionDisplayNameStrings.resx</DependentUpon>
</Compile>
<Compile Update="UI\Connection\ConnectionWindowStrings.Designer.cs"> <Compile Update="UI\Connection\ConnectionWindowStrings.Designer.cs">
<DependentUpon>ConnectionWindowStrings.resx</DependentUpon> <DependentUpon>ConnectionWindowStrings.resx</DependentUpon>
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
@ -88,6 +94,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Update="UI\Connection\ConnectionDisplayNameStrings.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>ConnectionDisplayNameStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="UI\Connection\ConnectionWindowStrings.resx"> <EmbeddedResource Update="UI\Connection\ConnectionWindowStrings.resx">
<LastGenOutput>ConnectionWindowStrings.Designer.cs</LastGenOutput> <LastGenOutput>ConnectionWindowStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator> <Generator>PublicResXFileCodeGenerator</Generator>

View File

@ -1,17 +1,14 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows; using System.Windows;
using System.Windows.Markup; using System.Windows.Markup;
using Newtonsoft.Json;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using PettingZoo.Core.Settings;
using PettingZoo.RabbitMQ; using PettingZoo.RabbitMQ;
using PettingZoo.Settings; using PettingZoo.Settings.LiteDB;
using PettingZoo.UI.Connection; using PettingZoo.UI.Connection;
using PettingZoo.UI.Main; using PettingZoo.UI.Main;
using PettingZoo.UI.Subscribe; using PettingZoo.UI.Subscribe;
using PettingZoo.UI.Tab;
using SimpleInjector; using SimpleInjector;
namespace PettingZoo namespace PettingZoo
@ -37,11 +34,10 @@ namespace PettingZoo
// See comments in RunApplication // See comments in RunApplication
container.Options.EnableAutoVerification = false; container.Options.EnableAutoVerification = false;
container.RegisterSingleton(() => new UserSettings(new AppDataSettingsSerializer("Settings.json")));
container.Register<IConnectionFactory, RabbitMQClientConnectionFactory>(); container.Register<IConnectionFactory, RabbitMQClientConnectionFactory>();
container.Register<IConnectionDialog, WindowConnectionDialog>(); container.Register<IConnectionDialog, WindowConnectionDialog>();
container.Register<ISubscribeDialog, WindowSubscribeDialog>(); container.Register<ISubscribeDialog, WindowSubscribeDialog>();
container.Register<IConnectionSettingsRepository, LiteDBConnectionSettingsRepository>();
container.Register<MainWindow>(); container.Register<MainWindow>();
@ -72,50 +68,9 @@ namespace PettingZoo
var mainWindow = container.GetInstance<MainWindow>(); var mainWindow = container.GetInstance<MainWindow>();
_ = app.Run(mainWindow); _ = app.Run(mainWindow);
} }
catch (Exception) catch (Exception e)
{ {
// TODO Log the exception and exit MessageBox.Show($"Fatal exception: {e.Message}", @"PettingZoo", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private class AppDataSettingsSerializer : IUserSettingsSerializer
{
private readonly string path;
private readonly string fullPath;
public AppDataSettingsSerializer(string filename)
{
var companyName = GetProductInfo<AssemblyCompanyAttribute>().Company;
var productName = GetProductInfo<AssemblyProductAttribute>().Product;
path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
companyName, productName);
fullPath = Path.Combine(path, filename);
}
public void Read(UserSettings settings)
{
if (File.Exists(fullPath))
JsonConvert.PopulateObject(File.ReadAllText(fullPath), settings);
}
public void Write(UserSettings settings)
{
_ = Directory.CreateDirectory(path);
File.WriteAllText(fullPath, JsonConvert.SerializeObject(settings, Formatting.Indented));
}
private T GetProductInfo<T>()
{
var attributes = GetType().Assembly.GetCustomAttributes(typeof(T), true);
return attributes.Length == 0
? throw new Exception("Missing product information in assembly")
: (T) attributes[0];
} }
} }
} }

View File

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

View File

@ -33,8 +33,12 @@
<Style x:Key="FooterButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}"> <Style x:Key="FooterButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="8,0,0,0" /> <Setter Property="Margin" Value="8,0,0,0" />
</Style> </Style>
<Style x:Key="FooterButtonLeft" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="0,0,8,0" />
</Style>
<Style x:Key="Form" TargetType="{x:Type ui:GridLayout}"> <Style x:Key="Form" TargetType="{x:Type ui:GridLayout}">
<Setter Property="ChildMargin" Value="4"/> <Setter Property="ChildMargin" Value="4"/>
</Style> </Style>
@ -78,4 +82,11 @@
<Setter Property="FontWeight" Value="Bold"/> <Setter Property="FontWeight" Value="Bold"/>
</Style> </Style>
<Style x:Key="Payload" TargetType="{x:Type TextBox}">
<Setter Property="AcceptsReturn" Value="True" />
<Setter Property="AcceptsTab" Value="True" />
<Setter Property="VerticalScrollBarVisibility" Value="Visible" />
<Setter Property="FontFamily" Value="Consolas,Courier New" />
</Style>
</ResourceDictionary> </ResourceDictionary>

16
PettingZoo/TODO.md Normal file
View File

@ -0,0 +1,16 @@
Must-have
---------
Should-have
-----------
- Support undocking tabs (and redocking afterwards)
- Allow tab reordering
- Save / load publisher messages (either as templates or to disk)
- Export received messages to Tapeti JSON file / Tapeti.Cmd command-line
Nice-to-have
------------
- JSON validation
- JSON syntax highlighting

View File

@ -14,14 +14,9 @@ namespace PettingZoo.UI
} }
protected virtual void RaiseOtherPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, IEqualityComparer<T>? comparer = null, [CallerMemberName] string? propertyName = null, protected bool SetField<T>(ref T field, T value, IEqualityComparer<T>? comparer = null, [CallerMemberName] string? propertyName = null,
params string[]? otherPropertiesChanged) DelegateCommand[]? delegateCommandsChanged = null,
string[]? otherPropertiesChanged = null)
{ {
if ((comparer ?? EqualityComparer<T>.Default).Equals(field, value)) if ((comparer ?? EqualityComparer<T>.Default).Equals(field, value))
return false; return false;
@ -29,12 +24,18 @@ namespace PettingZoo.UI
field = value; field = value;
RaisePropertyChanged(propertyName); RaisePropertyChanged(propertyName);
if (otherPropertiesChanged == null) if (otherPropertiesChanged != null)
return true; {
foreach (var otherProperty in otherPropertiesChanged)
foreach (var otherProperty in otherPropertiesChanged) RaisePropertyChanged(otherProperty);
RaisePropertyChanged(otherProperty); }
if (delegateCommandsChanged != null)
{
foreach (var delegateCommand in delegateCommandsChanged)
delegateCommand.RaiseCanExecuteChanged();
}
return true; return true;
} }
} }

View File

@ -0,0 +1,25 @@
<Window x:Class="PettingZoo.UI.Connection.ConnectionDisplayNameDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PettingZoo.UI.Connection"
mc:Ignorable="d"
Width="400"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStyle="ToolWindow"
WindowStartupLocation="CenterOwner"
Style="{StaticResource WindowStyle}"
Title="{x:Static local:ConnectionDisplayNameStrings.WindowTitle}"
FocusManager.FocusedElement="{Binding ElementName=DisplayNameTextBox}"
d:DataContext="{d:DesignInstance local:ConnectionDisplayNameViewModel}">
<StackPanel Margin="8">
<TextBox Name="DisplayNameTextBox" Text="{Binding DisplayName, UpdateSourceTrigger=PropertyChanged}" />
<UniformGrid HorizontalAlignment="Right" Rows="1" Columns="2" Style="{StaticResource FooterPanel}">
<Button IsDefault="True" Content="{x:Static local:ConnectionDisplayNameStrings.ButtonOK}" Style="{StaticResource FooterButton}" Command="{Binding OkCommand}"/>
<Button IsCancel="True" Content="{x:Static local:ConnectionDisplayNameStrings.ButtonCancel}" Style="{StaticResource FooterButton}"/>
</UniformGrid>
</StackPanel>
</Window>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,20 +7,49 @@
xmlns:connection="clr-namespace:PettingZoo.UI.Connection" xmlns:connection="clr-namespace:PettingZoo.UI.Connection"
mc:Ignorable="d" mc:Ignorable="d"
d:DataContext="{d:DesignInstance connection:DesignTimeConnectionViewModel, IsDesignTimeCreatable = True}" d:DataContext="{d:DesignInstance connection:DesignTimeConnectionViewModel, IsDesignTimeCreatable = True}"
Width="500" Width="700"
SizeToContent="Height" SizeToContent="Height"
ResizeMode="NoResize" ResizeMode="NoResize"
WindowStyle="ToolWindow" WindowStyle="ToolWindow"
Style="{StaticResource WindowStyle}" Style="{StaticResource WindowStyle}"
Title="{x:Static connection:ConnectionWindowStrings.WindowTitle}" Title="{x:Static connection:ConnectionWindowStrings.WindowTitle}"
FocusManager.FocusedElement="{Binding ElementName=HostTextBox}"> FocusManager.FocusedElement="{Binding ElementName=HostTextBox}"
<DockPanel Margin="8"> WindowStartupLocation="CenterOwner">
<UniformGrid DockPanel.Dock="Bottom" HorizontalAlignment="Right" Rows="1" Columns="2" Style="{StaticResource FooterPanel}"> <Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<UniformGrid Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" Rows="1" Columns="1" Style="{StaticResource FooterPanel}">
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonDelete}" Style="{StaticResource FooterButtonLeft}" Command="{Binding DeleteCommand}"/>
</UniformGrid>
<UniformGrid Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Rows="1" Columns="3" Style="{StaticResource FooterPanel}">
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonSave}" Style="{StaticResource FooterButtonLeft}" Command="{Binding SaveCommand}"/>
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonSaveAs}" Style="{StaticResource FooterButtonLeft}" Command="{Binding SaveAsCommand}"/>
</UniformGrid>
<UniformGrid Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" Rows="1" Columns="2" Style="{StaticResource FooterPanel}">
<Button IsDefault="True" Content="{x:Static connection:ConnectionWindowStrings.ButtonOK}" Style="{StaticResource FooterButton}" Command="{Binding OkCommand}"/> <Button IsDefault="True" Content="{x:Static connection:ConnectionWindowStrings.ButtonOK}" Style="{StaticResource FooterButton}" Command="{Binding OkCommand}"/>
<Button IsCancel="True" Content="{x:Static connection:ConnectionWindowStrings.ButtonCancel}" Style="{StaticResource FooterButton}"/> <Button IsCancel="True" Content="{x:Static connection:ConnectionWindowStrings.ButtonCancel}" Style="{StaticResource FooterButton}"/>
</UniformGrid> </UniformGrid>
<ui:GridLayout Style="{StaticResource Form}"> <ListBox Grid.Row="0" Grid.Column="0" Margin="0 0 8 0" Width="250" ItemsSource="{Binding StoredConnections}" SelectedValue="{Binding SelectedStoredConnection}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ui:GridLayout Style="{StaticResource Form}" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@ -37,28 +66,28 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static connection:ConnectionWindowStrings.LabelHost}"/> <Label Grid.Column="0" Grid.Row="0" Content="{x:Static connection:ConnectionWindowStrings.LabelHost}"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Host}" Name="HostTextBox" /> <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Host, UpdateSourceTrigger=PropertyChanged}" Name="HostTextBox" GotFocus="CaretToEnd" />
<Label Grid.Column="0" Grid.Row="1" Content="{x:Static connection:ConnectionWindowStrings.LabelPort}"/> <Label Grid.Column="0" Grid.Row="1" Content="{x:Static connection:ConnectionWindowStrings.LabelPort}"/>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Port}" Width="100" HorizontalAlignment="Left" PreviewTextInput="NumericPreviewTextInput" /> <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}" Width="100" HorizontalAlignment="Left" PreviewTextInput="NumericPreviewTextInput" GotFocus="CaretToEnd" />
<Label Grid.Column="0" Grid.Row="2" Content="{x:Static connection:ConnectionWindowStrings.LabelVirtualHost}"/> <Label Grid.Column="0" Grid.Row="2" Content="{x:Static connection:ConnectionWindowStrings.LabelVirtualHost}"/>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding VirtualHost}"/> <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding VirtualHost, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
<Label Grid.Column="0" Grid.Row="3" Content="{x:Static connection:ConnectionWindowStrings.LabelUsername}"/> <Label Grid.Column="0" Grid.Row="3" Content="{x:Static connection:ConnectionWindowStrings.LabelUsername}"/>
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Username}"/> <TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
<Label Grid.Column="0" Grid.Row="4" Content="{x:Static connection:ConnectionWindowStrings.LabelPassword}"/> <Label Grid.Column="0" Grid.Row="4" Content="{x:Static connection:ConnectionWindowStrings.LabelPassword}"/>
<PasswordBox Grid.Column="1" Grid.Row="4" ui:PasswordBoxAssistant.BindPassword="true" ui:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <PasswordBox Grid.Column="1" Grid.Row="4" ui:PasswordBoxAssistant.BindPassword="true" ui:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
<CheckBox Grid.Column="1" Grid.Row="6" Content="{x:Static connection:ConnectionWindowStrings.LabelSubscribe}" IsChecked="{Binding Subscribe}"/> <CheckBox Grid.Column="1" Grid.Row="6" Content="{x:Static connection:ConnectionWindowStrings.LabelSubscribe}" IsChecked="{Binding Subscribe}"/>
<Label Grid.Column="0" Grid.Row="7" Content="{x:Static connection:ConnectionWindowStrings.LabelExchange}"/> <Label Grid.Column="0" Grid.Row="7" Content="{x:Static connection:ConnectionWindowStrings.LabelExchange}"/>
<TextBox Grid.Column="1" Grid.Row="7" Text="{Binding Exchange}"/> <TextBox Grid.Column="1" Grid.Row="7" Text="{Binding Exchange, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
<Label Grid.Column="0" Grid.Row="8" Content="{x:Static connection:ConnectionWindowStrings.LabelRoutingKey}"/> <Label Grid.Column="0" Grid.Row="8" Content="{x:Static connection:ConnectionWindowStrings.LabelRoutingKey}"/>
<TextBox Grid.Column="1" Grid.Row="8" Text="{Binding RoutingKey}"/> <TextBox Grid.Column="1" Grid.Row="8" Text="{Binding RoutingKey, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
</ui:GridLayout> </ui:GridLayout>
</DockPanel> </Grid>
</Window> </Window>

View File

@ -1,13 +1,28 @@
using System.Windows; using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using PettingZoo.Core.Settings;
namespace PettingZoo.UI.Connection namespace PettingZoo.UI.Connection
{ {
public class WindowConnectionDialog : IConnectionDialog public class WindowConnectionDialog : IConnectionDialog
{ {
public ConnectionDialogParams? Show(ConnectionDialogParams? defaultParams = null) private readonly IConnectionSettingsRepository connectionSettingsRepository;
public WindowConnectionDialog(IConnectionSettingsRepository connectionSettingsRepository)
{ {
var viewModel = new ConnectionViewModel(defaultParams ?? ConnectionDialogParams.Default); this.connectionSettingsRepository = connectionSettingsRepository;
}
public async Task<ConnectionSettings?> Show()
{
var lastUsed = await connectionSettingsRepository.GetLastUsed();
var viewModel = new ConnectionViewModel(connectionSettingsRepository, lastUsed);
await viewModel.Initialize();
var window = new ConnectionWindow(viewModel) var window = new ConnectionWindow(viewModel)
{ {
Owner = Application.Current.MainWindow Owner = Application.Current.MainWindow
@ -17,10 +32,15 @@ namespace PettingZoo.UI.Connection
{ {
window.DialogResult = true; window.DialogResult = true;
}; };
return window.ShowDialog().GetValueOrDefault() if (!window.ShowDialog().GetValueOrDefault())
? viewModel.ToModel() return null;
: null;
var newSettings = viewModel.ToModel();
await connectionSettingsRepository.StoreLastUsed(newSettings);
return newSettings;
} }
} }
@ -29,10 +49,8 @@ namespace PettingZoo.UI.Connection
{ {
public ConnectionWindow(ConnectionViewModel viewModel) public ConnectionWindow(ConnectionViewModel viewModel)
{ {
WindowStartupLocation = WindowStartupLocation.CenterOwner;
InitializeComponent();
DataContext = viewModel; DataContext = viewModel;
InitializeComponent();
} }
@ -41,5 +59,14 @@ namespace PettingZoo.UI.Connection
if (!char.IsDigit(args.Text, args.Text.Length - 1)) if (!char.IsDigit(args.Text, args.Text.Length - 1))
args.Handled = true; args.Handled = true;
} }
private void CaretToEnd(object sender, RoutedEventArgs e)
{
if (sender is not TextBox textBox)
return;
textBox.CaretIndex = textBox.Text.Length;
}
} }
} }

View File

@ -19,7 +19,7 @@ namespace PettingZoo.UI.Connection {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ConnectionWindowStrings { public class ConnectionWindowStrings {
@ -70,7 +70,16 @@ namespace PettingZoo.UI.Connection {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to OK. /// Looks up a localized string similar to Delete.
/// </summary>
public static string ButtonDelete {
get {
return ResourceManager.GetString("ButtonDelete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connect.
/// </summary> /// </summary>
public static string ButtonOK { public static string ButtonOK {
get { get {
@ -79,7 +88,43 @@ namespace PettingZoo.UI.Connection {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Exchange:. /// Looks up a localized string similar to Save.
/// </summary>
public static string ButtonSave {
get {
return ResourceManager.GetString("ButtonSave", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save as....
/// </summary>
public static string ButtonSaveAs {
get {
return ResourceManager.GetString("ButtonSaveAs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Do you want to delete the connection settings &quot;{0}&quot;?.
/// </summary>
public static string DeleteConfirm {
get {
return ResourceManager.GetString("DeleteConfirm", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete connection.
/// </summary>
public static string DeleteConfirmTitle {
get {
return ResourceManager.GetString("DeleteConfirmTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exchange.
/// </summary> /// </summary>
public static string LabelExchange { public static string LabelExchange {
get { get {
@ -88,7 +133,7 @@ namespace PettingZoo.UI.Connection {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Host:. /// Looks up a localized string similar to Host.
/// </summary> /// </summary>
public static string LabelHost { public static string LabelHost {
get { get {
@ -97,7 +142,7 @@ namespace PettingZoo.UI.Connection {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Password:. /// Looks up a localized string similar to Password.
/// </summary> /// </summary>
public static string LabelPassword { public static string LabelPassword {
get { get {
@ -106,7 +151,7 @@ namespace PettingZoo.UI.Connection {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Port:. /// Looks up a localized string similar to Port.
/// </summary> /// </summary>
public static string LabelPort { public static string LabelPort {
get { get {
@ -115,7 +160,7 @@ namespace PettingZoo.UI.Connection {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Routing key:. /// Looks up a localized string similar to Routing key.
/// </summary> /// </summary>
public static string LabelRoutingKey { public static string LabelRoutingKey {
get { get {
@ -133,7 +178,7 @@ namespace PettingZoo.UI.Connection {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Username:. /// Looks up a localized string similar to Username.
/// </summary> /// </summary>
public static string LabelUsername { public static string LabelUsername {
get { get {
@ -142,7 +187,7 @@ namespace PettingZoo.UI.Connection {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Virtual host:. /// Looks up a localized string similar to Virtual host.
/// </summary> /// </summary>
public static string LabelVirtualHost { public static string LabelVirtualHost {
get { get {
@ -150,6 +195,15 @@ namespace PettingZoo.UI.Connection {
} }
} }
/// <summary>
/// Looks up a localized string similar to &lt;New connection&gt;.
/// </summary>
public static string LastUsedDisplayName {
get {
return ResourceManager.GetString("LastUsedDisplayName", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Connection parameters. /// Looks up a localized string similar to Connection parameters.
/// </summary> /// </summary>

View File

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

View File

@ -1,43 +1,11 @@
using System; using System;
using System.Threading.Tasks;
using PettingZoo.Core.Settings;
namespace PettingZoo.UI.Connection namespace PettingZoo.UI.Connection
{ {
public interface IConnectionDialog public interface IConnectionDialog
{ {
ConnectionDialogParams? Show(ConnectionDialogParams? defaultParams = null); Task<ConnectionSettings?> Show();
}
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;
}
} }
} }

View File

@ -3,13 +3,11 @@ using System.Windows;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using PettingZoo.UI.Connection; using PettingZoo.UI.Connection;
using PettingZoo.UI.Subscribe; using PettingZoo.UI.Subscribe;
using PettingZoo.UI.Tab;
// TODO improve readability of the connection status (especially when connecting/disconnected)
namespace PettingZoo.UI.Main 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 #pragma warning disable CA1001 // MainWindow can't be IDisposable, handled instead in OnDispatcherShutDownStarted
public partial class MainWindow public partial class MainWindow
{ {
@ -20,9 +18,9 @@ namespace PettingZoo.UI.Main
{ {
WindowStartupLocation = WindowStartupLocation.CenterScreen; WindowStartupLocation = WindowStartupLocation.CenterScreen;
InitializeComponent();
viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog); viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog);
DataContext = viewModel; DataContext = viewModel;
InitializeComponent();
Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted; Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted;
} }

View File

@ -19,7 +19,6 @@ namespace PettingZoo.UI.Main
private readonly ISubscribeDialog subscribeDialog; private readonly ISubscribeDialog subscribeDialog;
private readonly ITabFactory tabFactory; private readonly ITabFactory tabFactory;
private ConnectionDialogParams? connectionDialogParams;
private SubscribeDialogParams? subscribeDialogParams; private SubscribeDialogParams? subscribeDialogParams;
private IConnection? connection; private IConnection? connection;
private string connectionStatus; private string connectionStatus;
@ -44,11 +43,16 @@ namespace PettingZoo.UI.Main
public ITab? ActiveTab public ITab? ActiveTab
{ {
get => activeTab; get => activeTab;
set => SetField(ref activeTab, value, otherPropertiesChanged: new [] set
{ {
nameof(ToolbarCommands), var currentTab = activeTab;
nameof(ToolbarCommandsSeparatorVisibility)
}); if (!SetField(ref activeTab, value, otherPropertiesChanged: new[] { nameof(ToolbarCommands), nameof(ToolbarCommandsSeparatorVisibility) }))
return;
currentTab?.Deactivate();
activeTab?.Activate();
}
} }
public ICommand ConnectCommand => connectCommand; public ICommand ConnectCommand => connectCommand;
@ -87,6 +91,8 @@ namespace PettingZoo.UI.Main
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
GC.SuppressFinalize(this);
if (connection != null) if (connection != null)
await connection.DisposeAsync(); await connection.DisposeAsync();
} }
@ -94,28 +100,22 @@ namespace PettingZoo.UI.Main
private async void ConnectExecute() private async void ConnectExecute()
{ {
var newParams = connectionDialog.Show(connectionDialogParams); var connectionSettings = await connectionDialog.Show();
if (connectionSettings == null)
// TODO support command-line parameters for easier testing
// var newParams = new ConnectionDialogParams("localhost", "/", 5672, "guest", "guest", true, "test", "#");
if (newParams == null)
return; return;
if (connection != null) if (connection != null)
await connection.DisposeAsync(); await connection.DisposeAsync();
connectionDialogParams = newParams;
connection = connectionFactory.CreateConnection(new ConnectionParams( connection = connectionFactory.CreateConnection(new ConnectionParams(
connectionDialogParams.Host, connectionDialogParams.VirtualHost, connectionDialogParams.Port, connectionSettings.Host, connectionSettings.VirtualHost, connectionSettings.Port,
connectionDialogParams.Username, connectionDialogParams.Password)); connectionSettings.Username, connectionSettings.Password!));
connection.StatusChanged += ConnectionStatusChanged; connection.StatusChanged += ConnectionStatusChanged;
if (connectionDialogParams.Subscribe) if (connectionSettings.Subscribe)
{ {
var subscriber = connection.Subscribe(connectionDialogParams.Exchange, connectionDialogParams.RoutingKey); var subscriber = connection.Subscribe(connectionSettings.Exchange, connectionSettings.RoutingKey);
AddTab(tabFactory.CreateSubscriberTab(connection, subscriber)); AddTab(tabFactory.CreateSubscriberTab(connection, subscriber));
} }
ConnectionChanged(); ConnectionChanged();
@ -132,9 +132,7 @@ namespace PettingZoo.UI.Main
connection = null; connection = null;
} }
connectionDialogParams = null;
ConnectionStatus = GetConnectionStatus(null); ConnectionStatus = GetConnectionStatus(null);
ConnectionChanged(); ConnectionChanged();
} }

View File

@ -30,8 +30,8 @@ namespace PettingZoo.UI.Subscribe
{ {
WindowStartupLocation = WindowStartupLocation.CenterOwner; WindowStartupLocation = WindowStartupLocation.CenterOwner;
InitializeComponent();
DataContext = viewModel; DataContext = viewModel;
InitializeComponent();
} }
} }
} }

View File

@ -12,7 +12,14 @@ namespace PettingZoo.UI.Tab
} }
public interface ITab : ITabToolbarCommands public interface ITabActivate
{
void Activate();
void Deactivate();
}
public interface ITab : ITabToolbarCommands, ITabActivate
{ {
string Title { get; } string Title { get; }
ContentControl Content { get; } ContentControl Content { get; }

View File

@ -0,0 +1,10 @@
namespace PettingZoo.UI.Tab.Publisher
{
public interface IPublishDestination
{
string Exchange { get; }
string RoutingKey { get; }
string? GetReplyTo();
}
}

View File

@ -4,24 +4,60 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:res="clr-namespace:PettingZoo.UI.Tab.Publisher" xmlns:res="clr-namespace:PettingZoo.UI.Tab.Publisher"
xmlns:ui="clr-namespace:PettingZoo.UI"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance res:DesignTimePublisherViewModel, IsDesignTimeCreatable=True}" d:DataContext="{d:DesignInstance res:DesignTimePublisherViewModel, IsDesignTimeCreatable=True}"
Background="White"> Background="White">
<Grid Margin="4"> <ui:GridLayout Style="{StaticResource Form}" Margin="4" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="16"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="16"/>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" HorizontalAlignment="Center"> <Label Grid.Row="0" Grid.Column="1">
<StackPanel Orientation="Horizontal">
<RadioButton Content="{x:Static res:PublisherViewStrings.LabelSendToExchange}" IsChecked="{Binding SendToExchange}" Style="{StaticResource TypeSelection}" />
<RadioButton Content="{x:Static res:PublisherViewStrings.LabelSendToQueue}" IsChecked="{Binding SendToQueue}" Style="{StaticResource TypeSelection}" />
</StackPanel>
</Label>
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelExchange}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Exchange}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelRoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelQueue}" Visibility="{Binding QueueVisibility}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Queue}" Visibility="{Binding QueueVisibility}" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelReplyTo}" />
<StackPanel Orientation="Horizontal" Grid.Row="5" Grid.Column="1">
<RadioButton Content="{x:Static res:PublisherViewStrings.LabelReplyToSpecified}" IsChecked="{Binding ReplyToSpecified}" Style="{StaticResource TypeSelection}" />
<RadioButton Content="{x:Static res:PublisherViewStrings.LabelReplyToNewSubscriber}" IsChecked="{Binding ReplyToNewSubscriber}" Style="{StaticResource TypeSelection}" />
</StackPanel>
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding ReplyTo}" IsEnabled="{Binding ReplyToSpecified}" />
<StackPanel Orientation="Horizontal" Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Center">
<ToggleButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeRaw}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeRaw}" /> <ToggleButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeRaw}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeRaw}" />
<ToggleButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeTapeti}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeTapeti}" /> <ToggleButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeTapeti}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeTapeti}" />
</StackPanel> </StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" VerticalScrollBarVisibility="Auto">
<ContentControl Margin="0 8 0 0" Content="{Binding MessageTypeControl}" /> <ContentControl Margin="0 8 0 0" Content="{Binding MessageTypeControl}" />
</ScrollViewer> </ScrollViewer>
</Grid> </ui:GridLayout>
</UserControl> </UserControl>

View File

@ -9,8 +9,8 @@ namespace PettingZoo.UI.Tab.Publisher
{ {
public PublisherView(PublisherViewModel viewModel) public PublisherView(PublisherViewModel viewModel)
{ {
InitializeComponent();
DataContext = viewModel; DataContext = viewModel;
InitializeComponent();
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
Background = Brushes.Transparent; Background = Brushes.Transparent;

View File

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
// TODO publish button in page instead of just in toolbar
namespace PettingZoo.UI.Tab.Publisher namespace PettingZoo.UI.Tab.Publisher
{ {
public enum MessageType public enum MessageType
@ -14,9 +16,18 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public class PublisherViewModel : BaseViewModel, ITabToolbarCommands public class PublisherViewModel : BaseViewModel, ITabToolbarCommands, IPublishDestination
{ {
private readonly IConnection connection; private readonly IConnection connection;
private readonly ITabFactory tabFactory;
private readonly ITabHost tabHost;
private bool sendToExchange = true;
private string exchange = "";
private string routingKey = "";
private string queue = "";
private string replyTo = "";
private bool replyToSpecified = true;
private MessageType messageType; private MessageType messageType;
private UserControl? messageTypeControl; private UserControl? messageTypeControl;
@ -29,6 +40,66 @@ namespace PettingZoo.UI.Tab.Publisher
private readonly TabToolbarCommand[] toolbarCommands; private readonly TabToolbarCommand[] toolbarCommands;
public bool SendToExchange
{
get => sendToExchange;
set => SetField(ref sendToExchange, value, otherPropertiesChanged: new[] { nameof(SendToQueue), nameof(ExchangeVisibility), nameof(QueueVisibility) });
}
public bool SendToQueue
{
get => !SendToExchange;
set => SendToExchange = !value;
}
public string Exchange
{
get => exchange;
set => SetField(ref exchange, value);
}
public string RoutingKey
{
get => routingKey;
set => SetField(ref routingKey, value);
}
public string Queue
{
get => queue;
set => SetField(ref queue, value);
}
public string ReplyTo
{
get => replyTo;
set => SetField(ref replyTo, value);
}
public bool ReplyToSpecified
{
get => replyToSpecified;
set => SetField(ref replyToSpecified, value, otherPropertiesChanged: new[] { nameof(ReplyToNewSubscriber) });
}
public bool ReplyToNewSubscriber
{
get => !ReplyToSpecified;
set => ReplyToSpecified = !value;
}
public virtual Visibility ExchangeVisibility => SendToExchange ? Visibility.Visible : Visibility.Collapsed;
public virtual Visibility QueueVisibility => SendToQueue ? Visibility.Visible : Visibility.Collapsed;
public MessageType MessageType public MessageType MessageType
{ {
get => messageType; get => messageType;
@ -70,13 +141,21 @@ namespace PettingZoo.UI.Tab.Publisher
// TODO make more dynamic, include entered routing key for example // TODO make more dynamic, include entered routing key for example
#pragma warning disable CA1822 // Mark members as static - can't, it's part of the interface you silly, that would break the build
public string Title => "Publish"; public string Title => "Publish";
#pragma warning restore CA1822
public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands; public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands;
public PublisherViewModel(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null) string IPublishDestination.Exchange => SendToExchange ? Exchange : "";
string IPublishDestination.RoutingKey => SendToExchange ? RoutingKey : Queue;
public PublisherViewModel(ITabHost tabHost, ITabFactory tabFactory, IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null)
{ {
this.connection = connection; this.connection = connection;
this.tabFactory = tabFactory;
this.tabHost = tabHost;
publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute); publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute);
@ -109,7 +188,7 @@ namespace PettingZoo.UI.Tab.Publisher
switch (value) switch (value)
{ {
case MessageType.Raw: case MessageType.Raw:
var rawPublisherViewModel = new RawPublisherViewModel(connection); var rawPublisherViewModel = new RawPublisherViewModel(connection, this);
rawPublisherView ??= new RawPublisherView(rawPublisherViewModel); rawPublisherView ??= new RawPublisherView(rawPublisherViewModel);
MessageTypeControl = rawPublisherView; MessageTypeControl = rawPublisherView;
@ -117,7 +196,7 @@ namespace PettingZoo.UI.Tab.Publisher
break; break;
case MessageType.Tapeti: case MessageType.Tapeti:
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection); var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this);
tapetiPublisherView ??= new TapetiPublisherView(tapetiPublisherViewModel); tapetiPublisherView ??= new TapetiPublisherView(tapetiPublisherViewModel);
MessageTypeControl = tapetiPublisherView; MessageTypeControl = tapetiPublisherView;
@ -134,28 +213,49 @@ namespace PettingZoo.UI.Tab.Publisher
private void SetMessageTypeControl(ReceivedMessageInfo fromReceivedMessage) private void SetMessageTypeControl(ReceivedMessageInfo fromReceivedMessage)
{ {
Exchange = fromReceivedMessage.Exchange;
RoutingKey = fromReceivedMessage.RoutingKey;
if (TapetiPublisherViewModel.IsTapetiMessage(fromReceivedMessage)) if (TapetiPublisherViewModel.IsTapetiMessage(fromReceivedMessage))
{ {
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, fromReceivedMessage); var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, fromReceivedMessage);
tapetiPublisherView = new TapetiPublisherView(tapetiPublisherViewModel); tapetiPublisherView = new TapetiPublisherView(tapetiPublisherViewModel);
MessageType = MessageType.Tapeti; MessageType = MessageType.Tapeti;
} }
else else
{ {
var rawPublisherViewModel = new RawPublisherViewModel(connection, fromReceivedMessage); var rawPublisherViewModel = new RawPublisherViewModel(connection, this, fromReceivedMessage);
rawPublisherView = new RawPublisherView(rawPublisherViewModel); rawPublisherView = new RawPublisherView(rawPublisherViewModel);
MessageType = MessageType.Raw; MessageType = MessageType.Raw;
} }
} }
public string? GetReplyTo()
{
if (ReplyToSpecified)
return string.IsNullOrEmpty(ReplyTo) ? null : ReplyTo;
var subscriber = connection.Subscribe();
var tab = tabFactory.CreateSubscriberTab(connection, subscriber);
tabHost.AddTab(tab);
subscriber.Start();
return subscriber.QueueName;
}
} }
public class DesignTimePublisherViewModel : PublisherViewModel public class DesignTimePublisherViewModel : PublisherViewModel
{ {
public DesignTimePublisherViewModel() : base(null!) public DesignTimePublisherViewModel() : base(null!, null!, null!)
{ {
} }
public override Visibility ExchangeVisibility => Visibility.Visible;
public override Visibility QueueVisibility => Visibility.Visible;
} }
} }

View File

@ -69,6 +69,15 @@ namespace PettingZoo.UI.Tab.Publisher {
} }
} }
/// <summary>
/// Looks up a localized string similar to Exchange.
/// </summary>
public static string LabelExchange {
get {
return ResourceManager.GetString("LabelExchange", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Message type: . /// Looks up a localized string similar to Message type: .
/// </summary> /// </summary>
@ -78,6 +87,69 @@ namespace PettingZoo.UI.Tab.Publisher {
} }
} }
/// <summary>
/// Looks up a localized string similar to Queue.
/// </summary>
public static string LabelQueue {
get {
return ResourceManager.GetString("LabelQueue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reply To.
/// </summary>
public static string LabelReplyTo {
get {
return ResourceManager.GetString("LabelReplyTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to New subscriber tab.
/// </summary>
public static string LabelReplyToNewSubscriber {
get {
return ResourceManager.GetString("LabelReplyToNewSubscriber", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Specified queue.
/// </summary>
public static string LabelReplyToSpecified {
get {
return ResourceManager.GetString("LabelReplyToSpecified", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Routing key.
/// </summary>
public static string LabelRoutingKey {
get {
return ResourceManager.GetString("LabelRoutingKey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publish to exchange (topic).
/// </summary>
public static string LabelSendToExchange {
get {
return ResourceManager.GetString("LabelSendToExchange", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publish to queue (direct).
/// </summary>
public static string LabelSendToQueue {
get {
return ResourceManager.GetString("LabelSendToQueue", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Raw message. /// Looks up a localized string similar to Raw message.
/// </summary> /// </summary>

View File

@ -120,9 +120,33 @@
<data name="CommandPublish" xml:space="preserve"> <data name="CommandPublish" xml:space="preserve">
<value>Publish</value> <value>Publish</value>
</data> </data>
<data name="LabelExchange" xml:space="preserve">
<value>Exchange</value>
</data>
<data name="LabelMessageType" xml:space="preserve"> <data name="LabelMessageType" xml:space="preserve">
<value>Message type: </value> <value>Message type: </value>
</data> </data>
<data name="LabelQueue" xml:space="preserve">
<value>Queue</value>
</data>
<data name="LabelReplyTo" xml:space="preserve">
<value>Reply To</value>
</data>
<data name="LabelReplyToNewSubscriber" xml:space="preserve">
<value>New subscriber tab</value>
</data>
<data name="LabelReplyToSpecified" xml:space="preserve">
<value>Specified queue</value>
</data>
<data name="LabelRoutingKey" xml:space="preserve">
<value>Routing key</value>
</data>
<data name="LabelSendToExchange" xml:space="preserve">
<value>Publish to exchange (topic)</value>
</data>
<data name="LabelSendToQueue" xml:space="preserve">
<value>Publish to queue (direct)</value>
</data>
<data name="OptionMessageTypeRaw" xml:space="preserve"> <data name="OptionMessageTypeRaw" xml:space="preserve">
<value>Raw message</value> <value>Raw message</value>
</data> </data>

View File

@ -11,17 +11,11 @@
Background="White"> Background="White">
<ui:GridLayout Style="{StaticResource Form}"> <ui:GridLayout Style="{StaticResource Form}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" SharedSizeGroup="Label" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ui:GridLayout.RowDefinitions> <ui:GridLayout.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="16"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -42,31 +36,14 @@
</ui:GridLayout.RowDefinitions> </ui:GridLayout.RowDefinitions>
<Label Grid.Row="0" Grid.Column="1"> <Label Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelDeliveryMode}" />
<StackPanel Orientation="Horizontal"> <ComboBox Grid.Column="1" SelectedIndex="{Binding DeliveryModeIndex}">
<RadioButton Content="{x:Static publisher:RawPublisherViewStrings.LabelSendToExchange}" IsChecked="{Binding SendToExchange}" Style="{StaticResource TypeSelection}" />
<RadioButton Content="{x:Static publisher:RawPublisherViewStrings.LabelSendToQueue}" IsChecked="{Binding SendToQueue}" Style="{StaticResource TypeSelection}" />
</StackPanel>
</Label>
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelExchange}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Exchange}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelRoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelQueue}" Visibility="{Binding QueueVisibility}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Queue}" Visibility="{Binding QueueVisibility}" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelDeliveryMode}" />
<ComboBox Grid.Row="5" Grid.Column="1" SelectedIndex="{Binding DeliveryModeIndex}">
<ComboBoxItem Content="{x:Static publisher:RawPublisherViewStrings.DeliveryModeNonPersistent}" /> <ComboBoxItem Content="{x:Static publisher:RawPublisherViewStrings.DeliveryModeNonPersistent}" />
<ComboBoxItem Content="{x:Static publisher:RawPublisherViewStrings.DeliveryModePersistent}" /> <ComboBoxItem Content="{x:Static publisher:RawPublisherViewStrings.DeliveryModePersistent}" />
</ComboBox> </ComboBox>
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelHeaders}" /> <Label Grid.Row="1" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelHeaders}" />
<ItemsControl Grid.Row="6" Grid.Column="1" ItemsSource="{Binding Headers}"> <ItemsControl Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Headers}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Vertical" /> <StackPanel Orientation="Vertical" />
@ -116,43 +93,40 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelProperties}" Style="{StaticResource SectionLabel}"/> <Label Grid.Row="2" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelProperties}" Style="{StaticResource SectionLabel}"/>
<Label Grid.Row="8" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelContentType}" /> <Label Grid.Row="3" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelContentType}" />
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding ContentType}" /> <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding ContentType, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="9" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelCorrelationId}" /> <Label Grid.Row="4" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelCorrelationId}" />
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding CorrelationId}" /> <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding CorrelationId, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="10" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelReplyTo}" /> <Label Grid.Row="5" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelAppId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding ReplyTo}" /> <TextBox Grid.Row="5" Grid.Column="1" Text="{Binding AppId, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="11" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelAppId}" Visibility="{Binding PropertiesExpandedVisibility}" /> <Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelContentEncoding}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="11" Grid.Column="1" Text="{Binding AppId}" Visibility="{Binding PropertiesExpandedVisibility}" /> <TextBox Grid.Row="6" Grid.Column="1" Text="{Binding ContentEncoding, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="12" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelContentEncoding}" Visibility="{Binding PropertiesExpandedVisibility}" /> <Label Grid.Row="7" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelExpiration}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding ContentEncoding}" Visibility="{Binding PropertiesExpandedVisibility}" /> <TextBox Grid.Row="7" Grid.Column="1" Text="{Binding Expiration, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="13" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelExpiration}" Visibility="{Binding PropertiesExpandedVisibility}" /> <Label Grid.Row="8" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelMessageId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="13" Grid.Column="1" Text="{Binding Expiration}" Visibility="{Binding PropertiesExpandedVisibility}" /> <TextBox Grid.Row="8" Grid.Column="1" Text="{Binding MessageId, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="14" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelMessageId}" Visibility="{Binding PropertiesExpandedVisibility}" /> <Label Grid.Row="9" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelPriority}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="14" Grid.Column="1" Text="{Binding MessageId}" Visibility="{Binding PropertiesExpandedVisibility}" /> <TextBox Grid.Row="9" Grid.Column="1" Text="{Binding Priority, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="15" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelPriority}" Visibility="{Binding PropertiesExpandedVisibility}" /> <Label Grid.Row="10" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelTimestamp}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="15" Grid.Column="1" Text="{Binding Priority}" Visibility="{Binding PropertiesExpandedVisibility}" /> <TextBox Grid.Row="10" Grid.Column="1" Text="{Binding Timestamp, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="16" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelTimestamp}" Visibility="{Binding PropertiesExpandedVisibility}" /> <Label Grid.Row="11" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelType}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="16" Grid.Column="1" Text="{Binding Timestamp}" Visibility="{Binding PropertiesExpandedVisibility}" /> <TextBox Grid.Row="11" Grid.Column="1" Text="{Binding TypeProperty, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="17" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelType}" Visibility="{Binding PropertiesExpandedVisibility}" /> <Label Grid.Row="12" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelUserId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="17" Grid.Column="1" Text="{Binding TypeProperty}" Visibility="{Binding PropertiesExpandedVisibility}" /> <TextBox Grid.Row="12" Grid.Column="1" Text="{Binding UserId, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="18" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelUserId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="18" Grid.Column="1" Text="{Binding UserId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Button Grid.Row="19" Grid.Column="1" Content="{Binding PropertiesExpandedCollapsedText}" Command="{Binding PropertiesExpandCollapseCommand}" Cursor="Hand"> <Button Grid.Row="13" Grid.Column="1" Content="{Binding PropertiesExpandedCollapsedText}" Command="{Binding PropertiesExpandCollapseCommand}" Cursor="Hand">
<Button.Template> <Button.Template>
<ControlTemplate TargetType="{x:Type Button}"> <ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter /> <ContentPresenter />
@ -160,7 +134,7 @@
</Button.Template> </Button.Template>
</Button> </Button>
<Label Grid.Row="21" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelPayload}" /> <Label Grid.Row="15" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelPayload}" />
<TextBox Grid.Row="21" Grid.Column="1" Text="{Binding Payload}" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" Height="150" /> <TextBox Grid.Row="15" Grid.Column="1" Text="{Binding Payload, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Payload}" Height="150" />
</ui:GridLayout> </ui:GridLayout>
</UserControl> </UserControl>

View File

@ -13,16 +13,16 @@ namespace PettingZoo.UI.Tab.Publisher
/// </summary> /// </summary>
public partial class RawPublisherView public partial class RawPublisherView
{ {
private RawPublisherViewModel viewModel; private readonly RawPublisherViewModel viewModel;
private DispatcherTimer checkEmptyHeaderTimer; private readonly DispatcherTimer checkEmptyHeaderTimer;
public RawPublisherView(RawPublisherViewModel viewModel) public RawPublisherView(RawPublisherViewModel viewModel)
{ {
this.viewModel = viewModel; this.viewModel = viewModel;
InitializeComponent();
DataContext = viewModel; DataContext = viewModel;
InitializeComponent();
checkEmptyHeaderTimer = new DispatcherTimer(); checkEmptyHeaderTimer = new DispatcherTimer();
checkEmptyHeaderTimer.Tick += CheckEmptyHeaderTimerOnTick; checkEmptyHeaderTimer.Tick += CheckEmptyHeaderTimerOnTick;

View File

@ -13,20 +13,15 @@ namespace PettingZoo.UI.Tab.Publisher
public class RawPublisherViewModel : BaseViewModel public class RawPublisherViewModel : BaseViewModel
{ {
private readonly IConnection connection; private readonly IConnection connection;
private readonly IPublishDestination publishDestination;
private readonly DelegateCommand publishCommand; private readonly DelegateCommand publishCommand;
private readonly DelegateCommand propertiesExpandCollapseCommand; private readonly DelegateCommand propertiesExpandCollapseCommand;
private bool propertiesExpanded; private bool propertiesExpanded;
private bool sendToExchange = true;
private string exchange = "";
private string routingKey = "";
private string queue = "";
private MessageDeliveryMode deliveryMode; private MessageDeliveryMode deliveryMode;
private string contentType = "application/json"; private string contentType = "application/json";
private string correlationId = ""; private string correlationId = "";
private string replyTo = "";
private string appId = ""; private string appId = "";
private string contentEncoding = ""; private string contentEncoding = "";
private string expiration = ""; private string expiration = "";
@ -38,44 +33,6 @@ namespace PettingZoo.UI.Tab.Publisher
private string payload = ""; private string payload = "";
public bool SendToExchange
{
get => sendToExchange;
set => SetField(ref sendToExchange, value, otherPropertiesChanged: new[] { nameof(SendToQueue), nameof(ExchangeVisibility), nameof(QueueVisibility) });
}
public bool SendToQueue
{
get => !SendToExchange;
set => SendToExchange = !value;
}
public string Exchange
{
get => exchange;
set => SetField(ref exchange, value);
}
public string RoutingKey
{
get => routingKey;
set => SetField(ref routingKey, value);
}
public string Queue
{
get => queue;
set => SetField(ref queue, value);
}
public virtual Visibility ExchangeVisibility => SendToExchange ? Visibility.Visible : Visibility.Collapsed;
public virtual Visibility QueueVisibility => SendToQueue ? Visibility.Visible : Visibility.Collapsed;
public int DeliveryModeIndex public int DeliveryModeIndex
{ {
@ -98,13 +55,6 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public string ReplyTo
{
get => replyTo;
set => SetField(ref replyTo, value);
}
public string AppId public string AppId
{ {
get => appId; get => appId;
@ -194,20 +144,17 @@ namespace PettingZoo.UI.Tab.Publisher
protected Header LastHeader; protected Header LastHeader;
public RawPublisherViewModel(IConnection connection, ReceivedMessageInfo? receivedMessage = null) public RawPublisherViewModel(IConnection connection, IPublishDestination publishDestination, ReceivedMessageInfo? receivedMessage = null)
{ {
this.connection = connection; this.connection = connection;
this.publishDestination = publishDestination;
publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute); publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute);
propertiesExpandCollapseCommand = new DelegateCommand(PropertiesExpandCollapseExecute); propertiesExpandCollapseCommand = new DelegateCommand(PropertiesExpandCollapseExecute);
if (receivedMessage != null) if (receivedMessage != null)
{ {
Exchange = receivedMessage.Exchange;
RoutingKey = receivedMessage.RoutingKey;
CorrelationId = receivedMessage.Properties.CorrelationId ?? ""; CorrelationId = receivedMessage.Properties.CorrelationId ?? "";
ReplyTo = receivedMessage.Properties.ReplyTo ?? "";
Priority = receivedMessage.Properties.Priority?.ToString() ?? ""; Priority = receivedMessage.Properties.Priority?.ToString() ?? "";
AppId = receivedMessage.Properties.AppId ?? ""; AppId = receivedMessage.Properties.AppId ?? "";
ContentEncoding = receivedMessage.Properties.ContentEncoding ?? ""; ContentEncoding = receivedMessage.Properties.ContentEncoding ?? "";
@ -270,15 +217,14 @@ namespace PettingZoo.UI.Tab.Publisher
} }
// TODO check parsing of priority and timestamp // TODO check parsing of priority and timestamp
// TODO support for Reply To to dynamic queue which waits for a message (or opens a new subscriber tab?)
var headers = Headers.Where(h => h.IsValid()).ToDictionary(h => h.Key, h => h.Value); var headers = Headers.Where(h => h.IsValid()).ToDictionary(h => h.Key, h => h.Value);
// TODO background worker / async // TODO background worker / async
connection.Publish(new PublishMessageInfo( connection.Publish(new PublishMessageInfo(
SendToExchange ? Exchange : "", publishDestination.Exchange,
SendToExchange ? RoutingKey : Queue, publishDestination.RoutingKey,
Encoding.UTF8.GetBytes(Payload), Encoding.UTF8.GetBytes(Payload),
new MessageProperties(headers) new MessageProperties(headers)
{ {
@ -290,7 +236,7 @@ namespace PettingZoo.UI.Tab.Publisher
Expiration = NullIfEmpty(Expiration), Expiration = NullIfEmpty(Expiration),
MessageId = NullIfEmpty(MessageId), MessageId = NullIfEmpty(MessageId),
Priority = !string.IsNullOrEmpty(Priority) && byte.TryParse(Priority, out var priorityValue) ? priorityValue : null, Priority = !string.IsNullOrEmpty(Priority) && byte.TryParse(Priority, out var priorityValue) ? priorityValue : null,
ReplyTo = NullIfEmpty(ReplyTo), ReplyTo = publishDestination.GetReplyTo(),
Timestamp = !string.IsNullOrEmpty(Timestamp) && DateTime.TryParse(Timestamp, out var timestampValue) ? timestampValue : null, Timestamp = !string.IsNullOrEmpty(Timestamp) && DateTime.TryParse(Timestamp, out var timestampValue) ? timestampValue : null,
Type = NullIfEmpty(TypeProperty), Type = NullIfEmpty(TypeProperty),
UserId = NullIfEmpty(UserId) UserId = NullIfEmpty(UserId)
@ -341,7 +287,7 @@ namespace PettingZoo.UI.Tab.Publisher
public class DesignTimeRawPublisherViewModel : RawPublisherViewModel public class DesignTimeRawPublisherViewModel : RawPublisherViewModel
{ {
public DesignTimeRawPublisherViewModel() : base(null!) public DesignTimeRawPublisherViewModel() : base(null!, null!)
{ {
PropertiesExpanded = true; PropertiesExpanded = true;
@ -349,9 +295,5 @@ namespace PettingZoo.UI.Tab.Publisher
capturedLastHeader.Key = "Example"; capturedLastHeader.Key = "Example";
capturedLastHeader.Value = "header"; capturedLastHeader.Value = "header";
} }
public override Visibility ExchangeVisibility => Visibility.Visible;
public override Visibility QueueVisibility => Visibility.Visible;
} }
} }

View File

@ -141,15 +141,6 @@ namespace PettingZoo.UI.Tab.Publisher {
} }
} }
/// <summary>
/// Looks up a localized string similar to Exchange.
/// </summary>
public static string LabelExchange {
get {
return ResourceManager.GetString("LabelExchange", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Expiration. /// Looks up a localized string similar to Expiration.
/// </summary> /// </summary>
@ -204,51 +195,6 @@ namespace PettingZoo.UI.Tab.Publisher {
} }
} }
/// <summary>
/// Looks up a localized string similar to Queue.
/// </summary>
public static string LabelQueue {
get {
return ResourceManager.GetString("LabelQueue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reply To.
/// </summary>
public static string LabelReplyTo {
get {
return ResourceManager.GetString("LabelReplyTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Routing key.
/// </summary>
public static string LabelRoutingKey {
get {
return ResourceManager.GetString("LabelRoutingKey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publish to exchange (topic).
/// </summary>
public static string LabelSendToExchange {
get {
return ResourceManager.GetString("LabelSendToExchange", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publish to queue (direct).
/// </summary>
public static string LabelSendToQueue {
get {
return ResourceManager.GetString("LabelSendToQueue", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Timestamp. /// Looks up a localized string similar to Timestamp.
/// </summary> /// </summary>

View File

@ -144,9 +144,6 @@
<data name="LabelDeliveryMode" xml:space="preserve"> <data name="LabelDeliveryMode" xml:space="preserve">
<value>Delivery mode</value> <value>Delivery mode</value>
</data> </data>
<data name="LabelExchange" xml:space="preserve">
<value>Exchange</value>
</data>
<data name="LabelExpiration" xml:space="preserve"> <data name="LabelExpiration" xml:space="preserve">
<value>Expiration</value> <value>Expiration</value>
</data> </data>
@ -165,21 +162,6 @@
<data name="LabelProperties" xml:space="preserve"> <data name="LabelProperties" xml:space="preserve">
<value>Properties</value> <value>Properties</value>
</data> </data>
<data name="LabelQueue" xml:space="preserve">
<value>Queue</value>
</data>
<data name="LabelReplyTo" xml:space="preserve">
<value>Reply To</value>
</data>
<data name="LabelRoutingKey" xml:space="preserve">
<value>Routing key</value>
</data>
<data name="LabelSendToExchange" xml:space="preserve">
<value>Publish to exchange (topic)</value>
</data>
<data name="LabelSendToQueue" xml:space="preserve">
<value>Publish to queue (direct)</value>
</data>
<data name="LabelTimestamp" xml:space="preserve"> <data name="LabelTimestamp" xml:space="preserve">
<value>Timestamp</value> <value>Timestamp</value>
</data> </data>

View File

@ -11,17 +11,11 @@
Background="White"> Background="White">
<ui:GridLayout Style="{StaticResource Form}"> <ui:GridLayout Style="{StaticResource Form}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" SharedSizeGroup="Label" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ui:GridLayout.RowDefinitions> <ui:GridLayout.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="16"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -32,47 +26,28 @@
</ui:GridLayout.RowDefinitions> </ui:GridLayout.RowDefinitions>
<Label Grid.Row="0" Grid.Column="1"> <Label Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelProperties}" Style="{StaticResource SectionLabel}"/>
<StackPanel Orientation="Horizontal">
<RadioButton Content="{x:Static publisher:TapetiPublisherViewStrings.LabelSendToExchange}" IsChecked="{Binding SendToExchange}" Style="{StaticResource TypeSelection}" />
<RadioButton Content="{x:Static publisher:TapetiPublisherViewStrings.LabelSendToQueue}" IsChecked="{Binding SendToQueue}" Style="{StaticResource TypeSelection}" />
</StackPanel>
</Label>
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelExchange}" Visibility="{Binding ExchangeVisibility}" /> <Label Grid.Row="1" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelCorrelationId}" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Exchange}" Visibility="{Binding ExchangeVisibility}" /> <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding CorrelationId, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelRoutingKey}" Visibility="{Binding ExchangeVisibility}" /> <Label Grid.Row="4" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelAssemblyName}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RoutingKey}" Visibility="{Binding ExchangeVisibility}" /> <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding AssemblyName, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelQueue}" Visibility="{Binding QueueVisibility}" /> <Label Grid.Row="5" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelClassName}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Queue}" Visibility="{Binding QueueVisibility}" /> <Grid Grid.Row="5" Grid.Column="1">
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelProperties}" Style="{StaticResource SectionLabel}"/>
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelCorrelationId}" />
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding CorrelationId}" />
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelReplyTo}" />
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding ReplyTo}" />
<Label Grid.Row="10" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelAssemblyName}" />
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding AssemblyName, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="11" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelClassName}" />
<Grid Grid.Row="11" Grid.Column="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ClassName}" GotFocus="CaretToEnd" /> <TextBox Grid.Column="0" Text="{Binding ClassName, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd" />
<!-- <!--
<Button Grid.Column="1" Content="{x:Static publisher:TapetiPublisherViewStrings.ButtonBrowseClass}" /> <Button Grid.Column="1" Content="{x:Static publisher:TapetiPublisherViewStrings.ButtonBrowseClass}" />
--> -->
</Grid> </Grid>
<Label Grid.Row="12" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelPayload}" /> <Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelPayload}" />
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding Payload}" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" Height="150" /> <TextBox Grid.Row="6" Grid.Column="1" Text="{Binding Payload, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Payload}" Height="150" />
</ui:GridLayout> </ui:GridLayout>
</UserControl> </UserControl>

View File

@ -10,8 +10,8 @@ namespace PettingZoo.UI.Tab.Publisher
{ {
public TapetiPublisherView(TapetiPublisherViewModel viewModel) public TapetiPublisherView(TapetiPublisherViewModel viewModel)
{ {
InitializeComponent();
DataContext = viewModel; DataContext = viewModel;
InitializeComponent();
} }
@ -20,7 +20,7 @@ namespace PettingZoo.UI.Tab.Publisher
if (sender is not TextBox textBox) if (sender is not TextBox textBox)
return; return;
textBox.CaretIndex = textBox.Text?.Length ?? 0; textBox.CaretIndex = textBox.Text.Length;
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using IConnection = PettingZoo.Core.Connection.IConnection; using IConnection = PettingZoo.Core.Connection.IConnection;
@ -10,68 +9,15 @@ namespace PettingZoo.UI.Tab.Publisher
public class TapetiPublisherViewModel : BaseViewModel public class TapetiPublisherViewModel : BaseViewModel
{ {
private readonly IConnection connection; private readonly IConnection connection;
private readonly IPublishDestination publishDestination;
private readonly DelegateCommand publishCommand; private readonly DelegateCommand publishCommand;
private bool sendToExchange = true;
private string exchange = "";
private string routingKey = "";
private string queue = "";
private MessageDeliveryMode deliveryMode;
private string correlationId = ""; private string correlationId = "";
private string replyTo = "";
private string payload = ""; private string payload = "";
private string className = ""; private string className = "";
private string assemblyName = ""; private string assemblyName = "";
public bool SendToExchange
{
get => sendToExchange;
set => SetField(ref sendToExchange, value, otherPropertiesChanged: new[] { nameof(SendToQueue), nameof(ExchangeVisibility), nameof(QueueVisibility) });
}
public bool SendToQueue
{
get => !SendToExchange;
set => SendToExchange = !value;
}
public string Exchange
{
get => exchange;
set => SetField(ref exchange, value);
}
public string RoutingKey
{
get => routingKey;
set => SetField(ref routingKey, value);
}
public string Queue
{
get => queue;
set => SetField(ref queue, value);
}
public virtual Visibility ExchangeVisibility => SendToExchange ? Visibility.Visible : Visibility.Collapsed;
public virtual Visibility QueueVisibility => SendToQueue ? Visibility.Visible : Visibility.Collapsed;
public int DeliveryModeIndex
{
get => deliveryMode == MessageDeliveryMode.Persistent ? 1 : 0;
set => SetField(ref deliveryMode, value == 1 ? MessageDeliveryMode.Persistent : MessageDeliveryMode.NonPersistent);
}
public string CorrelationId public string CorrelationId
{ {
get => correlationId; get => correlationId;
@ -79,13 +25,6 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public string ReplyTo
{
get => replyTo;
set => SetField(ref replyTo, value);
}
public string ClassName public string ClassName
{ {
get => string.IsNullOrEmpty(className) ? AssemblyName + "." : className; get => string.IsNullOrEmpty(className) ? AssemblyName + "." : className;
@ -142,9 +81,10 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public TapetiPublisherViewModel(IConnection connection, ReceivedMessageInfo? receivedMessage = null) public TapetiPublisherViewModel(IConnection connection, IPublishDestination publishDestination, ReceivedMessageInfo? receivedMessage = null)
{ {
this.connection = connection; this.connection = connection;
this.publishDestination = publishDestination;
publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute); publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute);
@ -152,14 +92,9 @@ namespace PettingZoo.UI.Tab.Publisher
if (receivedMessage == null || !IsTapetiMessage(receivedMessage, out var receivedAssemblyName, out var receivedClassName)) if (receivedMessage == null || !IsTapetiMessage(receivedMessage, out var receivedAssemblyName, out var receivedClassName))
return; return;
Exchange = receivedMessage.Exchange;
RoutingKey = receivedMessage.RoutingKey;
AssemblyName = receivedAssemblyName; AssemblyName = receivedAssemblyName;
ClassName = receivedClassName; ClassName = receivedClassName;
CorrelationId = receivedMessage.Properties.CorrelationId ?? ""; CorrelationId = receivedMessage.Properties.CorrelationId ?? "";
ReplyTo = receivedMessage.Properties.ReplyTo ?? "";
Payload = Encoding.UTF8.GetString(receivedMessage.Body); Payload = Encoding.UTF8.GetString(receivedMessage.Body);
} }
@ -171,12 +106,11 @@ namespace PettingZoo.UI.Tab.Publisher
return string.IsNullOrEmpty(value) ? null : value; return string.IsNullOrEmpty(value) ? null : value;
} }
// TODO support for Reply To to dynamic queue which waits for a message (or opens a new subscriber tab?)
// TODO background worker / async // TODO background worker / async
connection.Publish(new PublishMessageInfo( connection.Publish(new PublishMessageInfo(
SendToExchange ? Exchange : "", publishDestination.Exchange,
SendToExchange ? RoutingKey : Queue, publishDestination.RoutingKey,
Encoding.UTF8.GetBytes(Payload), Encoding.UTF8.GetBytes(Payload),
new MessageProperties(new Dictionary<string, string> new MessageProperties(new Dictionary<string, string>
{ {
@ -185,8 +119,8 @@ namespace PettingZoo.UI.Tab.Publisher
{ {
ContentType = @"application/json", ContentType = @"application/json",
CorrelationId = NullIfEmpty(CorrelationId), CorrelationId = NullIfEmpty(CorrelationId),
DeliveryMode = deliveryMode, DeliveryMode = MessageDeliveryMode.Persistent,
ReplyTo = NullIfEmpty(ReplyTo) ReplyTo = publishDestination.GetReplyTo()
})); }));
} }
@ -201,12 +135,12 @@ namespace PettingZoo.UI.Tab.Publisher
public class DesignTimeTapetiPublisherViewModel : TapetiPublisherViewModel public class DesignTimeTapetiPublisherViewModel : TapetiPublisherViewModel
{ {
public DesignTimeTapetiPublisherViewModel() : base(null!) public DesignTimeTapetiPublisherViewModel() : base(null!, null!)
{ {
AssemblyName = "Messaging.Example";
ClassName = "Messaging.Example.ExampleMessage";
CorrelationId = "2c702859-bbbc-454e-87e2-4220c8c595d7";
Payload = "{\r\n \"Hello\": \"world!\"\r\n}";
} }
public override Visibility ExchangeVisibility => Visibility.Visible;
public override Visibility QueueVisibility => Visibility.Visible;
} }
} }

View File

@ -123,15 +123,6 @@ namespace PettingZoo.UI.Tab.Publisher {
} }
} }
/// <summary>
/// Looks up a localized string similar to Exchange.
/// </summary>
public static string LabelExchange {
get {
return ResourceManager.GetString("LabelExchange", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Payload. /// Looks up a localized string similar to Payload.
/// </summary> /// </summary>
@ -149,50 +140,5 @@ namespace PettingZoo.UI.Tab.Publisher {
return ResourceManager.GetString("LabelProperties", resourceCulture); return ResourceManager.GetString("LabelProperties", resourceCulture);
} }
} }
/// <summary>
/// Looks up a localized string similar to Queue.
/// </summary>
public static string LabelQueue {
get {
return ResourceManager.GetString("LabelQueue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reply To.
/// </summary>
public static string LabelReplyTo {
get {
return ResourceManager.GetString("LabelReplyTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Routing key.
/// </summary>
public static string LabelRoutingKey {
get {
return ResourceManager.GetString("LabelRoutingKey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publish to exchange (topic).
/// </summary>
public static string LabelSendToExchange {
get {
return ResourceManager.GetString("LabelSendToExchange", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publish to queue (direct).
/// </summary>
public static string LabelSendToQueue {
get {
return ResourceManager.GetString("LabelSendToQueue", resourceCulture);
}
}
} }
} }

View File

@ -138,28 +138,10 @@
<data name="LabelDeliveryMode" xml:space="preserve"> <data name="LabelDeliveryMode" xml:space="preserve">
<value>Delivery mode</value> <value>Delivery mode</value>
</data> </data>
<data name="LabelExchange" xml:space="preserve">
<value>Exchange</value>
</data>
<data name="LabelPayload" xml:space="preserve"> <data name="LabelPayload" xml:space="preserve">
<value>Payload</value> <value>Payload</value>
</data> </data>
<data name="LabelProperties" xml:space="preserve"> <data name="LabelProperties" xml:space="preserve">
<value>Properties</value> <value>Properties</value>
</data> </data>
<data name="LabelQueue" xml:space="preserve">
<value>Queue</value>
</data>
<data name="LabelReplyTo" xml:space="preserve">
<value>Reply To</value>
</data>
<data name="LabelRoutingKey" xml:space="preserve">
<value>Routing key</value>
</data>
<data name="LabelSendToExchange" xml:space="preserve">
<value>Publish to exchange (topic)</value>
</data>
<data name="LabelSendToQueue" xml:space="preserve">
<value>Publish to queue (direct)</value>
</data>
</root> </root>

View File

@ -10,8 +10,8 @@ namespace PettingZoo.UI.Tab.Subscriber
{ {
public SubscriberView(SubscriberViewModel viewModel) public SubscriberView(SubscriberViewModel viewModel)
{ {
InitializeComponent();
DataContext = viewModel; DataContext = viewModel;
InitializeComponent();
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))

View File

@ -7,12 +7,11 @@ using System.Windows.Input;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using PettingZoo.Core.Rendering; using PettingZoo.Core.Rendering;
// TODO update title with unread message count if tab is not active // TODO visual hint of where the last read message was when activating the tab again
// TODO export option (to Tapeti.Cmd compatible format / command-line of course)
namespace PettingZoo.UI.Tab.Subscriber namespace PettingZoo.UI.Tab.Subscriber
{ {
public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands, ITabActivate
{ {
private readonly ITabHost tabHost; private readonly ITabHost tabHost;
private readonly ITabFactory tabFactory; private readonly ITabFactory tabFactory;
@ -26,8 +25,13 @@ namespace PettingZoo.UI.Tab.Subscriber
private readonly DelegateCommand createPublisherCommand; private readonly DelegateCommand createPublisherCommand;
private bool tabActive;
private int unreadCount;
public ICommand ClearCommand => clearCommand; public ICommand ClearCommand => clearCommand;
// ReSharper disable once UnusedMember.Global - it is, but via a proxy
public ICommand CreatePublisherCommand => createPublisherCommand; public ICommand CreatePublisherCommand => createPublisherCommand;
public ObservableCollection<ReceivedMessageInfo> Messages { get; } public ObservableCollection<ReceivedMessageInfo> Messages { get; }
@ -53,7 +57,9 @@ namespace PettingZoo.UI.Tab.Subscriber
set => SetField(ref selectedMessageProperties, value); set => SetField(ref selectedMessageProperties, value);
} }
public string Title => $"{subscriber.Exchange} - {subscriber.RoutingKey}"; public string Title =>
(subscriber.Exchange != null ? $"{subscriber.Exchange} - {subscriber.RoutingKey}" : $"{subscriber.QueueName}") +
(tabActive || unreadCount == 0 ? "" : $" ({unreadCount})");
public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands; public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands;
@ -111,6 +117,12 @@ namespace PettingZoo.UI.Tab.Subscriber
{ {
RunFromUiScheduler(() => RunFromUiScheduler(() =>
{ {
if (!tabActive)
{
unreadCount++;
RaisePropertyChanged(nameof(Title));
}
Messages.Add(args.MessageInfo); Messages.Add(args.MessageInfo);
clearCommand.RaiseCanExecuteChanged(); clearCommand.RaiseCanExecuteChanged();
}); });
@ -131,6 +143,20 @@ namespace PettingZoo.UI.Tab.Subscriber
{ {
_ = Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, uiScheduler); _ = Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
} }
public void Activate()
{
tabActive = true;
unreadCount = 0;
RaisePropertyChanged(nameof(Title));
}
public void Deactivate()
{
tabActive = false;
}
} }
@ -149,6 +175,7 @@ namespace PettingZoo.UI.Tab.Subscriber
} }
public string QueueName => "dummy";
public string Exchange => "dummy"; public string Exchange => "dummy";
public string RoutingKey => "dummy"; public string RoutingKey => "dummy";

View File

@ -47,5 +47,17 @@ namespace PettingZoo.UI.Tab
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ToolbarCommands))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ToolbarCommands)));
}; };
} }
public void Activate()
{
(viewModel as ITabActivate)?.Activate();
}
public void Deactivate()
{
(viewModel as ITabActivate)?.Deactivate();
}
} }
} }

View File

@ -31,7 +31,7 @@ namespace PettingZoo.UI.Tab
public ITab CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null) public ITab CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null)
{ {
var viewModel = new PublisherViewModel(connection, fromReceivedMessage); var viewModel = new PublisherViewModel(tabHost, this, connection, fromReceivedMessage);
return new ViewTab<PublisherView, PublisherViewModel>( return new ViewTab<PublisherView, PublisherViewModel>(
closeTabCommand, closeTabCommand,
new PublisherView(viewModel), new PublisherView(viewModel),