Fixed #2: Improve connection status and changing servers

This commit is contained in:
Mark van Renswoude 2022-01-30 11:12:35 +01:00
parent d6b9970d51
commit 3d229b5ea8
17 changed files with 350 additions and 85 deletions

View File

@ -17,5 +17,11 @@
Username = username;
Password = password;
}
public override string ToString()
{
return $"{Host}:{Port}{VirtualHost}";
}
}
}

View File

@ -0,0 +1,100 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace PettingZoo.Core.Connection
{
public class DynamicConnection : IConnection
{
public Guid ConnectionId => currentConnection?.ConnectionId ?? Guid.Empty;
public ConnectionParams? ConnectionParams { get; private set; }
public ConnectionStatus Status { get; private set; } = ConnectionStatus.Disconnected;
public event EventHandler<StatusChangedEventArgs>? StatusChanged;
private IConnection? currentConnection;
public async ValueTask DisposeAsync()
{
if (currentConnection != null)
await currentConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
public void Connect()
{
CheckConnection();
currentConnection.Connect();
}
public async ValueTask Disconnect()
{
if (currentConnection == null)
return;
var disconnectedConnectionId = currentConnection.ConnectionId;
await currentConnection.DisposeAsync();
currentConnection = null;
ConnectionStatusChanged(this, new StatusChangedEventArgs(disconnectedConnectionId, ConnectionStatus.Disconnected));
}
public void SetConnection(IConnection connection)
{
if (currentConnection != null)
{
currentConnection.StatusChanged -= ConnectionStatusChanged;
ConnectionStatusChanged(this, new StatusChangedEventArgs(currentConnection.ConnectionId, ConnectionStatus.Disconnected));
}
currentConnection = connection;
// Assume we get the new connection before Connect is called, thus before the status changes
if (currentConnection != null)
currentConnection.StatusChanged += ConnectionStatusChanged;
}
public ISubscriber Subscribe(string exchange, string routingKey)
{
CheckConnection();
return currentConnection.Subscribe(exchange, routingKey);
}
public ISubscriber Subscribe()
{
CheckConnection();
return currentConnection.Subscribe();
}
public Task Publish(PublishMessageInfo messageInfo)
{
CheckConnection();
return currentConnection.Publish(messageInfo);
}
private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs e)
{
ConnectionParams = e.ConnectionParams;
Status = e.Status;
StatusChanged?.Invoke(sender, e);
}
[MemberNotNull(nameof(currentConnection))]
private void CheckConnection()
{
if (currentConnection == null)
throw new InvalidOperationException("No current connection");
}
}
}

View File

@ -5,8 +5,15 @@ namespace PettingZoo.Core.Connection
{
public interface IConnection : IAsyncDisposable
{
Guid ConnectionId { get; }
ConnectionParams? ConnectionParams { get; }
ConnectionStatus Status { get; }
event EventHandler<StatusChangedEventArgs> StatusChanged;
void Connect();
ISubscriber Subscribe(string exchange, string routingKey);
ISubscriber Subscribe();
@ -25,14 +32,18 @@ namespace PettingZoo.Core.Connection
public class StatusChangedEventArgs : EventArgs
{
public Guid ConnectionId { get; }
public ConnectionStatus Status { get; }
public string? Context { get; }
public ConnectionParams? ConnectionParams { get; }
public Exception? Exception { get; }
public StatusChangedEventArgs(ConnectionStatus status, string? context)
public StatusChangedEventArgs(Guid connectionId, ConnectionStatus status, ConnectionParams? connectionParams = null, Exception? exception = null)
{
ConnectionId = connectionId;
Status = status;
Context = context;
ConnectionParams = connectionParams;
Exception = exception;
}
}
}

View File

@ -3,51 +3,56 @@ using System.Threading;
using System.Threading.Tasks;
using PettingZoo.Core.Connection;
using RabbitMQ.Client;
using IConnection = RabbitMQ.Client.IConnection;
namespace PettingZoo.RabbitMQ
{
public class RabbitMQClientConnection : Core.Connection.IConnection
{
public Guid ConnectionId { get; } = Guid.NewGuid();
public ConnectionParams? ConnectionParams { get; }
public ConnectionStatus Status { get; set; }
public event EventHandler<StatusChangedEventArgs>? StatusChanged;
private const int ConnectRetryDelay = 5000;
private readonly CancellationTokenSource connectionTaskToken = new();
private readonly Task connectionTask;
private Task? connectionTask;
private readonly object connectionLock = new();
private global::RabbitMQ.Client.IConnection? connection;
private IModel? model;
private IConnection? connection;
public event EventHandler<StatusChangedEventArgs>? StatusChanged;
public RabbitMQClientConnection(ConnectionParams connectionParams)
{
connectionTask = Task.Factory.StartNew(() => TryConnection(connectionParams, connectionTaskToken.Token), CancellationToken.None);
ConnectionParams = connectionParams;
}
public async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
if (connectionTask == null)
return;
connectionTaskToken.Cancel();
if (!connectionTask.IsCompleted)
await connectionTask;
lock (connectionLock)
{
if (model != null)
{
model.Dispose();
model = null;
}
if (connection != null)
{
connection.Dispose();
connection = null;
}
}
}
GC.SuppressFinalize(this);
public void Connect()
{
connectionTask = Task.Factory.StartNew(() => TryConnection(ConnectionParams!, connectionTaskToken.Token), CancellationToken.None);
}
@ -67,6 +72,7 @@ namespace PettingZoo.RabbitMQ
{
lock (connectionLock)
{
var model = connection?.CreateModel();
var subscriber = new RabbitMQClientSubscriber(model, exchange, routingKey);
if (model != null)
return subscriber;
@ -79,10 +85,10 @@ namespace PettingZoo.RabbitMQ
lock (connectionLock)
{
if (model == null)
if (connection == null)
return;
subscriber.Connected(model);
subscriber.Connected(connection.CreateModel());
}
StatusChanged -= ConnectSubscriber;
@ -97,12 +103,30 @@ namespace PettingZoo.RabbitMQ
public Task Publish(PublishMessageInfo messageInfo)
{
if (model == null)
IConnection? lockedConnection;
lock (connectionLock)
{
lockedConnection = connection;
}
if (lockedConnection == null)
throw new InvalidOperationException("Not connected");
model.BasicPublish(messageInfo.Exchange, messageInfo.RoutingKey, false,
RabbitMQClientPropertiesConverter.Convert(messageInfo.Properties, model.CreateBasicProperties()),
messageInfo.Body);
using (var model = lockedConnection.CreateModel())
{
try
{
model.BasicPublish(messageInfo.Exchange, messageInfo.RoutingKey, false,
RabbitMQClientPropertiesConverter.Convert(messageInfo.Properties,
model.CreateBasicProperties()),
messageInfo.Body);
}
finally
{
model.Close();
}
}
return Task.CompletedTask;
}
@ -119,22 +143,22 @@ namespace PettingZoo.RabbitMQ
Password = connectionParams.Password
};
var statusContext = $"{connectionParams.Host}:{connectionParams.Port}{connectionParams.VirtualHost}";
while (!cancellationToken.IsCancellationRequested)
{
DoStatusChanged(ConnectionStatus.Connecting, statusContext);
DoStatusChanged(ConnectionStatus.Connecting);
try
{
connection = factory.CreateConnection();
model = connection.CreateModel();
DoStatusChanged(ConnectionStatus.Connected, statusContext);
lock (connectionLock)
{
connection = factory.CreateConnection();
}
DoStatusChanged(ConnectionStatus.Connected);
break;
}
catch (Exception e)
{
DoStatusChanged(ConnectionStatus.Error, e.Message);
DoStatusChanged(ConnectionStatus.Error, e);
try
{
@ -148,9 +172,10 @@ namespace PettingZoo.RabbitMQ
}
private void DoStatusChanged(ConnectionStatus status, string? context = null)
private void DoStatusChanged(ConnectionStatus status, Exception? exception = null)
{
StatusChanged?.Invoke(this, new StatusChangedEventArgs(status, context));
Status = status;
StatusChanged?.Invoke(this, new StatusChangedEventArgs(ConnectionId, status, ConnectionParams, exception));
}
}
}

View File

@ -160,7 +160,7 @@ namespace PettingZoo.UI.Main {
}
/// <summary>
/// Looks up a localized string similar to Connected.
/// Looks up a localized string similar to Connected to {0}.
/// </summary>
public static string StatusConnected {
get {

View File

@ -151,7 +151,7 @@
<value>Importing messages...</value>
</data>
<data name="StatusConnected" xml:space="preserve">
<value>Connected</value>
<value>Connected to {0}</value>
</data>
<data name="StatusConnecting" xml:space="preserve">
<value>Connecting to {0}...</value>

View File

@ -43,7 +43,7 @@ namespace PettingZoo.UI.Main
private readonly IExportImportFormatProvider exportImportFormatProvider;
private SubscribeDialogParams? subscribeDialogParams;
private IConnection? connection;
private readonly DynamicConnection connection = new();
private string connectionStatus;
private ITab? activeTab;
private readonly Dictionary<ITab, Window> undockedTabs = new();
@ -141,15 +141,15 @@ namespace PettingZoo.UI.Main
closeTabCommand = new DelegateCommand(CloseTabExecute, HasActiveTabCanExecute);
undockTabCommand = new DelegateCommand(UndockTabExecute, HasActiveTabCanExecute);
importCommand = new DelegateCommand(ImportExecute);
connection.StatusChanged += ConnectionStatusChanged;
}
public async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
if (connection != null)
await connection.DisposeAsync();
await connection.DisposeAsync();
}
@ -159,13 +159,9 @@ namespace PettingZoo.UI.Main
if (connectionSettings == null)
return;
if (connection != null)
await connection.DisposeAsync();
connection = connectionFactory.CreateConnection(new ConnectionParams(
connection.SetConnection(connectionFactory.CreateConnection(new ConnectionParams(
connectionSettings.Host, connectionSettings.VirtualHost, connectionSettings.Port,
connectionSettings.Username, connectionSettings.Password));
connection.StatusChanged += ConnectionStatusChanged;
connectionSettings.Username, connectionSettings.Password)));
if (connectionSettings.Subscribe)
{
@ -173,40 +169,22 @@ namespace PettingZoo.UI.Main
tabFactory.CreateSubscriberTab(connection, subscriber);
}
connection.Connect();
ConnectionChanged();
}
private async void DisconnectExecute()
{
Tabs.Clear();
var capturedUndockedTabs = undockedTabs.ToList();
undockedTabs.Clear();
foreach (var undockedTab in capturedUndockedTabs)
undockedTab.Value.Close();
RaisePropertyChanged(nameof(NoTabsVisibility));
undockTabCommand.RaiseCanExecuteChanged();
if (connection != null)
{
await connection.DisposeAsync();
connection = null;
}
ConnectionStatus = GetConnectionStatus(null);
ConnectionStatusType = ConnectionStatusType.Error;
ConnectionChanged();
await connection.Disconnect();
}
private void SubscribeExecute()
{
if (connection == null)
if (connection.Status != Core.Connection.ConnectionStatus.Connected)
return;
var newParams = subscribeDialog.Show(subscribeDialogParams);
if (newParams == null)
return;
@ -220,16 +198,16 @@ namespace PettingZoo.UI.Main
private void PublishExecute()
{
if (connection == null)
if (connection.Status != Core.Connection.ConnectionStatus.Connected)
return;
tabFactory.CreatePublisherTab(connection);
}
private bool IsConnectedCanExecute()
{
return connection != null;
return connection.Status == Core.Connection.ConnectionStatus.Connected;
}
@ -419,6 +397,8 @@ namespace PettingZoo.UI.Main
Core.Connection.ConnectionStatus.Connecting => ConnectionStatusType.Connecting,
_ => ConnectionStatusType.Error
};
Application.Current.Dispatcher.BeginInvoke(ConnectionChanged);
}
@ -427,9 +407,9 @@ namespace PettingZoo.UI.Main
{
return args?.Status switch
{
Core.Connection.ConnectionStatus.Connecting => string.Format(MainWindowStrings.StatusConnecting, args.Context),
Core.Connection.ConnectionStatus.Connected => string.Format(MainWindowStrings.StatusConnected, args.Context),
Core.Connection.ConnectionStatus.Error => string.Format(MainWindowStrings.StatusError, args.Context),
Core.Connection.ConnectionStatus.Connecting => string.Format(MainWindowStrings.StatusConnecting, args.ConnectionParams),
Core.Connection.ConnectionStatus.Connected => string.Format(MainWindowStrings.StatusConnected, args.ConnectionParams),
Core.Connection.ConnectionStatus.Error => string.Format(MainWindowStrings.StatusError, args.Exception?.Message),
Core.Connection.ConnectionStatus.Disconnected => MainWindowStrings.StatusDisconnected,
_ => MainWindowStrings.StatusDisconnected
};

View File

@ -4,7 +4,7 @@ namespace PettingZoo.UI.Tab
{
public interface ITabFactory
{
void CreateSubscriberTab(IConnection? connection, ISubscriber subscriber);
void CreateSubscriberTab(IConnection connection, ISubscriber subscriber);
string CreateReplySubscriberTab(IConnection connection);
void CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null);
}

View File

@ -8,7 +8,7 @@
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:valueConverters="clr-namespace:PettingZoo.WPF.ValueConverters;assembly=PettingZoo.WPF"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignHeight="1200"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance res:DesignTimePublisherViewModel, IsDesignTimeCreatable=True}"
Background="White">
@ -34,6 +34,7 @@
<RowDefinition Height="16" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label" />
@ -81,6 +82,7 @@
<ContentControl Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 8 0 0" Content="{Binding MessageTypeControl}" />
<Button Grid.Row="11" Grid.Column="1" Command="{Binding PublishCommand}" Content="{x:Static res:PublisherViewStrings.CommandPublish}" HorizontalAlignment="Left" />
<TextBlock Grid.Row="12" Grid.Column="1" Text="{x:Static res:PublisherViewStrings.Published}" Visibility="{Binding PublishedVisibility}" />
</controls:GridLayout>
</ScrollViewer>

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using Accessibility;
using System.Windows.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using PettingZoo.Core.Connection;
@ -17,11 +17,12 @@ using PettingZoo.Core.Generator;
using PettingZoo.Core.Macros;
using PettingZoo.Core.Settings;
using PettingZoo.WPF.ViewModel;
using Application = System.Windows.Application;
using UserControl = System.Windows.Controls.UserControl;
namespace PettingZoo.UI.Tab.Publisher
{
public class PublisherViewModel : BaseViewModel, ITabToolbarCommands, ITabHostWindowNotify, IPublishDestination
public class PublisherViewModel : BaseViewModel, IDisposable, ITabToolbarCommands, ITabHostWindowNotify, IPublishDestination
{
private readonly IConnection connection;
private readonly IExampleGenerator exampleGenerator;
@ -197,6 +198,19 @@ namespace PettingZoo.UI.Tab.Publisher
public ICommand ImportCommand => importCommand;
private readonly DispatcherTimer publishedVisibilityTimer = new()
{
Interval = TimeSpan.FromSeconds(1)
};
private Visibility publishedVisibility = Visibility.Hidden;
public Visibility PublishedVisibility
{
get => publishedVisibility;
set => SetField(ref publishedVisibility, value);
}
public string Title => SendToQueue
? string.IsNullOrWhiteSpace(Queue) ? PublisherViewStrings.TabTitleEmpty :
string.Format(PublisherViewStrings.TabTitle, Queue)
@ -244,17 +258,51 @@ namespace PettingZoo.UI.Tab.Publisher
PropertyChanged += (_, _) => { saveCommand.RaiseCanExecuteChanged(); };
// ReSharper disable once ConditionIsAlwaysTrueOrFalse - null in design time
if (connection != null)
connection.StatusChanged += ConnectionStatusChanged;
publishedVisibilityTimer.Tick += (_, _) =>
{
PublishedVisibility = Visibility.Hidden;
publishedVisibilityTimer.Stop();
};
}
public void Dispose()
{
connection.StatusChanged -= ConnectionStatusChanged;
GC.SuppressFinalize(this);
}
private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(() =>
{
publishCommand.RaiseCanExecuteChanged();
});
}
private void PublishExecute()
{
messageTypePublishCommand?.Execute(null);
PublishedVisibility = Visibility.Visible;
publishedVisibilityTimer.Stop();
publishedVisibilityTimer.Start();
}
private bool PublishCanExecute()
{
if (connection.Status != ConnectionStatus.Connected)
return false;
if (SendToExchange)
{
if (string.IsNullOrWhiteSpace(Exchange) || string.IsNullOrWhiteSpace(RoutingKey))
@ -571,6 +619,8 @@ namespace PettingZoo.UI.Tab.Publisher
SelectedStoredMessage = StoredMessages[0];
ActiveStoredMessage = StoredMessages[1];
};
PublishedVisibility = Visibility.Visible;
}
public override Visibility ExchangeVisibility => Visibility.Visible;

View File

@ -177,6 +177,15 @@ namespace PettingZoo.UI.Tab.Publisher {
}
}
/// <summary>
/// Looks up a localized string similar to Message published.
/// </summary>
public static string Published {
get {
return ResourceManager.GetString("Published", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Re: .
/// </summary>

View File

@ -156,6 +156,9 @@
<data name="PanelTitleMessages" xml:space="preserve">
<value>Saved messages</value>
</data>
<data name="Published" xml:space="preserve">
<value>Message published</value>
</data>
<data name="ReplyToCorrelationIdPrefix" xml:space="preserve">
<value>Re: </value>
</data>

View File

@ -37,8 +37,8 @@
<ListBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource Messages}}" />
<ListBoxItem HorizontalContentAlignment="Stretch" IsEnabled="False" IsHitTestVisible="False">
<Grid Visibility="{Binding UnreadMessagesVisibility}">
<ListBoxItem HorizontalContentAlignment="Stretch" IsEnabled="False" IsHitTestVisible="False" Visibility="{Binding UnreadMessagesVisibility}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="DateTime" />
<ColumnDefinition Width="Auto" />
@ -51,6 +51,17 @@
</Grid>
</ListBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource UnreadMessages}}" />
<ListBoxItem HorizontalContentAlignment="Stretch" IsEnabled="False" IsHitTestVisible="False" Visibility="{Binding StatusVisibility}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="DateTime" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding StatusText}" HorizontalAlignment="Center" Background="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Foreground="{x:Static SystemColors.GrayTextBrush}" />
</Grid>
</ListBoxItem>
</CompositeCollection>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>

View File

@ -25,7 +25,7 @@ namespace PettingZoo.UI.Tab.Subscriber
{
private readonly ILogger logger;
private readonly ITabFactory tabFactory;
private readonly IConnection? connection;
private readonly IConnection connection;
private readonly ISubscriber subscriber;
private readonly IExportImportFormatProvider exportImportFormatProvider;
private ReceivedMessageInfo? selectedMessage;
@ -106,8 +106,14 @@ namespace PettingZoo.UI.Tab.Subscriber
public Visibility ReplyTabVisibility => IsReplyTab ? Visibility.Visible : Visibility.Collapsed;
// ReSharper restore UnusedMember.Global
private readonly Guid subscribeConnectionId;
private ConnectionStatus connectionStatus = ConnectionStatus.Connecting;
public SubscriberViewModel(ILogger logger, ITabFactory tabFactory, IConnection? connection, ISubscriber subscriber, IExportImportFormatProvider exportImportFormatProvider, bool isReplyTab)
public Visibility StatusVisibility => connectionStatus is ConnectionStatus.Connecting or ConnectionStatus.Disconnected or ConnectionStatus.Error ? Visibility.Visible : Visibility.Collapsed;
public string StatusText => connectionStatus == ConnectionStatus.Connecting ? SubscriberViewStrings.StatusConnecting : SubscriberViewStrings.StatusDisconnected;
public SubscriberViewModel(ILogger logger, ITabFactory tabFactory, IConnection connection, ISubscriber subscriber, IExportImportFormatProvider exportImportFormatProvider, bool isReplyTab)
{
IsReplyTab = isReplyTab;
@ -133,6 +139,8 @@ namespace PettingZoo.UI.Tab.Subscriber
createPublisherCommand = new DelegateCommand(CreatePublisherExecute, CreatePublisherCanExecute);
subscribeConnectionId = connection.ConnectionId;
connection.StatusChanged += ConnectionStatusChanged;
subscriber.MessageReceived += SubscriberMessageReceived;
subscriber.Start();
}
@ -141,11 +149,47 @@ namespace PettingZoo.UI.Tab.Subscriber
public void Dispose()
{
GC.SuppressFinalize(this);
connection.StatusChanged -= ConnectionStatusChanged;
newMessageTimer?.Dispose();
subscriber.Dispose();
}
private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs e)
{
// If another connection has been made, this does not concern us
if (e.ConnectionId != subscribeConnectionId)
return;
// The subscriber will not reconnect, so after the first disconnect it's over for us
if (connectionStatus is ConnectionStatus.Disconnected)
return;
switch (e.Status)
{
case ConnectionStatus.Connecting:
case ConnectionStatus.Error:
return;
case ConnectionStatus.Connected:
connectionStatus = ConnectionStatus.Connected;
break;
case ConnectionStatus.Disconnected:
default:
connectionStatus = ConnectionStatus.Disconnected;
break;
}
Application.Current.Dispatcher.BeginInvoke(() =>
{
RaisePropertyChanged(nameof(StatusVisibility));
RaisePropertyChanged(nameof(StatusText));
});
}
private void ClearExecute()
{
Messages.Clear();
@ -254,7 +298,7 @@ namespace PettingZoo.UI.Tab.Subscriber
private void CreatePublisherExecute()
{
if (connection == null)
if (connection.Status != ConnectionStatus.Connected)
return;
tabFactory.CreatePublisherTab(connection, SelectedMessage);
@ -263,7 +307,7 @@ namespace PettingZoo.UI.Tab.Subscriber
private bool CreatePublisherCanExecute()
{
return connection != null && SelectedMessage != null;
return SelectedMessage != null;
}

View File

@ -194,5 +194,23 @@ namespace PettingZoo.UI.Tab.Subscriber {
return ResourceManager.GetString("ReplyTabTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connecting....
/// </summary>
public static string StatusConnecting {
get {
return ResourceManager.GetString("StatusConnecting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Disconnected.
/// </summary>
public static string StatusDisconnected {
get {
return ResourceManager.GetString("StatusDisconnected", resourceCulture);
}
}
}
}

View File

@ -162,4 +162,10 @@
<data name="ReplyTabTitle" xml:space="preserve">
<value>Replies</value>
</data>
<data name="StatusConnecting" xml:space="preserve">
<value>Connecting...</value>
</data>
<data name="StatusDisconnected" xml:space="preserve">
<value>Disconnected</value>
</data>
</root>

View File

@ -36,7 +36,7 @@ namespace PettingZoo.UI.Tab
}
public void CreateSubscriberTab(IConnection? connection, ISubscriber subscriber)
public void CreateSubscriberTab(IConnection connection, ISubscriber subscriber)
{
InternalCreateSubscriberTab(connection, subscriber, false);
}
@ -75,7 +75,7 @@ namespace PettingZoo.UI.Tab
}
private ITab InternalCreateSubscriberTab(IConnection? connection, ISubscriber subscriber, bool isReplyTab)
private ITab InternalCreateSubscriberTab(IConnection connection, ISubscriber subscriber, bool isReplyTab)
{
var viewModel = new SubscriberViewModel(logger, this, connection, subscriber, exportImportFormatProvider, isReplyTab);
var tab = new ViewTab<SubscriberView, SubscriberViewModel>(