Implemented indicator for new messages

This commit is contained in:
Mark van Renswoude 2022-01-03 17:20:30 +01:00
parent c75ea0cc62
commit 79776fd813
8 changed files with 154 additions and 20 deletions

View File

@ -34,7 +34,7 @@ namespace PettingZoo.Test.Tapeti
objectValue.Should().HaveElement("RecursiveValue").Which.Type.Should().Be(JTokenType.Null); objectValue.Should().HaveElement("RecursiveValue").Which.Type.Should().Be(JTokenType.Null);
// Via type mapping // Via type mapping
// TODO // TODO test type mappings
} }
} }

View File

@ -0,0 +1,23 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace PettingZoo.UI.Tab.Subscriber
{
public class SameMessageVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return ReferenceEquals(values[0], values[1])
? Visibility.Visible
: Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@ -11,6 +11,9 @@
d:DesignWidth="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance res:DesignTimeSubscriberViewModel, IsDesignTimeCreatable=True}" d:DataContext="{d:DesignInstance res:DesignTimeSubscriberViewModel, IsDesignTimeCreatable=True}"
Background="White"> Background="White">
<UserControl.Resources>
<res:SameMessageVisibilityConverter x:Key="SameMessageVisibilityConverter" />
</UserControl.Resources>
<Grid Margin="4"> <Grid Margin="4">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@ -30,11 +33,37 @@
<DataTemplate> <DataTemplate>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="150"/> <ColumnDefinition Width="Auto" MinWidth="150" SharedSizeGroup="DateTime"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </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.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>
<ColumnDefinition SharedSizeGroup="DateTime" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</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" />
<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" />
</Grid>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding ReceivedTimestamp, StringFormat=g}" Style="{StaticResource Timestamp}" />
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding RoutingKey}" Style="{StaticResource RoutingKey}" />
<Grid.ContextMenu> <Grid.ContextMenu>
<ContextMenu> <ContextMenu>
@ -66,6 +95,8 @@
<Border Grid.Column="0" Grid.Row="1" Style="{StaticResource SidePanel}"> <Border Grid.Column="0" Grid.Row="1" Style="{StaticResource SidePanel}">
<DockPanel> <DockPanel>
<Label DockPanel.Dock="Top" Style="{StaticResource HeaderLabel}" Content="{x:Static res:SubscriberViewStrings.PanelTitleBody}"/> <Label DockPanel.Dock="Top" Style="{StaticResource HeaderLabel}" Content="{x:Static res:SubscriberViewStrings.PanelTitleBody}"/>
<!-- TODO use AvalonEdit with syntax highlighting depending on content type -->
<TextBox <TextBox
Text="{Binding SelectedMessageBody, Mode=OneWay}" Text="{Binding SelectedMessageBody, Mode=OneWay}"
BorderThickness="0" BorderThickness="0"

View File

@ -1,14 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
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 visual hint of where the last read message was when activating the tab again // 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
{ {
@ -18,7 +20,7 @@ namespace PettingZoo.UI.Tab.Subscriber
private readonly ITabFactory tabFactory; private readonly ITabFactory tabFactory;
private readonly IConnection connection; private readonly IConnection connection;
private readonly ISubscriber subscriber; private readonly ISubscriber subscriber;
private readonly TaskScheduler uiScheduler; private readonly Dispatcher dispatcher;
private ReceivedMessageInfo? selectedMessage; private ReceivedMessageInfo? selectedMessage;
private readonly DelegateCommand clearCommand; private readonly DelegateCommand clearCommand;
private readonly TabToolbarCommand[] toolbarCommands; private readonly TabToolbarCommand[] toolbarCommands;
@ -27,6 +29,8 @@ 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 int unreadCount; private int unreadCount;
@ -47,6 +51,14 @@ namespace PettingZoo.UI.Tab.Subscriber
} }
} }
public ReceivedMessageInfo? NewMessage
{
get => newMessage;
set => SetField(ref newMessage, value);
}
public string SelectedMessageBody => public string SelectedMessageBody =>
SelectedMessage != null SelectedMessage != null
? MessageBodyRenderer.Render(SelectedMessage.Body, SelectedMessage.Properties.ContentType) ? MessageBodyRenderer.Render(SelectedMessage.Body, SelectedMessage.Properties.ContentType)
@ -70,8 +82,8 @@ namespace PettingZoo.UI.Tab.Subscriber
this.tabFactory = tabFactory; this.tabFactory = tabFactory;
this.connection = connection; this.connection = connection;
this.subscriber = subscriber; this.subscriber = subscriber;
uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); dispatcher = Dispatcher.CurrentDispatcher;
Messages = new ObservableCollection<ReceivedMessageInfo>(); Messages = new ObservableCollection<ReceivedMessageInfo>();
clearCommand = new DelegateCommand(ClearExecute, ClearCanExecute); clearCommand = new DelegateCommand(ClearExecute, ClearCanExecute);
@ -116,12 +128,14 @@ namespace PettingZoo.UI.Tab.Subscriber
private void SubscriberMessageReceived(object? sender, MessageReceivedEventArgs args) private void SubscriberMessageReceived(object? sender, MessageReceivedEventArgs args)
{ {
RunFromUiScheduler(() => dispatcher.BeginInvoke(() =>
{ {
if (!tabActive) if (!tabActive)
{ {
unreadCount++; unreadCount++;
RaisePropertyChanged(nameof(Title)); RaisePropertyChanged(nameof(Title));
NewMessage ??= args.MessageInfo;
} }
Messages.Add(args.MessageInfo); Messages.Add(args.MessageInfo);
@ -140,22 +154,39 @@ namespace PettingZoo.UI.Tab.Subscriber
} }
private void RunFromUiScheduler(Action action)
{
_ = Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
public void Activate() public void Activate()
{ {
tabActive = true; tabActive = true;
unreadCount = 0; unreadCount = 0;
RaisePropertyChanged(nameof(Title)); RaisePropertyChanged(nameof(Title));
if (NewMessage == null)
return;
newMessageTimer?.Dispose();
newMessageTimer = new Timer(
_ =>
{
dispatcher.BeginInvoke(() =>
{
NewMessage = null;
});
},
null,
TimeSpan.FromSeconds(5),
Timeout.InfiniteTimeSpan);
} }
public void Deactivate() public void Deactivate()
{ {
if (newMessageTimer != null)
{
newMessageTimer.Dispose();
newMessageTimer = null;
}
NewMessage = null;
tabActive = false; tabActive = false;
} }
} }
@ -165,6 +196,20 @@ 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++)
Messages.Add(new ReceivedMessageInfo(
"designtime",
$"designtime.message.{i}",
Encoding.UTF8.GetBytes(@"Design-time message"),
new MessageProperties(null)
{
ContentType = "text/fake",
ReplyTo = "/dev/null"
},
DateTime.Now));
SelectedMessage = Messages[2];
NewMessage = Messages[2];
} }

View File

@ -96,6 +96,15 @@ namespace PettingZoo.UI.Tab.Subscriber {
} }
} }
/// <summary>
/// Looks up a localized string similar to New messages.
/// </summary>
public static string LabelNewMessages {
get {
return ResourceManager.GetString("LabelNewMessages", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Body. /// Looks up a localized string similar to Body.
/// </summary> /// </summary>

View File

@ -112,10 +112,10 @@
<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="CommandClear" xml:space="preserve"> <data name="CommandClear" xml:space="preserve">
<value>Clear</value> <value>Clear</value>
@ -129,6 +129,9 @@
<data name="DeliveryModePersistent" xml:space="preserve"> <data name="DeliveryModePersistent" xml:space="preserve">
<value>Persistent</value> <value>Persistent</value>
</data> </data>
<data name="LabelNewMessages" xml:space="preserve">
<value>New messages</value>
</data>
<data name="PanelTitleBody" xml:space="preserve"> <data name="PanelTitleBody" xml:space="preserve">
<value>Body</value> <value>Body</value>
</data> </data>

View File

@ -8,7 +8,7 @@ using PettingZoo.WPF.ViewModel;
namespace PettingZoo.UI.Tab.Undocked namespace PettingZoo.UI.Tab.Undocked
{ {
public class UndockedTabHostViewModel : BaseViewModel public class UndockedTabHostViewModel : BaseViewModel, ITabActivate
{ {
private readonly ITabHost tabHost; private readonly ITabHost tabHost;
private readonly ITab tab; private readonly ITab tab;
@ -51,6 +51,17 @@ namespace PettingZoo.UI.Tab.Undocked
{ {
tabHost.UndockedTabClosed(tab); tabHost.UndockedTabClosed(tab);
} }
public void Activate()
{
(tab as ITabActivate)?.Activate();
}
public void Deactivate()
{
(tab as ITabActivate)?.Deactivate();
}
} }

View File

@ -1,5 +1,7 @@
using System.Windows; using System;
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Threading;
namespace PettingZoo.UI.Tab.Undocked namespace PettingZoo.UI.Tab.Undocked
{ {
@ -30,6 +32,16 @@ namespace PettingZoo.UI.Tab.Undocked
{ {
viewModel.WindowClosed(); viewModel.WindowClosed();
}; };
Activated += (_, _) =>
{
viewModel.Activate();
};
Deactivated += (_, _) =>
{
viewModel.Deactivate();
};
} }
private void Toolbar_Loaded(object sender, RoutedEventArgs e) private void Toolbar_Loaded(object sender, RoutedEventArgs e)