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;
ISubscriber Subscribe(string exchange, string routingKey);
ISubscriber Subscribe();
Task Publish(PublishMessageInfo messageInfo);
}

View File

@ -4,8 +4,9 @@ namespace PettingZoo.Core.Connection
{
public interface ISubscriber : IAsyncDisposable
{
string Exchange {get; }
string RoutingKey { get; }
string? QueueName { get; }
string? Exchange {get; }
string? RoutingKey { get; }
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;
}
}
GC.SuppressFinalize(this);
}
public ISubscriber Subscribe(string exchange, string routingKey)
{
return CreateSubscriber(exchange, routingKey);
}
public ISubscriber Subscribe()
{
return CreateSubscriber(null, null);
}
private ISubscriber CreateSubscriber(string? exchange, string? routingKey)
{
lock (connectionLock)
{
var subscriber = new RabbitMQClientSubscriber(model, exchange, routingKey);
if (model != null)
if (model != null)
return subscriber;
void ConnectSubscriber(object? sender, StatusChangedEventArgs args)
{
if (args.Status != ConnectionStatus.Connected)
@ -67,20 +81,20 @@ namespace PettingZoo.RabbitMQ
{
if (model == null)
return;
subscriber.Connected(model);
}
StatusChanged -= ConnectSubscriber;
}
StatusChanged += ConnectSubscriber;
return subscriber;
}
}
public Task Publish(PublishMessageInfo messageInfo)
{
if (model == null)

View File

@ -13,12 +13,13 @@ namespace PettingZoo.RabbitMQ
private string? consumerTag;
private bool started;
public string Exchange { get; }
public string RoutingKey { get; }
public string? QueueName { get; private set; }
public string? Exchange { get; }
public string? RoutingKey { get; }
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public RabbitMQClientSubscriber(IModel? model, string exchange, string routingKey)
public RabbitMQClientSubscriber(IModel? model, string? exchange, string? routingKey)
{
this.model = model;
Exchange = exchange;
@ -28,6 +29,8 @@ namespace PettingZoo.RabbitMQ
public ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
if (model != null && consumerTag != null && model.IsOpen)
model.BasicCancelNoWait(consumerTag);
@ -41,13 +44,14 @@ namespace PettingZoo.RabbitMQ
if (model == null)
return;
var queueName = model.QueueDeclare().QueueName;
model.QueueBind(queueName, Exchange, RoutingKey);
QueueName = model.QueueDeclare().QueueName;
if (Exchange != null && RoutingKey != null)
model.QueueBind(QueueName, Exchange, RoutingKey);
var consumer = new EventingBasicConsumer(model);
consumer.Received += ClientReceived;
consumerTag = model.BasicConsume(queueName, true, consumer);
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.RabbitMQ", "PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj", "{220149F3-A8D6-44ED-B3B6-DFE506EB018A}"
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
Global
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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.Build.0 = Release|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7157B09C-FDD9-4928-B14D-C25B784CA865}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

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">
<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/=WPF/@EntryIndexedValue">WPF</s:String></wpf:ResourceDictionary>

View File

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

View File

@ -1,17 +1,14 @@
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using Newtonsoft.Json;
using PettingZoo.Core.Connection;
using PettingZoo.Core.Settings;
using PettingZoo.RabbitMQ;
using PettingZoo.Settings;
using PettingZoo.Settings.LiteDB;
using PettingZoo.UI.Connection;
using PettingZoo.UI.Main;
using PettingZoo.UI.Subscribe;
using PettingZoo.UI.Tab;
using SimpleInjector;
namespace PettingZoo
@ -37,11 +34,10 @@ namespace PettingZoo
// See comments in RunApplication
container.Options.EnableAutoVerification = false;
container.RegisterSingleton(() => new UserSettings(new AppDataSettingsSerializer("Settings.json")));
container.Register<IConnectionFactory, RabbitMQClientConnectionFactory>();
container.Register<IConnectionDialog, WindowConnectionDialog>();
container.Register<ISubscribeDialog, WindowSubscribeDialog>();
container.Register<IConnectionSettingsRepository, LiteDBConnectionSettingsRepository>();
container.Register<MainWindow>();
@ -72,50 +68,9 @@ namespace PettingZoo
var mainWindow = container.GetInstance<MainWindow>();
_ = app.Run(mainWindow);
}
catch (Exception)
catch (Exception e)
{
// TODO Log the exception and exit
}
}
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];
MessageBox.Show($"Fatal exception: {e.Message}", @"PettingZoo", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}

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}}">
<Setter Property="Margin" Value="8,0,0,0" />
</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}">
<Setter Property="ChildMargin" Value="4"/>
</Style>
@ -78,4 +82,11 @@
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style x:Key="Payload" TargetType="{x:Type TextBox}">
<Setter Property="AcceptsReturn" Value="True" />
<Setter Property="AcceptsTab" Value="True" />
<Setter Property="VerticalScrollBarVisibility" Value="Visible" />
<Setter Property="FontFamily" Value="Consolas,Courier New" />
</Style>
</ResourceDictionary>

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,
params string[]? otherPropertiesChanged)
DelegateCommand[]? delegateCommandsChanged = null,
string[]? otherPropertiesChanged = null)
{
if ((comparer ?? EqualityComparer<T>.Default).Equals(field, value))
return false;
@ -29,12 +24,18 @@ namespace PettingZoo.UI
field = value;
RaisePropertyChanged(propertyName);
if (otherPropertiesChanged == null)
return true;
foreach (var otherProperty in otherPropertiesChanged)
RaisePropertyChanged(otherProperty);
if (otherPropertiesChanged != null)
{
foreach (var otherProperty in otherPropertiesChanged)
RaisePropertyChanged(otherProperty);
}
if (delegateCommandsChanged != null)
{
foreach (var delegateCommand in delegateCommandsChanged)
delegateCommand.RaiseCanExecuteChanged();
}
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.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using PettingZoo.Core.Settings;
// TODO validate input
// TODO profiles
// TODO "save password" checkbox
namespace PettingZoo.UI.Connection
{
public class ConnectionViewModel : BaseViewModel
{
private string host;
private string virtualHost;
private readonly IConnectionSettingsRepository connectionSettingsRepository;
private readonly ConnectionSettings defaultSettings;
private string host = null!;
private string virtualHost = null!;
private int port;
private string username;
private string password;
private string username = null!;
private string password = null!;
private bool subscribe;
private string exchange;
private string routingKey;
private string exchange = null!;
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
{
get => host;
set => SetField(ref host, value);
set => SetField(ref host, value, delegateCommandsChanged: connectionChangedCommands);
}
public string VirtualHost
{
get => virtualHost;
set => SetField(ref virtualHost, value);
set => SetField(ref virtualHost, value, delegateCommandsChanged: connectionChangedCommands);
}
public int Port
{
get => port;
set => SetField(ref port, value);
set => SetField(ref port, value, delegateCommandsChanged: connectionChangedCommands);
}
public string Username
{
get => username;
set => SetField(ref username, value);
set => SetField(ref username, value, delegateCommandsChanged: connectionChangedCommands);
}
public string Password
@ -53,46 +67,142 @@ namespace PettingZoo.UI.Connection
public bool Subscribe
{
get => subscribe;
set => SetField(ref subscribe, value);
set => SetField(ref subscribe, value, delegateCommandsChanged: connectionChangedCommands);
}
public string Exchange
{
get => exchange;
set => SetField(ref exchange, value);
set
{
if (SetField(ref exchange, value, delegateCommandsChanged: connectionChangedCommands))
AutoToggleSubscribe();
}
}
public string 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 ConnectionViewModel(ConnectionDialogParams model)
public ConnectionViewModel(IConnectionSettingsRepository connectionSettingsRepository, ConnectionSettings defaultSettings)
{
OkCommand = new DelegateCommand(OkExecute, OkCanExecute);
host = model.Host;
virtualHost = model.VirtualHost;
port = model.Port;
username = model.Username;
password = model.Password;
this.connectionSettingsRepository = connectionSettingsRepository;
this.defaultSettings = defaultSettings;
subscribe = model.Subscribe;
exchange = model.Exchange;
routingKey = model.RoutingKey;
okCommand = new DelegateCommand(OkExecute, OkCanExecute);
saveCommand = new DelegateCommand(SaveExecute, SaveCanExecute);
saveAsCommand = new DelegateCommand(SaveAsExecute, SaveAsCanExecute);
deleteCommand = new DelegateCommand(DeleteExecute, DeleteCanExecute);
connectionChangedCommands = new[] { saveCommand, saveAsCommand, okCommand };
}
public 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 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"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance connection:DesignTimeConnectionViewModel, IsDesignTimeCreatable = True}"
Width="500"
Width="700"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStyle="ToolWindow"
Style="{StaticResource WindowStyle}"
Title="{x:Static connection:ConnectionWindowStrings.WindowTitle}"
FocusManager.FocusedElement="{Binding ElementName=HostTextBox}">
<DockPanel Margin="8">
<UniformGrid DockPanel.Dock="Bottom" HorizontalAlignment="Right" Rows="1" Columns="2" Style="{StaticResource FooterPanel}">
FocusManager.FocusedElement="{Binding ElementName=HostTextBox}"
WindowStartupLocation="CenterOwner">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<UniformGrid Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" Rows="1" Columns="1" Style="{StaticResource FooterPanel}">
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonDelete}" Style="{StaticResource FooterButtonLeft}" Command="{Binding DeleteCommand}"/>
</UniformGrid>
<UniformGrid Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Rows="1" Columns="3" Style="{StaticResource FooterPanel}">
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonSave}" Style="{StaticResource FooterButtonLeft}" Command="{Binding SaveCommand}"/>
<Button Content="{x:Static connection:ConnectionWindowStrings.ButtonSaveAs}" Style="{StaticResource FooterButtonLeft}" Command="{Binding SaveAsCommand}"/>
</UniformGrid>
<UniformGrid Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" Rows="1" Columns="2" Style="{StaticResource FooterPanel}">
<Button IsDefault="True" Content="{x:Static connection:ConnectionWindowStrings.ButtonOK}" Style="{StaticResource FooterButton}" Command="{Binding OkCommand}"/>
<Button IsCancel="True" Content="{x:Static connection:ConnectionWindowStrings.ButtonCancel}" Style="{StaticResource FooterButton}"/>
</UniformGrid>
<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>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
@ -37,28 +66,28 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static connection:ConnectionWindowStrings.LabelHost}"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Host}" 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}"/>
<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}"/>
<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}"/>
<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}"/>
<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}"/>
<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}"/>
<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>
</DockPanel>
</Grid>
</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 PettingZoo.Core.Settings;
namespace PettingZoo.UI.Connection
{
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)
{
Owner = Application.Current.MainWindow
@ -17,10 +32,15 @@ namespace PettingZoo.UI.Connection
{
window.DialogResult = true;
};
return window.ShowDialog().GetValueOrDefault()
? viewModel.ToModel()
: null;
if (!window.ShowDialog().GetValueOrDefault())
return null;
var newSettings = viewModel.ToModel();
await connectionSettingsRepository.StoreLastUsed(newSettings);
return newSettings;
}
}
@ -29,10 +49,8 @@ namespace PettingZoo.UI.Connection
{
public ConnectionWindow(ConnectionViewModel viewModel)
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;
InitializeComponent();
DataContext = viewModel;
InitializeComponent();
}
@ -41,5 +59,14 @@ namespace PettingZoo.UI.Connection
if (!char.IsDigit(args.Text, args.Text.Length - 1))
args.Handled = true;
}
private void CaretToEnd(object sender, RoutedEventArgs e)
{
if (sender is not TextBox textBox)
return;
textBox.CaretIndex = textBox.Text.Length;
}
}
}

View File

@ -19,7 +19,7 @@ namespace PettingZoo.UI.Connection {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ConnectionWindowStrings {
@ -70,7 +70,16 @@ namespace PettingZoo.UI.Connection {
}
/// <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>
public static string ButtonOK {
get {
@ -79,7 +88,43 @@ namespace PettingZoo.UI.Connection {
}
/// <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>
public static string LabelExchange {
get {
@ -88,7 +133,7 @@ namespace PettingZoo.UI.Connection {
}
/// <summary>
/// Looks up a localized string similar to Host:.
/// Looks up a localized string similar to Host.
/// </summary>
public static string LabelHost {
get {
@ -97,7 +142,7 @@ namespace PettingZoo.UI.Connection {
}
/// <summary>
/// Looks up a localized string similar to Password:.
/// Looks up a localized string similar to Password.
/// </summary>
public static string LabelPassword {
get {
@ -106,7 +151,7 @@ namespace PettingZoo.UI.Connection {
}
/// <summary>
/// Looks up a localized string similar to Port:.
/// Looks up a localized string similar to Port.
/// </summary>
public static string LabelPort {
get {
@ -115,7 +160,7 @@ namespace PettingZoo.UI.Connection {
}
/// <summary>
/// Looks up a localized string similar to Routing key:.
/// Looks up a localized string similar to Routing key.
/// </summary>
public static string LabelRoutingKey {
get {
@ -133,7 +178,7 @@ namespace PettingZoo.UI.Connection {
}
/// <summary>
/// Looks up a localized string similar to Username:.
/// Looks up a localized string similar to Username.
/// </summary>
public static string LabelUsername {
get {
@ -142,7 +187,7 @@ namespace PettingZoo.UI.Connection {
}
/// <summary>
/// Looks up a localized string similar to Virtual host:.
/// Looks up a localized string similar to Virtual host.
/// </summary>
public static string LabelVirtualHost {
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>
/// Looks up a localized string similar to Connection parameters.
/// </summary>

View File

@ -120,32 +120,50 @@
<data name="ButtonCancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="ButtonDelete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="ButtonOK" xml:space="preserve">
<value>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 name="LabelExchange" xml:space="preserve">
<value>Exchange:</value>
<value>Exchange</value>
</data>
<data name="LabelHost" xml:space="preserve">
<value>Host:</value>
<value>Host</value>
</data>
<data name="LabelPassword" xml:space="preserve">
<value>Password:</value>
<value>Password</value>
</data>
<data name="LabelPort" xml:space="preserve">
<value>Port:</value>
<value>Port</value>
</data>
<data name="LabelRoutingKey" xml:space="preserve">
<value>Routing key:</value>
<value>Routing key</value>
</data>
<data name="LabelSubscribe" xml:space="preserve">
<value>Subscribe</value>
</data>
<data name="LabelUsername" xml:space="preserve">
<value>Username:</value>
<value>Username</value>
</data>
<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 name="WindowTitle" xml:space="preserve">
<value>Connection parameters</value>

View File

@ -1,43 +1,11 @@
using System;
using System.Threading.Tasks;
using PettingZoo.Core.Settings;
namespace PettingZoo.UI.Connection
{
public interface IConnectionDialog
{
ConnectionDialogParams? Show(ConnectionDialogParams? defaultParams = null);
}
public class ConnectionDialogParams
{
public string Host { get; }
public string VirtualHost { get; }
public int Port { get; }
public string Username { get; }
public string Password { get; }
public bool Subscribe { get; }
public string Exchange { get; }
public string RoutingKey { get; }
public static ConnectionDialogParams Default { get; } = new("localhost", "/", 5672, "guest", "guest", false, "", "#");
public ConnectionDialogParams(string host, string virtualHost, int port, string username, string password, bool subscribe, string exchange, string routingKey)
{
if (subscribe && (string.IsNullOrEmpty(exchange) || string.IsNullOrEmpty(routingKey)))
throw new ArgumentException(@"Exchange and RoutingKey must be provided when Subscribe is true", nameof(subscribe));
Host = host;
VirtualHost = virtualHost;
Port = port;
Username = username;
Password = password;
Subscribe = subscribe;
Exchange = exchange;
RoutingKey = routingKey;
}
Task<ConnectionSettings?> Show();
}
}

View File

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

View File

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

View File

@ -30,8 +30,8 @@ namespace PettingZoo.UI.Subscribe
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;
InitializeComponent();
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; }
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:res="clr-namespace:PettingZoo.UI.Tab.Publisher"
xmlns:ui="clr-namespace:PettingZoo.UI"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance res:DesignTimePublisherViewModel, IsDesignTimeCreatable=True}"
Background="White">
<Grid Margin="4">
<ui:GridLayout Style="{StaticResource Form}" Margin="4" Grid.IsSharedSizeScope="True">
<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="*" />
</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.OptionMessageTypeTapeti}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeTapeti}" />
</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}" />
</ScrollViewer>
</Grid>
</ui:GridLayout>
</UserControl>

View File

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

View File

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using PettingZoo.Core.Connection;
// TODO publish button in page instead of just in toolbar
namespace PettingZoo.UI.Tab.Publisher
{
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 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 UserControl? messageTypeControl;
@ -29,6 +40,66 @@ namespace PettingZoo.UI.Tab.Publisher
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
{
get => messageType;
@ -70,13 +141,21 @@ namespace PettingZoo.UI.Tab.Publisher
// 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";
#pragma warning restore CA1822
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.tabFactory = tabFactory;
this.tabHost = tabHost;
publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute);
@ -109,7 +188,7 @@ namespace PettingZoo.UI.Tab.Publisher
switch (value)
{
case MessageType.Raw:
var rawPublisherViewModel = new RawPublisherViewModel(connection);
var rawPublisherViewModel = new RawPublisherViewModel(connection, this);
rawPublisherView ??= new RawPublisherView(rawPublisherViewModel);
MessageTypeControl = rawPublisherView;
@ -117,7 +196,7 @@ namespace PettingZoo.UI.Tab.Publisher
break;
case MessageType.Tapeti:
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection);
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this);
tapetiPublisherView ??= new TapetiPublisherView(tapetiPublisherViewModel);
MessageTypeControl = tapetiPublisherView;
@ -134,28 +213,49 @@ namespace PettingZoo.UI.Tab.Publisher
private void SetMessageTypeControl(ReceivedMessageInfo fromReceivedMessage)
{
Exchange = fromReceivedMessage.Exchange;
RoutingKey = fromReceivedMessage.RoutingKey;
if (TapetiPublisherViewModel.IsTapetiMessage(fromReceivedMessage))
{
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, fromReceivedMessage);
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, fromReceivedMessage);
tapetiPublisherView = new TapetiPublisherView(tapetiPublisherViewModel);
MessageType = MessageType.Tapeti;
}
else
{
var rawPublisherViewModel = new RawPublisherViewModel(connection, fromReceivedMessage);
var rawPublisherViewModel = new RawPublisherViewModel(connection, this, fromReceivedMessage);
rawPublisherView = new RawPublisherView(rawPublisherViewModel);
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 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>
/// Looks up a localized string similar to Message type: .
/// </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>
/// Looks up a localized string similar to Raw message.
/// </summary>

View File

@ -120,9 +120,33 @@
<data name="CommandPublish" xml:space="preserve">
<value>Publish</value>
</data>
<data name="LabelExchange" xml:space="preserve">
<value>Exchange</value>
</data>
<data name="LabelMessageType" xml:space="preserve">
<value>Message type: </value>
</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">
<value>Raw message</value>
</data>

View File

@ -11,17 +11,11 @@
Background="White">
<ui:GridLayout Style="{StaticResource Form}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="Label" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<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" />
@ -42,31 +36,14 @@
</ui:GridLayout.RowDefinitions>
<Label Grid.Row="0" Grid.Column="1">
<StackPanel Orientation="Horizontal">
<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}">
<Label Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelDeliveryMode}" />
<ComboBox Grid.Column="1" SelectedIndex="{Binding DeliveryModeIndex}">
<ComboBoxItem Content="{x:Static publisher:RawPublisherViewStrings.DeliveryModeNonPersistent}" />
<ComboBoxItem Content="{x:Static publisher:RawPublisherViewStrings.DeliveryModePersistent}" />
</ComboBox>
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelHeaders}" />
<ItemsControl Grid.Row="6" Grid.Column="1" ItemsSource="{Binding Headers}">
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelHeaders}" />
<ItemsControl Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Headers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
@ -116,43 +93,40 @@
</ItemsControl.ItemTemplate>
</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}" />
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding ContentType}" />
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelContentType}" />
<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}" />
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding CorrelationId}" />
<Label Grid.Row="4" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelCorrelationId}" />
<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}" />
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding ReplyTo}" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelAppId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<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}" />
<TextBox Grid.Row="11" Grid.Column="1" Text="{Binding AppId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelContentEncoding}" 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}" />
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding ContentEncoding}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelExpiration}" 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}" />
<TextBox Grid.Row="13" Grid.Column="1" Text="{Binding Expiration}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="8" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelMessageId}" 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}" />
<TextBox Grid.Row="14" Grid.Column="1" Text="{Binding MessageId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="9" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelPriority}" 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}" />
<TextBox Grid.Row="15" Grid.Column="1" Text="{Binding Priority}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="10" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelTimestamp}" 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}" />
<TextBox Grid.Row="16" Grid.Column="1" Text="{Binding Timestamp}" Visibility="{Binding PropertiesExpandedVisibility}" />
<Label Grid.Row="11" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelType}" 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}" />
<TextBox Grid.Row="17" Grid.Column="1" Text="{Binding TypeProperty}" 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}" />
<Label Grid.Row="12" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelUserId}" Visibility="{Binding PropertiesExpandedVisibility}" />
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding UserId, UpdateSourceTrigger=PropertyChanged}" 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>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter />
@ -160,7 +134,7 @@
</Button.Template>
</Button>
<Label Grid.Row="21" 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" />
<Label Grid.Row="15" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelPayload}" />
<TextBox Grid.Row="15" Grid.Column="1" Text="{Binding Payload, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Payload}" Height="150" />
</ui:GridLayout>
</UserControl>

View File

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

View File

@ -13,20 +13,15 @@ namespace PettingZoo.UI.Tab.Publisher
public class RawPublisherViewModel : BaseViewModel
{
private readonly IConnection connection;
private readonly IPublishDestination publishDestination;
private readonly DelegateCommand publishCommand;
private readonly DelegateCommand propertiesExpandCollapseCommand;
private bool propertiesExpanded;
private bool sendToExchange = true;
private string exchange = "";
private string routingKey = "";
private string queue = "";
private MessageDeliveryMode deliveryMode;
private string contentType = "application/json";
private string correlationId = "";
private string replyTo = "";
private string appId = "";
private string contentEncoding = "";
private string expiration = "";
@ -38,44 +33,6 @@ namespace PettingZoo.UI.Tab.Publisher
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
{
@ -98,13 +55,6 @@ namespace PettingZoo.UI.Tab.Publisher
}
public string ReplyTo
{
get => replyTo;
set => SetField(ref replyTo, value);
}
public string AppId
{
get => appId;
@ -194,20 +144,17 @@ namespace PettingZoo.UI.Tab.Publisher
protected Header LastHeader;
public RawPublisherViewModel(IConnection connection, ReceivedMessageInfo? receivedMessage = null)
public RawPublisherViewModel(IConnection connection, IPublishDestination publishDestination, ReceivedMessageInfo? receivedMessage = null)
{
this.connection = connection;
this.publishDestination = publishDestination;
publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute);
propertiesExpandCollapseCommand = new DelegateCommand(PropertiesExpandCollapseExecute);
if (receivedMessage != null)
{
Exchange = receivedMessage.Exchange;
RoutingKey = receivedMessage.RoutingKey;
CorrelationId = receivedMessage.Properties.CorrelationId ?? "";
ReplyTo = receivedMessage.Properties.ReplyTo ?? "";
Priority = receivedMessage.Properties.Priority?.ToString() ?? "";
AppId = receivedMessage.Properties.AppId ?? "";
ContentEncoding = receivedMessage.Properties.ContentEncoding ?? "";
@ -270,15 +217,14 @@ namespace PettingZoo.UI.Tab.Publisher
}
// 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);
// TODO background worker / async
connection.Publish(new PublishMessageInfo(
SendToExchange ? Exchange : "",
SendToExchange ? RoutingKey : Queue,
publishDestination.Exchange,
publishDestination.RoutingKey,
Encoding.UTF8.GetBytes(Payload),
new MessageProperties(headers)
{
@ -290,7 +236,7 @@ namespace PettingZoo.UI.Tab.Publisher
Expiration = NullIfEmpty(Expiration),
MessageId = NullIfEmpty(MessageId),
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,
Type = NullIfEmpty(TypeProperty),
UserId = NullIfEmpty(UserId)
@ -341,7 +287,7 @@ namespace PettingZoo.UI.Tab.Publisher
public class DesignTimeRawPublisherViewModel : RawPublisherViewModel
{
public DesignTimeRawPublisherViewModel() : base(null!)
public DesignTimeRawPublisherViewModel() : base(null!, null!)
{
PropertiesExpanded = true;
@ -349,9 +295,5 @@ namespace PettingZoo.UI.Tab.Publisher
capturedLastHeader.Key = "Example";
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>
/// Looks up a localized string similar to Expiration.
/// </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>
/// Looks up a localized string similar to Timestamp.
/// </summary>

View File

@ -144,9 +144,6 @@
<data name="LabelDeliveryMode" xml:space="preserve">
<value>Delivery mode</value>
</data>
<data name="LabelExchange" xml:space="preserve">
<value>Exchange</value>
</data>
<data name="LabelExpiration" xml:space="preserve">
<value>Expiration</value>
</data>
@ -165,21 +162,6 @@
<data name="LabelProperties" xml:space="preserve">
<value>Properties</value>
</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">
<value>Timestamp</value>
</data>

View File

@ -11,17 +11,11 @@
Background="White">
<ui:GridLayout Style="{StaticResource Form}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="Label" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<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" />
@ -32,47 +26,28 @@
</ui:GridLayout.RowDefinitions>
<Label Grid.Row="0" Grid.Column="1">
<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.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelProperties}" Style="{StaticResource SectionLabel}"/>
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelExchange}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Exchange}" 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 CorrelationId, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelRoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="4" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelAssemblyName}" />
<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}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Queue}" Visibility="{Binding QueueVisibility}" />
<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">
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelClassName}" />
<Grid Grid.Row="5" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</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}" />
-->
</Grid>
<Label Grid.Row="12" 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" />
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelPayload}" />
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding Payload, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Payload}" Height="150" />
</ui:GridLayout>
</UserControl>

View File

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

View File

@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;
using PettingZoo.Core.Connection;
using IConnection = PettingZoo.Core.Connection.IConnection;
@ -10,68 +9,15 @@ namespace PettingZoo.UI.Tab.Publisher
public class TapetiPublisherViewModel : BaseViewModel
{
private readonly IConnection connection;
private readonly IPublishDestination publishDestination;
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 replyTo = "";
private string payload = "";
private string className = "";
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
{
get => correlationId;
@ -79,13 +25,6 @@ namespace PettingZoo.UI.Tab.Publisher
}
public string ReplyTo
{
get => replyTo;
set => SetField(ref replyTo, value);
}
public string 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.publishDestination = publishDestination;
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))
return;
Exchange = receivedMessage.Exchange;
RoutingKey = receivedMessage.RoutingKey;
AssemblyName = receivedAssemblyName;
ClassName = receivedClassName;
CorrelationId = receivedMessage.Properties.CorrelationId ?? "";
ReplyTo = receivedMessage.Properties.ReplyTo ?? "";
Payload = Encoding.UTF8.GetString(receivedMessage.Body);
}
@ -171,12 +106,11 @@ namespace PettingZoo.UI.Tab.Publisher
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
connection.Publish(new PublishMessageInfo(
SendToExchange ? Exchange : "",
SendToExchange ? RoutingKey : Queue,
publishDestination.Exchange,
publishDestination.RoutingKey,
Encoding.UTF8.GetBytes(Payload),
new MessageProperties(new Dictionary<string, string>
{
@ -185,8 +119,8 @@ namespace PettingZoo.UI.Tab.Publisher
{
ContentType = @"application/json",
CorrelationId = NullIfEmpty(CorrelationId),
DeliveryMode = deliveryMode,
ReplyTo = NullIfEmpty(ReplyTo)
DeliveryMode = MessageDeliveryMode.Persistent,
ReplyTo = publishDestination.GetReplyTo()
}));
}
@ -201,12 +135,12 @@ namespace PettingZoo.UI.Tab.Publisher
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>
/// Looks up a localized string similar to Payload.
/// </summary>
@ -149,50 +140,5 @@ namespace PettingZoo.UI.Tab.Publisher {
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">
<value>Delivery mode</value>
</data>
<data name="LabelExchange" xml:space="preserve">
<value>Exchange</value>
</data>
<data name="LabelPayload" xml:space="preserve">
<value>Payload</value>
</data>
<data name="LabelProperties" xml:space="preserve">
<value>Properties</value>
</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>

View File

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

View File

@ -7,12 +7,11 @@ using System.Windows.Input;
using PettingZoo.Core.Connection;
using PettingZoo.Core.Rendering;
// TODO update title with unread message count if tab is not active
// TODO export option (to Tapeti.Cmd compatible format / command-line of course)
// TODO visual hint of where the last read message was when activating the tab again
namespace PettingZoo.UI.Tab.Subscriber
{
public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands
public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands, ITabActivate
{
private readonly ITabHost tabHost;
private readonly ITabFactory tabFactory;
@ -26,8 +25,13 @@ namespace PettingZoo.UI.Tab.Subscriber
private readonly DelegateCommand createPublisherCommand;
private bool tabActive;
private int unreadCount;
public ICommand ClearCommand => clearCommand;
// ReSharper disable once UnusedMember.Global - it is, but via a proxy
public ICommand CreatePublisherCommand => createPublisherCommand;
public ObservableCollection<ReceivedMessageInfo> Messages { get; }
@ -53,7 +57,9 @@ namespace PettingZoo.UI.Tab.Subscriber
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;
@ -111,6 +117,12 @@ namespace PettingZoo.UI.Tab.Subscriber
{
RunFromUiScheduler(() =>
{
if (!tabActive)
{
unreadCount++;
RaisePropertyChanged(nameof(Title));
}
Messages.Add(args.MessageInfo);
clearCommand.RaiseCanExecuteChanged();
});
@ -131,6 +143,20 @@ namespace PettingZoo.UI.Tab.Subscriber
{
_ = 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 RoutingKey => "dummy";

View File

@ -47,5 +47,17 @@ namespace PettingZoo.UI.Tab
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)
{
var viewModel = new PublisherViewModel(connection, fromReceivedMessage);
var viewModel = new PublisherViewModel(tabHost, this, connection, fromReceivedMessage);
return new ViewTab<PublisherView, PublisherViewModel>(
closeTabCommand,
new PublisherView(viewModel),