1
0
mirror of synced 2024-11-14 17:33:49 +00:00

Implemented a few ToDo's

- Check required fields before publishing
- Allow cancelling of package downloading
- Check for JsonConverter attribute
- Improved read/unread message separator
This commit is contained in:
Mark van Renswoude 2022-01-10 11:52:07 +01:00
parent 5bc2096a24
commit 785ddbd5b2
13 changed files with 172 additions and 79 deletions

View File

@ -54,12 +54,13 @@ namespace PettingZoo.Tapeti
progressWindow.Top = windowBounds.Top + (windowBounds.Height - progressWindow.Height) / 2; progressWindow.Top = windowBounds.Top + (windowBounds.Height - progressWindow.Height) / 2;
progressWindow.Show(); progressWindow.Show();
var cancellationToken = progressWindow.CancellationToken;
Task.Run(async () => Task.Run(async () =>
{ {
try try
{ {
// TODO allow cancelling (by closing the progress window and optionally a Cancel button) var assemblies = await args.Assemblies.GetAssemblies(progressWindow, cancellationToken);
var assemblies = await args.Assemblies.GetAssemblies(progressWindow, CancellationToken.None);
// var classes = // var classes =
var examples = LoadExamples(assemblies); var examples = LoadExamples(assemblies);
@ -94,10 +95,11 @@ namespace PettingZoo.Tapeti
// ReSharper disable once ConstantConditionalAccessQualifier - if I remove it, there's a "Dereference of a possibly null reference" warning instead // ReSharper disable once ConstantConditionalAccessQualifier - if I remove it, there's a "Dereference of a possibly null reference" warning instead
progressWindow?.Close(); progressWindow?.Close();
MessageBox.Show($"Error while loading assembly: {e.Message}", "Petting Zoo - Exception", MessageBoxButton.OK, MessageBoxImage.Error); if (e is not OperationCanceledException)
MessageBox.Show($"Error while loading assembly: {e.Message}", "Petting Zoo - Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}); });
} }
}); }, CancellationToken.None);
}); });
}; };

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace PettingZoo.Tapeti namespace PettingZoo.Tapeti
@ -59,7 +60,20 @@ namespace PettingZoo.Tapeti
actualType = equivalentType; actualType = equivalentType;
// TODO check for JsonConverter attribute? doubt we'll be able to generate a nice value for it, but at least we can provide a placeholder try
{
if (type.GetCustomAttribute<JsonConverterAttribute>() != null)
{
// This type uses custom Json conversion so there's no way to know how to provide an example.
// We could try to create an instance of the type and pass it through the converter, but for now we'll
// just output a placeholder.
return "<custom JsonConverter - manual input required>";
}
}
catch
{
// Move along
}
// String is also a class // String is also a class
if (actualType == typeof(string)) if (actualType == typeof(string))

View File

@ -60,6 +60,15 @@ namespace PettingZoo.Tapeti.UI.PackageProgress {
} }
} }
/// <summary>
/// Looks up a localized string similar to Cancel.
/// </summary>
public static string ButtonCancel {
get {
return ResourceManager.GetString("ButtonCancel", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Reading message classes.... /// Looks up a localized string similar to Reading message classes....
/// </summary> /// </summary>

View File

@ -112,11 +112,14 @@
<value>2.0</value> <value>2.0</value>
</resheader> </resheader>
<resheader name="reader"> <resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="ButtonCancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="WindowTitle" xml:space="preserve"> <data name="WindowTitle" xml:space="preserve">
<value>Reading message classes...</value> <value>Reading message classes...</value>
</data> </data>

View File

@ -5,10 +5,13 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:packageProgress="clr-namespace:PettingZoo.Tapeti.UI.PackageProgress" xmlns:packageProgress="clr-namespace:PettingZoo.Tapeti.UI.PackageProgress"
mc:Ignorable="d" mc:Ignorable="d"
Height="80"
Width="400" Width="400"
Title="{x:Static packageProgress:PackageProgressStrings.WindowTitle}" Title="{x:Static packageProgress:PackageProgressStrings.WindowTitle}"
ResizeMode="NoResize" ResizeMode="NoResize"
WindowStyle="ToolWindow"> WindowStyle="ToolWindow"
<ProgressBar Height="25" Margin="16" VerticalAlignment="Center" Name="Progress" Maximum="100" /> SizeToContent="Height">
<StackPanel Orientation="Vertical">
<ProgressBar Height="25" Margin="16" VerticalAlignment="Center" Name="Progress" Maximum="100" />
<Button HorizontalAlignment="Center" Margin="0,0,0,16" Content="{x:Static packageProgress:PackageProgressStrings.ButtonCancel}" Click="CancelButton_OnClick" />
</StackPanel>
</Window> </Window>

View File

@ -1,4 +1,7 @@
using System; using System;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
namespace PettingZoo.Tapeti.UI.PackageProgress namespace PettingZoo.Tapeti.UI.PackageProgress
{ {
@ -7,9 +10,19 @@ namespace PettingZoo.Tapeti.UI.PackageProgress
/// </summary> /// </summary>
public partial class PackageProgressWindow : IProgress<int> public partial class PackageProgressWindow : IProgress<int>
{ {
private readonly CancellationTokenSource cancellationTokenSource = new();
public CancellationToken CancellationToken => cancellationTokenSource.Token;
public PackageProgressWindow() public PackageProgressWindow()
{ {
InitializeComponent(); InitializeComponent();
Closed += (_, _) =>
{
cancellationTokenSource.Cancel();
};
} }
@ -20,5 +33,11 @@ namespace PettingZoo.Tapeti.UI.PackageProgress
Progress.Value = value; Progress.Value = value;
}); });
} }
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
{
cancellationTokenSource.Cancel();
((Button)sender).IsEnabled = false;
}
} }
} }

View File

@ -88,7 +88,6 @@ namespace PettingZoo.Tapeti.UI.PackageSelection
public ICommand AssemblyBrowse => assemblyBrowse; public ICommand AssemblyBrowse => assemblyBrowse;
// TODO hint for extra assemblies path
public static string HintNuGetSources => string.Format(PackageSelectionStrings.HintNuGetSources, PettingZooPaths.InstallationRoot, PettingZooPaths.AppDataRoot); public static string HintNuGetSources => string.Format(PackageSelectionStrings.HintNuGetSources, PettingZooPaths.InstallationRoot, PettingZooPaths.AppDataRoot);
public string NuGetSearchTerm public string NuGetSearchTerm

View File

@ -1,12 +1,12 @@
Must-have Must-have
--------- ---------
- Check required fields before enabling Publish button
Should-have Should-have
----------- -----------
- Save / load publisher messages (either as templates or to disk) - Save / load publisher messages (either as templates or to disk)
- Tapeti: export received messages to Tapeti.Cmd JSON file / Tapeti.Cmd command-line - Tapeti: export received messages to Tapeti.Cmd JSON file / Tapeti.Cmd command-line
- Tapeti: import Tapeti.Cmd JSON file into Subscriber-esque tab for easier browsing
- Tapeti: fetch NuGet dependencies to improve the chances of succesfully loading the assembly, instead of the current "extraAssembliesPaths" workaround - Tapeti: fetch NuGet dependencies to improve the chances of succesfully loading the assembly, instead of the current "extraAssembliesPaths" workaround

View File

@ -45,7 +45,9 @@ namespace PettingZoo.UI.Tab.Publisher
public bool SendToExchange public bool SendToExchange
{ {
get => sendToExchange; get => sendToExchange;
set => SetField(ref sendToExchange, value, otherPropertiesChanged: new[] { nameof(SendToQueue), nameof(ExchangeVisibility), nameof(QueueVisibility), nameof(Title) }); set => SetField(ref sendToExchange, value,
delegateCommandsChanged: new [] { publishCommand },
otherPropertiesChanged: new[] { nameof(SendToQueue), nameof(ExchangeVisibility), nameof(QueueVisibility), nameof(Title) });
} }
@ -59,21 +61,21 @@ namespace PettingZoo.UI.Tab.Publisher
public string Exchange public string Exchange
{ {
get => exchange; get => exchange;
set => SetField(ref exchange, value); set => SetField(ref exchange, value, delegateCommandsChanged: new[] { publishCommand });
} }
public string RoutingKey public string RoutingKey
{ {
get => routingKey; get => routingKey;
set => SetField(ref routingKey, value, otherPropertiesChanged: new[] { nameof(Title) }); set => SetField(ref routingKey, value, delegateCommandsChanged: new[] { publishCommand }, otherPropertiesChanged: new[] { nameof(Title) });
} }
public string Queue public string Queue
{ {
get => queue; get => queue;
set => SetField(ref queue, value, otherPropertiesChanged: new[] { nameof(Title) }); set => SetField(ref queue, value, delegateCommandsChanged: new[] { publishCommand }, otherPropertiesChanged: new[] { nameof(Title) });
} }
@ -183,6 +185,17 @@ namespace PettingZoo.UI.Tab.Publisher
private bool PublishCanExecute() private bool PublishCanExecute()
{ {
if (SendToExchange)
{
if (string.IsNullOrWhiteSpace(Exchange) || string.IsNullOrWhiteSpace(RoutingKey))
return false;
}
else
{
if (string.IsNullOrWhiteSpace(Queue))
return false;
}
return messageTypePublishCommand?.CanExecute(null) ?? false; return messageTypePublishCommand?.CanExecute(null) ?? false;
} }
@ -197,6 +210,11 @@ namespace PettingZoo.UI.Tab.Publisher
if (rawPublisherView == null) if (rawPublisherView == null)
{ {
rawPublisherViewModel = new RawPublisherViewModel(connection, this); rawPublisherViewModel = new RawPublisherViewModel(connection, this);
rawPublisherViewModel.PublishCommand.CanExecuteChanged += (_, _) =>
{
publishCommand.RaiseCanExecuteChanged();
};
rawPublisherView ??= new RawPublisherView(rawPublisherViewModel); rawPublisherView ??= new RawPublisherView(rawPublisherViewModel);
} }
else else
@ -213,6 +231,11 @@ namespace PettingZoo.UI.Tab.Publisher
if (tapetiPublisherView == null) if (tapetiPublisherView == null)
{ {
tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator); tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator);
tapetiPublisherViewModel.PublishCommand.CanExecuteChanged += (_, _) =>
{
publishCommand.RaiseCanExecuteChanged();
};
tapetiPublisherView ??= new TapetiPublisherView(tapetiPublisherViewModel); tapetiPublisherView ??= new TapetiPublisherView(tapetiPublisherViewModel);
if (tabHostWindow != null) if (tabHostWindow != null)

View File

@ -115,7 +115,7 @@ namespace PettingZoo.UI.Tab.Publisher
public string Payload public string Payload
{ {
get => payload; get => payload;
set => SetField(ref payload, value); set => SetField(ref payload, value, delegateCommandsChanged: new [] { publishCommand });
} }
@ -267,10 +267,9 @@ namespace PettingZoo.UI.Tab.Publisher
} }
private static bool PublishCanExecute() private bool PublishCanExecute()
{ {
// TODO validate input return !string.IsNullOrWhiteSpace(Payload);
return true;
} }

View File

@ -36,10 +36,15 @@ namespace PettingZoo.UI.Tab.Publisher
public string ClassName public string ClassName
{ {
get => string.IsNullOrEmpty(className) ? AssemblyName + "." : className; get => string.IsNullOrWhiteSpace(className)
? string.IsNullOrWhiteSpace(AssemblyName)
? ""
: AssemblyName + "."
: className;
set set
{ {
if (SetField(ref className, value)) if (SetField(ref className, value, delegateCommandsChanged: new[] { publishCommand }))
validatingExample = null; validatingExample = null;
} }
} }
@ -48,7 +53,7 @@ namespace PettingZoo.UI.Tab.Publisher
public string AssemblyName public string AssemblyName
{ {
get => assemblyName; get => assemblyName;
set => SetField(ref assemblyName, value, otherPropertiesChanged: set => SetField(ref assemblyName, value, delegateCommandsChanged: new[] { publishCommand }, otherPropertiesChanged:
string.IsNullOrEmpty(value) || string.IsNullOrEmpty(className) string.IsNullOrEmpty(value) || string.IsNullOrEmpty(className)
? new [] { nameof(ClassName) } ? new [] { nameof(ClassName) }
: null : null
@ -59,7 +64,7 @@ namespace PettingZoo.UI.Tab.Publisher
public string Payload public string Payload
{ {
get => payload; get => payload;
set => SetField(ref payload, value); set => SetField(ref payload, value, delegateCommandsChanged: new[] { publishCommand });
} }
@ -164,12 +169,15 @@ namespace PettingZoo.UI.Tab.Publisher
} }
private static bool PublishCanExecute() private bool PublishCanExecute()
{ {
// TODO validate input return
return true; !string.IsNullOrWhiteSpace(assemblyName) &&
!string.IsNullOrWhiteSpace(ClassName) &&
!string.IsNullOrWhiteSpace(Payload);
} }
public void HostWindowChanged(Window? hostWindow) public void HostWindowChanged(Window? hostWindow)
{ {
tabHostWindow = hostWindow; tabHostWindow = hostWindow;

View File

@ -7,6 +7,7 @@
xmlns:res="clr-namespace:PettingZoo.UI.Tab.Subscriber" xmlns:res="clr-namespace:PettingZoo.UI.Tab.Subscriber"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:connection="clr-namespace:PettingZoo.Core.Connection;assembly=PettingZoo.Core"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
@ -24,48 +25,46 @@
<ListBox Grid.Column="0" Grid.Row="0" <ListBox Grid.Column="0" Grid.Row="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Messages}"
SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay}" SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay}"
ui:ListBox.AutoScroll="True" ui:ListBox.AutoScroll="True"
x:Name="ReferenceControlForBorder"> x:Name="ReferenceControlForBorder"
Grid.IsSharedSizeScope="True">
<ListBox.Resources> <ListBox.Resources>
<ui:BindingProxy x:Key="ContextMenuProxy" Data="{Binding}" /> <ui:BindingProxy x:Key="ContextMenuProxy" Data="{Binding}" />
<CollectionViewSource x:Key="Messages"
Source="{Binding Messages}" />
<CollectionViewSource x:Key="UnreadMessages"
Source="{Binding UnreadMessages}" />
</ListBox.Resources> </ListBox.Resources>
<ListBox.ItemTemplate> <ListBox.ItemsSource>
<DataTemplate> <CompositeCollection>
<Grid> <CollectionContainer Collection="{Binding Source={StaticResource Messages}}" />
<Grid.ColumnDefinitions> <ListBoxItem HorizontalContentAlignment="Stretch" IsEnabled="False" IsHitTestVisible="False">
<ColumnDefinition Width="Auto" MinWidth="150" SharedSizeGroup="DateTime"/> <Grid Visibility="{Binding UnreadMessagesVisibility}">
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- TODO insert as non-focusable item instead, so it's not part of the selection (and perhaps also fixes the bug mentioned in SubscriberViewModel) -->
<Grid Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="DateTime" /> <ColumnDefinition SharedSizeGroup="DateTime" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.Visibility>
<MultiBinding Converter="{StaticResource SameMessageVisibilityConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="DataContext" />
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ListBox}}" Path="DataContext.NewMessage" />
</MultiBinding>
</Grid.Visibility>
<Separator Grid.Column="0" Margin="0,0,8,0" /> <Separator Grid.Column="0" Margin="0,0,8,0" />
<TextBlock Grid.Column="1" Text="{x:Static res:SubscriberViewStrings.LabelNewMessages}" HorizontalAlignment="Center" Background="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Foreground="{x:Static SystemColors.GrayTextBrush}" /> <TextBlock Grid.Column="1" Text="{x:Static res:SubscriberViewStrings.LabelNewMessages}" HorizontalAlignment="Center" Background="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Foreground="{x:Static SystemColors.GrayTextBrush}" />
<Separator Grid.Column="2" Margin="8,0,0,0" /> <Separator Grid.Column="2" Margin="8,0,0,0" />
</Grid> </Grid>
</ListBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource UnreadMessages}}" />
</CompositeCollection>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type connection:ReceivedMessageInfo}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="150" SharedSizeGroup="DateTime"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding ReceivedTimestamp, StringFormat=g}" Style="{StaticResource Timestamp}" /> <TextBlock Grid.Column="0" Text="{Binding ReceivedTimestamp, StringFormat=g}" Style="{StaticResource Timestamp}" />
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding RoutingKey}" Style="{StaticResource RoutingKey}" /> <TextBlock Grid.Column="1" Text="{Binding RoutingKey}" Style="{StaticResource RoutingKey}" />
<Grid.ContextMenu> <Grid.ContextMenu>
<ContextMenu> <ContextMenu>

View File

@ -1,17 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading; using System.Windows.Threading;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using PettingZoo.Core.Rendering; using PettingZoo.Core.Rendering;
using PettingZoo.WPF.ViewModel; using PettingZoo.WPF.ViewModel;
// TODO if the "New message" line is visible when this tab is undocked, the line in the ListBox does not shrink. Haven't been able to figure out yet how to solve it
namespace PettingZoo.UI.Tab.Subscriber namespace PettingZoo.UI.Tab.Subscriber
{ {
public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands, ITabActivate public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands, ITabActivate
@ -29,7 +27,6 @@ namespace PettingZoo.UI.Tab.Subscriber
private readonly DelegateCommand createPublisherCommand; private readonly DelegateCommand createPublisherCommand;
private bool tabActive; private bool tabActive;
private ReceivedMessageInfo? newMessage;
private Timer? newMessageTimer; private Timer? newMessageTimer;
private int unreadCount; private int unreadCount;
@ -39,7 +36,8 @@ namespace PettingZoo.UI.Tab.Subscriber
// ReSharper disable once UnusedMember.Global - it is, but via a proxy // ReSharper disable once UnusedMember.Global - it is, but via a proxy
public ICommand CreatePublisherCommand => createPublisherCommand; public ICommand CreatePublisherCommand => createPublisherCommand;
public ObservableCollection<ReceivedMessageInfo> Messages { get; } public ObservableCollectionEx<ReceivedMessageInfo> Messages { get; }
public ObservableCollectionEx<ReceivedMessageInfo> UnreadMessages { get; }
public ReceivedMessageInfo? SelectedMessage public ReceivedMessageInfo? SelectedMessage
{ {
@ -52,11 +50,7 @@ namespace PettingZoo.UI.Tab.Subscriber
} }
public ReceivedMessageInfo? NewMessage public Visibility UnreadMessagesVisibility => UnreadMessages.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
{
get => newMessage;
set => SetField(ref newMessage, value);
}
public string SelectedMessageBody => public string SelectedMessageBody =>
@ -85,7 +79,8 @@ namespace PettingZoo.UI.Tab.Subscriber
dispatcher = Dispatcher.CurrentDispatcher; dispatcher = Dispatcher.CurrentDispatcher;
Messages = new ObservableCollection<ReceivedMessageInfo>(); Messages = new ObservableCollectionEx<ReceivedMessageInfo>();
UnreadMessages = new ObservableCollectionEx<ReceivedMessageInfo>();
clearCommand = new DelegateCommand(ClearExecute, ClearCanExecute); clearCommand = new DelegateCommand(ClearExecute, ClearCanExecute);
toolbarCommands = new[] toolbarCommands = new[]
@ -103,6 +98,8 @@ namespace PettingZoo.UI.Tab.Subscriber
private void ClearExecute() private void ClearExecute()
{ {
Messages.Clear(); Messages.Clear();
UnreadMessages.Clear();
RaisePropertyChanged(nameof(UnreadMessagesVisibility));
clearCommand.RaiseCanExecuteChanged(); clearCommand.RaiseCanExecuteChanged();
} }
@ -135,10 +132,13 @@ namespace PettingZoo.UI.Tab.Subscriber
unreadCount++; unreadCount++;
RaisePropertyChanged(nameof(Title)); RaisePropertyChanged(nameof(Title));
NewMessage ??= args.MessageInfo; UnreadMessages.Add(args.MessageInfo);
if (UnreadMessages.Count == 1)
RaisePropertyChanged(nameof(UnreadMessagesVisibility));
} }
else
Messages.Add(args.MessageInfo);
Messages.Add(args.MessageInfo);
clearCommand.RaiseCanExecuteChanged(); clearCommand.RaiseCanExecuteChanged();
}); });
} }
@ -161,7 +161,7 @@ namespace PettingZoo.UI.Tab.Subscriber
RaisePropertyChanged(nameof(Title)); RaisePropertyChanged(nameof(Title));
if (NewMessage == null) if (UnreadMessages.Count == 0)
return; return;
newMessageTimer?.Dispose(); newMessageTimer?.Dispose();
@ -170,7 +170,23 @@ namespace PettingZoo.UI.Tab.Subscriber
{ {
dispatcher.BeginInvoke(() => dispatcher.BeginInvoke(() =>
{ {
NewMessage = null; if (UnreadMessages.Count == 0)
return;
Messages.BeginUpdate();
UnreadMessages.BeginUpdate();
try
{
Messages.AddRange(UnreadMessages);
UnreadMessages.Clear();
}
finally
{
UnreadMessages.EndUpdate();
Messages.EndUpdate();
}
RaisePropertyChanged(nameof(UnreadMessagesVisibility));
}); });
}, },
null, null,
@ -178,6 +194,7 @@ namespace PettingZoo.UI.Tab.Subscriber
Timeout.InfiniteTimeSpan); Timeout.InfiniteTimeSpan);
} }
public void Deactivate() public void Deactivate()
{ {
if (newMessageTimer != null) if (newMessageTimer != null)
@ -186,7 +203,6 @@ namespace PettingZoo.UI.Tab.Subscriber
newMessageTimer = null; newMessageTimer = null;
} }
NewMessage = null;
tabActive = false; tabActive = false;
} }
} }
@ -197,22 +213,21 @@ namespace PettingZoo.UI.Tab.Subscriber
public DesignTimeSubscriberViewModel() : base(null!, null!, null!, new DesignTimeSubscriber()) public DesignTimeSubscriberViewModel() : base(null!, null!, null!, new DesignTimeSubscriber())
{ {
for (var i = 1; i <= 5; i++) for (var i = 1; i <= 5; i++)
Messages.Add(new ReceivedMessageInfo( (i > 2 ? UnreadMessages : Messages).Add(new ReceivedMessageInfo(
"designtime", "designtime",
$"designtime.message.{i}", $"designtime.message.{i}",
Encoding.UTF8.GetBytes(@"Design-time message"), Encoding.UTF8.GetBytes(@"Design-time message"),
new MessageProperties(null) new MessageProperties(null)
{ {
ContentType = "text/fake", ContentType = "text/fake",
ReplyTo = "/dev/null" ReplyTo = "/dev/null"
}, },
DateTime.Now)); DateTime.Now));
SelectedMessage = Messages[2]; SelectedMessage = UnreadMessages[0];
NewMessage = Messages[2];
} }
private class DesignTimeSubscriber : ISubscriber private class DesignTimeSubscriber : ISubscriber
{ {
public ValueTask DisposeAsync() public ValueTask DisposeAsync()