1
0
mirror of synced 2024-11-21 20:33:49 +00:00

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<IConnectionFactory, RabbitMQClientConnectionFactory>();
container.Register<IConnectionDialog, WindowConnectionDialog>(); container.Register<IConnectionDialog, WindowConnectionDialog>();
container.Register<ISubscribeDialog, WindowSubscribeDialog>(); container.Register<ISubscribeDialog, WindowSubscribeDialog>();
container.Register<ITabFactory, ViewTabFactory>();
container.Register<MainWindow>(); 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; 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; WindowStartupLocation = WindowStartupLocation.CenterScreen;
InitializeComponent(); InitializeComponent();
viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog, tabFactory); viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog);
DataContext = viewModel; DataContext = viewModel;
Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted; Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted;

View File

@ -12,7 +12,7 @@ using PettingZoo.UI.Tab;
namespace PettingZoo.UI.Main namespace PettingZoo.UI.Main
{ {
public class MainWindowViewModel : BaseViewModel, IAsyncDisposable public class MainWindowViewModel : BaseViewModel, IAsyncDisposable, ITabHost
{ {
private readonly IConnectionFactory connectionFactory; private readonly IConnectionFactory connectionFactory;
private readonly IConnectionDialog connectionDialog; private readonly IConnectionDialog connectionDialog;
@ -66,12 +66,11 @@ namespace PettingZoo.UI.Main
public MainWindowViewModel(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog, public MainWindowViewModel(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog,
ISubscribeDialog subscribeDialog, ITabFactory tabFactory) ISubscribeDialog subscribeDialog)
{ {
this.connectionFactory = connectionFactory; this.connectionFactory = connectionFactory;
this.connectionDialog = connectionDialog; this.connectionDialog = connectionDialog;
this.subscribeDialog = subscribeDialog; this.subscribeDialog = subscribeDialog;
this.tabFactory = tabFactory;
connectionStatus = GetConnectionStatus(null); connectionStatus = GetConnectionStatus(null);
@ -81,6 +80,8 @@ namespace PettingZoo.UI.Main
publishCommand = new DelegateCommand(PublishExecute, IsConnectedCanExecute); publishCommand = new DelegateCommand(PublishExecute, IsConnectedCanExecute);
subscribeCommand = new DelegateCommand(SubscribeExecute, IsConnectedCanExecute); subscribeCommand = new DelegateCommand(SubscribeExecute, IsConnectedCanExecute);
closeTabCommand = new DelegateCommand(CloseTabExecute, CloseTabCanExecute); closeTabCommand = new DelegateCommand(CloseTabExecute, CloseTabCanExecute);
tabFactory = new ViewTabFactory(this, closeTabCommand);
} }
@ -113,7 +114,7 @@ namespace PettingZoo.UI.Main
if (connectionDialogParams.Subscribe) if (connectionDialogParams.Subscribe)
{ {
var subscriber = connection.Subscribe(connectionDialogParams.Exchange, connectionDialogParams.RoutingKey); 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; subscribeDialogParams = newParams;
var subscriber = connection.Subscribe(subscribeDialogParams.Exchange, subscribeDialogParams.RoutingKey); 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) if (connection == null)
return; 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); Tabs.Add(tab);
ActiveTab = tab; ActiveTab = tab;
@ -233,7 +234,7 @@ namespace PettingZoo.UI.Main
public class DesignTimeMainWindowViewModel : MainWindowViewModel 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 namespace PettingZoo.UI.Tab
{ {
@ -9,7 +8,7 @@ namespace PettingZoo.UI.Tab
public interface ITabFactory public interface ITabFactory
{ {
ITab CreateSubscriberTab(ICommand closeTabCommand, ISubscriber subscriber); ITab CreateSubscriberTab(IConnection connection, ISubscriber subscriber);
ITab CreatePublisherTab(ICommand closeTabCommand, IConnection connection); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
@ -73,7 +74,7 @@ namespace PettingZoo.UI.Tab.Publisher
public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands; public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands;
public PublisherViewModel(IConnection connection) public PublisherViewModel(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null)
{ {
this.connection = connection; this.connection = connection;
@ -84,6 +85,9 @@ namespace PettingZoo.UI.Tab.Publisher
new TabToolbarCommand(PublishCommand, PublisherViewStrings.CommandPublish, SvgIconHelper.LoadFromResource("/Images/PublishSend.svg")) new TabToolbarCommand(PublishCommand, PublisherViewStrings.CommandPublish, SvgIconHelper.LoadFromResource("/Images/PublishSend.svg"))
}; };
if (fromReceivedMessage != null)
SetMessageTypeControl(fromReceivedMessage);
else
SetMessageTypeControl(MessageType.Raw); SetMessageTypeControl(MessageType.Raw);
} }
@ -126,6 +130,82 @@ namespace PettingZoo.UI.Tab.Publisher
publishCommand.RaiseCanExecuteChanged(); 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> </Grid.ColumnDefinitions>
<ListBox Grid.Column="0" Grid.Row="0" <ListBox Grid.Column="0" Grid.Row="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Messages}" ItemsSource="{Binding Messages}"
SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay}" SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay}"
ui:ListBox.AutoScroll="True"> ui:ListBox.AutoScroll="True">
<ListBox.Resources>
<ui:BindingProxy x:Key="ContextMenuProxy" Data="{Binding}" />
</ListBox.Resources>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid> <Grid>
@ -30,6 +34,13 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding ReceivedTimestamp, StringFormat=g}" Style="{StaticResource Timestamp}"></TextBlock> <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> <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> </Grid>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
@ -58,8 +69,10 @@
<Label DockPanel.Dock="Top" Style="{StaticResource HeaderLabel}" Content="{x:Static res:SubscriberViewStrings.PanelTitleProperties}"/> <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 ItemsSource="{Binding SelectedMessageProperties}" AutoGenerateColumns="False" IsReadOnly="True" Style="{StaticResource Properties}">
<DataGrid.Columns> <DataGrid.Columns>
<!-- ReSharper disable Xaml.BindingWithContextNotResolved - bindings are correct -->
<DataGridTextColumn Binding="{Binding Key}" Header="{x:Static res:SubscriberViewStrings.PropertyName}" Width="100"/> <DataGridTextColumn Binding="{Binding Key}" Header="{x:Static res:SubscriberViewStrings.PropertyName}" Width="100"/>
<DataGridTextColumn Binding="{Binding Value}" Header="{x:Static res:SubscriberViewStrings.PropertyValue}" Width="*"/> <DataGridTextColumn Binding="{Binding Value}" Header="{x:Static res:SubscriberViewStrings.PropertyValue}" Width="*"/>
<!-- ReSharper restore Xaml.BindingWithContextNotResolved -->
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</DockPanel> </DockPanel>

View File

@ -9,12 +9,14 @@ using PettingZoo.Core.Rendering;
// TODO update title with unread message count if tab is not active // 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 export option (to Tapeti.Cmd compatible format / command-line of course)
// TODO send message to (new) publisher tab
namespace PettingZoo.UI.Tab.Subscriber namespace PettingZoo.UI.Tab.Subscriber
{ {
public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands
{ {
private readonly ITabHost tabHost;
private readonly ITabFactory tabFactory;
private readonly IConnection connection;
private readonly ISubscriber subscriber; private readonly ISubscriber subscriber;
private readonly TaskScheduler uiScheduler; private readonly TaskScheduler uiScheduler;
private ReceivedMessageInfo? selectedMessage; private ReceivedMessageInfo? selectedMessage;
@ -22,8 +24,11 @@ namespace PettingZoo.UI.Tab.Subscriber
private readonly TabToolbarCommand[] toolbarCommands; private readonly TabToolbarCommand[] toolbarCommands;
private IDictionary<string, string>? selectedMessageProperties; private IDictionary<string, string>? selectedMessageProperties;
private readonly DelegateCommand createPublisherCommand;
public ICommand ClearCommand => clearCommand; public ICommand ClearCommand => clearCommand;
public ICommand CreatePublisherCommand => createPublisherCommand;
public ObservableCollection<ReceivedMessageInfo> Messages { get; } public ObservableCollection<ReceivedMessageInfo> Messages { get; }
@ -52,8 +57,11 @@ namespace PettingZoo.UI.Tab.Subscriber
public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands; 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; this.subscriber = subscriber;
uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
@ -66,6 +74,8 @@ namespace PettingZoo.UI.Tab.Subscriber
new TabToolbarCommand(ClearCommand, SubscriberViewStrings.CommandClear, SvgIconHelper.LoadFromResource("/Images/Clear.svg")) new TabToolbarCommand(ClearCommand, SubscriberViewStrings.CommandClear, SvgIconHelper.LoadFromResource("/Images/Clear.svg"))
}; };
createPublisherCommand = new DelegateCommand(CreatePublisherExecute, CreatePublisherCanExecute);
subscriber.MessageReceived += SubscriberMessageReceived; subscriber.MessageReceived += SubscriberMessageReceived;
subscriber.Start(); 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) private void SubscriberMessageReceived(object? sender, MessageReceivedEventArgs args)
{ {
RunFromUiScheduler(() => RunFromUiScheduler(() =>
@ -96,6 +119,8 @@ namespace PettingZoo.UI.Tab.Subscriber
private void UpdateSelectedMessageProperties() private void UpdateSelectedMessageProperties()
{ {
createPublisherCommand.RaiseCanExecuteChanged();
SelectedMessageProperties = SelectedMessage != null SelectedMessageProperties = SelectedMessage != null
? MessagePropertiesRenderer.Render(SelectedMessage.Properties) ? MessagePropertiesRenderer.Render(SelectedMessage.Properties)
: null; : null;
@ -111,7 +136,7 @@ namespace PettingZoo.UI.Tab.Subscriber
public class DesignTimeSubscriberViewModel : SubscriberViewModel 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. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class SubscriberViewStrings { 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> /// <summary>
/// Looks up a localized string similar to Non-persistent. /// Looks up a localized string similar to Non-persistent.
/// </summary> /// </summary>

View File

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

View File

@ -7,9 +7,20 @@ namespace PettingZoo.UI.Tab
{ {
public class ViewTabFactory : ITabFactory 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>( return new ViewTab<SubscriberView, SubscriberViewModel>(
closeTabCommand, closeTabCommand,
new SubscriberView(viewModel), 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>( return new ViewTab<PublisherView, PublisherViewModel>(
closeTabCommand, closeTabCommand,
new PublisherView(viewModel), new PublisherView(viewModel),