Added option to create publisher tab from received message

This commit is contained in:
Mark van Renswoude 2021-12-15 12:02:05 +01:00
parent 057cac4e22
commit e9de38b556
12 changed files with 196 additions and 25 deletions

View File

@ -42,7 +42,6 @@ namespace PettingZoo
container.Register<IConnectionFactory, RabbitMQClientConnectionFactory>();
container.Register<IConnectionDialog, WindowConnectionDialog>();
container.Register<ISubscribeDialog, WindowSubscribeDialog>();
container.Register<ITabFactory, ViewTabFactory>();
container.Register<MainWindow>();

View File

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

View File

@ -16,12 +16,12 @@ namespace PettingZoo.UI.Main
private readonly MainWindowViewModel viewModel;
public MainWindow(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog, ISubscribeDialog subscribeDialog, ITabFactory tabFactory)
public MainWindow(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog, ISubscribeDialog subscribeDialog)
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
InitializeComponent();
viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog, tabFactory);
viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog);
DataContext = viewModel;
Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted;

View File

@ -12,7 +12,7 @@ using PettingZoo.UI.Tab;
namespace PettingZoo.UI.Main
{
public class MainWindowViewModel : BaseViewModel, IAsyncDisposable
public class MainWindowViewModel : BaseViewModel, IAsyncDisposable, ITabHost
{
private readonly IConnectionFactory connectionFactory;
private readonly IConnectionDialog connectionDialog;
@ -66,12 +66,11 @@ namespace PettingZoo.UI.Main
public MainWindowViewModel(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog,
ISubscribeDialog subscribeDialog, ITabFactory tabFactory)
ISubscribeDialog subscribeDialog)
{
this.connectionFactory = connectionFactory;
this.connectionDialog = connectionDialog;
this.subscribeDialog = subscribeDialog;
this.tabFactory = tabFactory;
connectionStatus = GetConnectionStatus(null);
@ -81,6 +80,8 @@ namespace PettingZoo.UI.Main
publishCommand = new DelegateCommand(PublishExecute, IsConnectedCanExecute);
subscribeCommand = new DelegateCommand(SubscribeExecute, IsConnectedCanExecute);
closeTabCommand = new DelegateCommand(CloseTabExecute, CloseTabCanExecute);
tabFactory = new ViewTabFactory(this, closeTabCommand);
}
@ -113,7 +114,7 @@ namespace PettingZoo.UI.Main
if (connectionDialogParams.Subscribe)
{
var subscriber = connection.Subscribe(connectionDialogParams.Exchange, connectionDialogParams.RoutingKey);
AddTab(tabFactory.CreateSubscriberTab(CloseTabCommand, subscriber));
AddTab(tabFactory.CreateSubscriberTab(connection, subscriber));
}
@ -150,7 +151,7 @@ namespace PettingZoo.UI.Main
subscribeDialogParams = newParams;
var subscriber = connection.Subscribe(subscribeDialogParams.Exchange, subscribeDialogParams.RoutingKey);
AddTab(tabFactory.CreateSubscriberTab(CloseTabCommand, subscriber));
AddTab(tabFactory.CreateSubscriberTab(connection, subscriber));
}
@ -159,7 +160,7 @@ namespace PettingZoo.UI.Main
if (connection == null)
return;
AddTab(tabFactory.CreatePublisherTab(CloseTabCommand, connection));
AddTab(tabFactory.CreatePublisherTab(connection));
}
@ -194,7 +195,7 @@ namespace PettingZoo.UI.Main
}
private void AddTab(ITab tab)
public void AddTab(ITab tab)
{
Tabs.Add(tab);
ActiveTab = tab;
@ -233,7 +234,7 @@ namespace PettingZoo.UI.Main
public class DesignTimeMainWindowViewModel : MainWindowViewModel
{
public DesignTimeMainWindowViewModel() : base(null!, null!, null!, null!)
public DesignTimeMainWindowViewModel() : base(null!, null!, null!)
{
}
}

View File

@ -1,5 +1,4 @@
using System.Windows.Input;
using PettingZoo.Core.Connection;
using PettingZoo.Core.Connection;
namespace PettingZoo.UI.Tab
{
@ -9,7 +8,7 @@ namespace PettingZoo.UI.Tab
public interface ITabFactory
{
ITab CreateSubscriberTab(ICommand closeTabCommand, ISubscriber subscriber);
ITab CreatePublisherTab(ICommand closeTabCommand, IConnection connection);
ITab CreateSubscriberTab(IConnection connection, ISubscriber subscriber);
ITab CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null);
}
}

View File

@ -0,0 +1,7 @@
namespace PettingZoo.UI.Tab
{
public interface ITabHost
{
void AddTab(ITab tab);
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Controls;
using System.Windows.Input;
using PettingZoo.Core.Connection;
@ -73,7 +74,7 @@ namespace PettingZoo.UI.Tab.Publisher
public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands;
public PublisherViewModel(IConnection connection)
public PublisherViewModel(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null)
{
this.connection = connection;
@ -84,7 +85,10 @@ namespace PettingZoo.UI.Tab.Publisher
new TabToolbarCommand(PublishCommand, PublisherViewStrings.CommandPublish, SvgIconHelper.LoadFromResource("/Images/PublishSend.svg"))
};
SetMessageTypeControl(MessageType.Raw);
if (fromReceivedMessage != null)
SetMessageTypeControl(fromReceivedMessage);
else
SetMessageTypeControl(MessageType.Raw);
}
@ -126,6 +130,82 @@ namespace PettingZoo.UI.Tab.Publisher
publishCommand.RaiseCanExecuteChanged();
}
private void SetMessageTypeControl(ReceivedMessageInfo fromReceivedMessage)
{
// TODO move to individual viewmodels?
if (IsTapetiMessage(fromReceivedMessage, out var assemblyName, out var className))
{
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection)
{
Exchange = fromReceivedMessage.Exchange,
RoutingKey = fromReceivedMessage.RoutingKey,
AssemblyName = assemblyName,
ClassName = className,
CorrelationId = fromReceivedMessage.Properties.CorrelationId ?? "",
ReplyTo = fromReceivedMessage.Properties.ReplyTo ?? "",
Payload = Encoding.UTF8.GetString(fromReceivedMessage.Body)
};
tapetiPublisherView ??= new TapetiPublisherView(tapetiPublisherViewModel);
SetMessageTypeControl(MessageType.Tapeti);
}
else
{
var rawPublisherViewModel = new RawPublisherViewModel(connection)
{
Exchange = fromReceivedMessage.Exchange,
RoutingKey = fromReceivedMessage.RoutingKey,
CorrelationId = fromReceivedMessage.Properties.CorrelationId ?? "",
ReplyTo = fromReceivedMessage.Properties.ReplyTo ?? "",
Priority = fromReceivedMessage.Properties.Priority?.ToString() ?? "",
AppId = fromReceivedMessage.Properties.AppId ?? "",
ContentEncoding = fromReceivedMessage.Properties.ContentEncoding ?? "",
ContentType = fromReceivedMessage.Properties.ContentType ?? "",
Expiration = fromReceivedMessage.Properties.Expiration ?? "",
MessageId = fromReceivedMessage.Properties.MessageId ?? "",
Timestamp = fromReceivedMessage.Properties.Timestamp?.ToString() ?? "",
TypeProperty = fromReceivedMessage.Properties.Type ?? "",
UserId = fromReceivedMessage.Properties.UserId ?? "",
Payload = Encoding.UTF8.GetString(fromReceivedMessage.Body)
};
foreach (var header in fromReceivedMessage.Properties.Headers)
rawPublisherViewModel.Headers.Add(new RawPublisherViewModel.Header
{
Key = header.Key,
Value = header.Value
});
rawPublisherView = new RawPublisherView(rawPublisherViewModel);
SetMessageTypeControl(MessageType.Raw);
}
}
private static bool IsTapetiMessage(ReceivedMessageInfo receivedMessage, out string assemblyName, out string className)
{
assemblyName = "";
className = "";
if (receivedMessage.Properties.ContentType != @"application/json")
return false;
if (!receivedMessage.Properties.Headers.TryGetValue(@"classType", out var classType))
return false;
var parts = classType.Split(':');
if (parts.Length != 2)
return false;
className = parts[0];
assemblyName = parts[1];
return true;
}
}

View File

@ -18,9 +18,13 @@
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" Grid.Row="0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Messages}"
SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay}"
ui:ListBox.AutoScroll="True">
<ListBox.Resources>
<ui:BindingProxy x:Key="ContextMenuProxy" Data="{Binding}" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
@ -30,6 +34,13 @@
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding ReceivedTimestamp, StringFormat=g}" Style="{StaticResource Timestamp}"></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding RoutingKey}" Style="{StaticResource RoutingKey}"></TextBlock>
<Grid.ContextMenu>
<ContextMenu>
<!-- ReSharper disable once Xaml.BindingWithContextNotResolved - binding is correct, just weird because of the required proxy -->
<MenuItem Header="{x:Static res:SubscriberViewStrings.ContextPublish}" Command="{Binding Data.CreatePublisherCommand, Source={StaticResource ContextMenuProxy}}" InputGestureText="Ctrl+P" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
@ -58,8 +69,10 @@
<Label DockPanel.Dock="Top" Style="{StaticResource HeaderLabel}" Content="{x:Static res:SubscriberViewStrings.PanelTitleProperties}"/>
<DataGrid ItemsSource="{Binding SelectedMessageProperties}" AutoGenerateColumns="False" IsReadOnly="True" Style="{StaticResource Properties}">
<DataGrid.Columns>
<!-- ReSharper disable Xaml.BindingWithContextNotResolved - bindings are correct -->
<DataGridTextColumn Binding="{Binding Key}" Header="{x:Static res:SubscriberViewStrings.PropertyName}" Width="100"/>
<DataGridTextColumn Binding="{Binding Value}" Header="{x:Static res:SubscriberViewStrings.PropertyValue}" Width="*"/>
<!-- ReSharper restore Xaml.BindingWithContextNotResolved -->
</DataGrid.Columns>
</DataGrid>
</DockPanel>

View File

@ -9,12 +9,14 @@ 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 send message to (new) publisher tab
namespace PettingZoo.UI.Tab.Subscriber
{
public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands
{
private readonly ITabHost tabHost;
private readonly ITabFactory tabFactory;
private readonly IConnection connection;
private readonly ISubscriber subscriber;
private readonly TaskScheduler uiScheduler;
private ReceivedMessageInfo? selectedMessage;
@ -22,8 +24,11 @@ namespace PettingZoo.UI.Tab.Subscriber
private readonly TabToolbarCommand[] toolbarCommands;
private IDictionary<string, string>? selectedMessageProperties;
private readonly DelegateCommand createPublisherCommand;
public ICommand ClearCommand => clearCommand;
public ICommand CreatePublisherCommand => createPublisherCommand;
public ObservableCollection<ReceivedMessageInfo> Messages { get; }
@ -52,8 +57,11 @@ namespace PettingZoo.UI.Tab.Subscriber
public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands;
public SubscriberViewModel(ISubscriber subscriber)
public SubscriberViewModel(ITabHost tabHost, ITabFactory tabFactory, IConnection connection, ISubscriber subscriber)
{
this.tabHost = tabHost;
this.tabFactory = tabFactory;
this.connection = connection;
this.subscriber = subscriber;
uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
@ -66,6 +74,8 @@ namespace PettingZoo.UI.Tab.Subscriber
new TabToolbarCommand(ClearCommand, SubscriberViewStrings.CommandClear, SvgIconHelper.LoadFromResource("/Images/Clear.svg"))
};
createPublisherCommand = new DelegateCommand(CreatePublisherExecute, CreatePublisherCanExecute);
subscriber.MessageReceived += SubscriberMessageReceived;
subscriber.Start();
}
@ -84,6 +94,19 @@ namespace PettingZoo.UI.Tab.Subscriber
}
private void CreatePublisherExecute()
{
var publisherTab = tabFactory.CreatePublisherTab(connection, SelectedMessage);
tabHost.AddTab(publisherTab);
}
private bool CreatePublisherCanExecute()
{
return SelectedMessage != null;
}
private void SubscriberMessageReceived(object? sender, MessageReceivedEventArgs args)
{
RunFromUiScheduler(() =>
@ -96,6 +119,8 @@ namespace PettingZoo.UI.Tab.Subscriber
private void UpdateSelectedMessageProperties()
{
createPublisherCommand.RaiseCanExecuteChanged();
SelectedMessageProperties = SelectedMessage != null
? MessagePropertiesRenderer.Render(SelectedMessage.Properties)
: null;
@ -111,7 +136,7 @@ namespace PettingZoo.UI.Tab.Subscriber
public class DesignTimeSubscriberViewModel : SubscriberViewModel
{
public DesignTimeSubscriberViewModel() : base(new DesignTimeSubscriber())
public DesignTimeSubscriberViewModel() : base(null!, null!, null!, new DesignTimeSubscriber())
{
}

View File

@ -19,7 +19,7 @@ namespace PettingZoo.UI.Tab.Subscriber {
// 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 SubscriberViewStrings {
@ -69,6 +69,15 @@ namespace PettingZoo.UI.Tab.Subscriber {
}
}
/// <summary>
/// Looks up a localized string similar to Open in new Publisher tab.
/// </summary>
public static string ContextPublish {
get {
return ResourceManager.GetString("ContextPublish", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Non-persistent.
/// </summary>

View File

@ -120,6 +120,9 @@
<data name="CommandClear" xml:space="preserve">
<value>Clear</value>
</data>
<data name="ContextPublish" xml:space="preserve">
<value>Open in new Publisher tab</value>
</data>
<data name="DeliveryModeNonPersistent" xml:space="preserve">
<value>Non-persistent</value>
</data>

View File

@ -7,9 +7,20 @@ namespace PettingZoo.UI.Tab
{
public class ViewTabFactory : ITabFactory
{
public ITab CreateSubscriberTab(ICommand closeTabCommand, ISubscriber subscriber)
private readonly ITabHost tabHost;
private readonly ICommand closeTabCommand;
public ViewTabFactory(ITabHost tabHost, ICommand closeTabCommand)
{
var viewModel = new SubscriberViewModel(subscriber);
this.tabHost = tabHost;
this.closeTabCommand = closeTabCommand;
}
public ITab CreateSubscriberTab(IConnection connection, ISubscriber subscriber)
{
var viewModel = new SubscriberViewModel(tabHost, this, connection, subscriber);
return new ViewTab<SubscriberView, SubscriberViewModel>(
closeTabCommand,
new SubscriberView(viewModel),
@ -18,9 +29,9 @@ namespace PettingZoo.UI.Tab
}
public ITab CreatePublisherTab(ICommand closeTabCommand, IConnection connection)
public ITab CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null)
{
var viewModel = new PublisherViewModel(connection);
var viewModel = new PublisherViewModel(connection, fromReceivedMessage);
return new ViewTab<PublisherView, PublisherViewModel>(
closeTabCommand,
new PublisherView(viewModel),