diff --git a/Source/Assets/VectorIcons.axaml b/Source/Assets/VectorIcons.axaml
index f0f5adf..89dae29 100644
--- a/Source/Assets/VectorIcons.axaml
+++ b/Source/Assets/VectorIcons.axaml
@@ -41,6 +41,7 @@
M14,2 C20.6274,2 26,7.37258 26,14 C26,20.6274 20.6274,26 14,26 C7.37258,26 2,20.6274 2,14 C2,7.37258 7.37258,2 14,2 Z M14,3.5 C8.20101,3.5 3.5,8.20101 3.5,14 C3.5,19.799 8.20101,24.5 14,24.5 C19.799,24.5 24.5,19.799 24.5,14 C24.5,8.20101 19.799,3.5 14,3.5 Z M14,11 C14.3796833,11 14.6934889,11.2821653 14.7431531,11.6482323 L14.75,11.75 L14.75,19.25 C14.75,19.6642 14.4142,20 14,20 C13.6203167,20 13.3065111,19.7178347 13.2568469,19.3517677 L13.25,19.25 L13.25,11.75 C13.25,11.3358 13.5858,11 14,11 Z M14,7 C14.5523,7 15,7.44772 15,8 C15,8.55228 14.5523,9 14,9 C13.4477,9 13,8.55228 13,8 C13,7.44772 13.4477,7 14,7 Z
+ M37.75,9 C40.6494949,9 43,11.3505051 43,14.25 L43,33.75 C43,36.6494949 40.6494949,39 37.75,39 L10.25,39 C7.35050506,39 5,36.6494949 5,33.75 L5,14.25 C5,11.3505051 7.35050506,9 10.25,9 L37.75,9 Z M40.5,18.351 L24.6023984,27.0952699 C24.2689733,27.2786537 23.8727436,27.2990297 23.5253619,27.1563978 L23.3976016,27.0952699 L7.5,18.351 L7.5,33.75 C7.5,35.2687831 8.73121694,36.5 10.25,36.5 L37.75,36.5 C39.2687831,36.5 40.5,35.2687831 40.5,33.75 L40.5,18.351 Z M37.75,11.5 L10.25,11.5 C8.73121694,11.5 7.5,12.7312169 7.5,14.25 L7.5,15.499 L24,24.573411 L40.5,15.498 L40.5,14.25 C40.5,12.7312169 39.2687831,11.5 37.75,11.5 Z
M2.99946 7.51126C2.09768 8.08885 1.5 9.09963 1.5 10.25V16.25C1.5 19.1495 3.85051 21.5 6.75 21.5H15.75C16.9004 21.5 17.9112 20.9023 18.4887 20.0005L6.75 20C4.67893 20 3 18.3211 3 16.25L2.99946 7.51126ZM18.75 4H7.25C5.51697 4 4.10075 5.35645 4.00514 7.06558L4 7.25V15.75C4 17.483 5.35645 18.8992 7.06558 18.9949L7.25 19H18.75C20.483 19 21.8992 17.6435 21.9949 15.9344L22 15.75V7.25C22 5.51697 20.6435 4.10075 18.9344 4.00514L18.75 4ZM5.5 8.899L12.6507 12.6637C12.8381 12.7623 13.0569 12.7764 13.2532 12.706L13.3493 12.6637L20.5 8.9V15.75C20.5 16.6682 19.7929 17.4212 18.8935 17.4942L18.75 17.5H7.25C6.33183 17.5 5.57881 16.7929 5.5058 15.8935L5.5 15.75V8.899ZM7.25 5.5H18.75C19.6682 5.5 20.4212 6.20711 20.4942 7.10647L20.498 7.206L13 11.1525L5.50057 7.20483C5.52453 6.25921 6.2986 5.5 7.25 5.5Z
M8.75 13.5C10.2862 13.5 11.5735 14.5658 11.9126 15.9983L21.25 16C21.6642 16 22 16.3358 22 16.75C22 17.1297 21.7178 17.4435 21.3518 17.4932L21.25 17.5L11.9129 17.5007C11.5741 18.9337 10.2866 20 8.75 20C7.21345 20 5.92594 18.9337 5.58712 17.5007L2.75 17.5C2.33579 17.5 2 17.1642 2 16.75C2 16.3703 2.28215 16.0565 2.64823 16.0068L2.75 16L5.58712 15.9993C5.92594 14.5663 7.21345 13.5 8.75 13.5ZM8.75 15C7.98586 15 7.33611 15.4898 7.09753 16.1725L7.07696 16.2352L7.03847 16.3834C7.01326 16.5016 7 16.6242 7 16.75C7 16.9048 7.02011 17.055 7.05785 17.1979L7.09766 17.3279L7.12335 17.3966C7.38055 18.0431 8.01191 18.5 8.75 18.5C9.51376 18.5 10.1632 18.0107 10.4021 17.3285L10.4422 17.1978L10.4251 17.2581C10.4738 17.0973 10.5 16.9267 10.5 16.75C10.5 16.6452 10.4908 16.5425 10.4731 16.4428L10.4431 16.3057L10.4231 16.2353L10.3763 16.1024C10.1188 15.4565 9.48771 15 8.75 15ZM15.25 4C16.7866 4 18.0741 5.06632 18.4129 6.49934L21.25 6.5C21.6642 6.5 22 6.83579 22 7.25C22 7.6297 21.7178 7.94349 21.3518 7.99315L21.25 8L18.4129 8.00066C18.0741 9.43368 16.7866 10.5 15.25 10.5C13.7134 10.5 12.4259 9.43368 12.0871 8.00066L2.75 8C2.33579 8 2 7.66421 2 7.25C2 6.8703 2.28215 6.55651 2.64823 6.50685L2.75 6.5L12.0874 6.49833C12.4265 5.06582 13.7138 4 15.25 4ZM15.25 5.5C14.4859 5.5 13.8361 5.98976 13.5975 6.6725L13.577 6.73515L13.5385 6.88337C13.5133 7.0016 13.5 7.12425 13.5 7.25C13.5 7.40483 13.5201 7.55497 13.5579 7.69794L13.5977 7.82787L13.6234 7.89664C13.8805 8.54307 14.5119 9 15.25 9C16.0138 9 16.6632 8.51073 16.9021 7.82852L16.9422 7.69781L16.9251 7.75808C16.9738 7.59729 17 7.4267 17 7.25C17 7.14518 16.9908 7.0425 16.9731 6.94275L16.9431 6.80565L16.9231 6.73529L16.8763 6.60236C16.6188 5.95647 15.9877 5.5 15.25 5.5Z
diff --git a/Source/Common/PageItemsViewModel.cs b/Source/Common/PageItemsViewModel.cs
index 3e9b6c4..a0d2b3d 100644
--- a/Source/Common/PageItemsViewModel.cs
+++ b/Source/Common/PageItemsViewModel.cs
@@ -21,47 +21,62 @@ public void Clear()
SelectedItem = default;
}
- public void MoveItemDown(TItem? item)
+ public void MoveItemDown(object? item)
{
if (item == null)
{
return;
}
- var index = Collection.IndexOf(item);
+ var index = Collection.IndexOf((TItem)item);
+ Move(index, ++index);
+ }
- if (index >= Collection.Count - 1)
+ public void MoveItemUp(object? item)
+ {
+ if (item == null)
{
return;
}
- Collection.Move(index, ++index);
+ var index = Collection.IndexOf((TItem)item);
+ Move(index, --index);
}
- public void MoveItemUp(TItem? item)
+ public void RemoveItem(object? item)
{
if (item == null)
{
return;
}
- var index = Collection.IndexOf(item);
+ var index = Collection.IndexOf((TItem)item);
+ Collection.Remove((TItem)item);
- if (index <= 0)
+ if (index > 0)
{
- return;
+ SelectedItem = Collection[--index];
}
-
- Collection.Move(index, --index);
}
- public void RemoveItem(TItem? item)
+ void Move(int from, int to)
{
- if (item == null)
+ if (from == -1)
{
return;
}
- Collection.Remove(item);
+ if (to == Collection.Count)
+ {
+ return;
+ }
+
+ var restoreSelection = ReferenceEquals(Collection[from], SelectedItem);
+ Collection.Move(from, to);
+
+ if (restoreSelection)
+ {
+ SelectedItem = Collection[to];
+ }
}
}
\ No newline at end of file
diff --git a/Source/Controls/BufferPreview/BufferConverter.cs b/Source/Controls/BufferPreview/BufferConverter.cs
index 08d860f..d8174d6 100644
--- a/Source/Controls/BufferPreview/BufferConverter.cs
+++ b/Source/Controls/BufferPreview/BufferConverter.cs
@@ -2,20 +2,13 @@
namespace mqttMultimeter.Controls;
-public sealed class BufferConverter
+public sealed class BufferConverter(string name, string? grammar, Func convert)
{
- readonly Func _convertCallback;
+ readonly Func _convertCallback = convert ?? throw new ArgumentNullException(nameof(convert));
- public BufferConverter(string name, string? grammar, Func convert)
- {
- Name = name ?? throw new ArgumentNullException(nameof(name));
- Grammar = grammar;
- _convertCallback = convert ?? throw new ArgumentNullException(nameof(convert));
- }
-
- public string? Grammar { get; }
+ public string? Grammar { get; } = grammar;
- public string Name { get; }
+ public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
public string Convert(byte[]? buffer)
{
diff --git a/Source/Controls/BufferPreview/BufferPreviewView.axaml b/Source/Controls/BufferPreview/BufferPreviewView.axaml
index 2b7e835..905d143 100644
--- a/Source/Controls/BufferPreview/BufferPreviewView.axaml
+++ b/Source/Controls/BufferPreview/BufferPreviewView.axaml
@@ -80,12 +80,13 @@
BorderThickness="0"
Grid.Row="1"
ShowLineNumbers="True"
+ FontFamily="{StaticResource MonospaceFontFamily}"
IsVisible="{Binding !ShowRaw, RelativeSource={RelativeSource TemplatedParent}}" />
+ IsVisible="{Binding ShowRaw, RelativeSource={RelativeSource TemplatedParent}}" />
diff --git a/Source/Controls/BufferPreview/BufferPreviewView.axaml.cs b/Source/Controls/BufferPreview/BufferPreviewView.axaml.cs
index e1b99f7..d431c16 100644
--- a/Source/Controls/BufferPreview/BufferPreviewView.axaml.cs
+++ b/Source/Controls/BufferPreview/BufferPreviewView.axaml.cs
@@ -44,7 +44,7 @@ public sealed class BufferInspectorView : TemplatedControl
string _content = string.Empty;
Button? _copyToClipboardButton;
- string? _currentTextEditorLanguage;
+ HexBox? _hexBox;
Button? _saveToFileButton;
TextEditor? _textEditor;
TextMate.Installation? _textMateInstallation;
@@ -80,8 +80,8 @@ public string? SelectedFormatName
set => SetValue(SelectedFormatNameProperty, value);
}
- static ObservableCollection SharedConverters { get; } = new()
- {
+ static ObservableCollection SharedConverters { get; } =
+ [
new BufferConverter("ASCII", null, b => Encoding.ASCII.GetString(b)),
new BufferConverter("Base64", null, Convert.ToBase64String),
new BufferConverter("Binary", null, BinaryEncoder.GetString),
@@ -94,6 +94,7 @@ public string? SelectedFormatName
return JsonSerializer.Serialize(JsonNode.Parse(json), JsonSerializerOptions);
}),
+
new BufferConverter("MessagePack as JSON",
"source.json.comments",
b =>
@@ -102,6 +103,7 @@ public string? SelectedFormatName
return JsonSerializerService.Instance?.Format(json) ?? string.Empty;
}),
+
new BufferConverter("RAW", null, _ => "RAW"), // Special case!
new BufferConverter("Unicode", null, b => Encoding.Unicode.GetString(b)),
new BufferConverter("UTF-8", null, b => Encoding.UTF8.GetString(b)),
@@ -113,7 +115,7 @@ public string? SelectedFormatName
var xml = Encoding.UTF8.GetString(b);
return XDocument.Parse(xml).ToString(SaveOptions.None);
})
- };
+ ];
public bool ShowRaw
{
@@ -125,6 +127,8 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
+ _hexBox = (HexBox)this.GetTemplateChild("HexBox");
+
_textEditor = (TextEditor)this.GetTemplateChild("TextEditor");
_textMateInstallation = _textEditor.InstallTextMate(_textEditorRegistryOptions);
SyncTextEditor();
@@ -148,7 +152,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
if (change.Property == SelectedFormatProperty)
{
- ShowRaw = SelectedFormat?.Name == "RAW";
+ ShowRaw = ReferenceEquals(SelectedFormat?.Name, "RAW");
}
if (change.Property == SelectedFormatNameProperty)
@@ -185,17 +189,15 @@ void OnSaveToFile(object? sender, RoutedEventArgs e)
}
}
};
-
+
var file = await TopLevel.GetTopLevel(this)!.StorageProvider.SaveFilePickerAsync(filePickerOptions);
if (file == null)
{
return;
}
- await using (var stream = await file.OpenWriteAsync())
- {
- await stream.WriteAsync(Buffer ?? Array.Empty());
- }
+ await using var stream = await file.OpenWriteAsync();
+ await stream.WriteAsync(Buffer ?? Array.Empty());
}
catch (FileNotFoundException)
{
@@ -239,10 +241,12 @@ void SelectFormat()
{
if (string.IsNullOrEmpty(SelectedFormatName))
{
- SelectedFormat = Formats.FirstOrDefault();
+ SelectedFormat = Formats.FirstOrDefault(i => i.Name.Equals("UTF-8"));
+ }
+ else
+ {
+ SelectedFormat = Formats.FirstOrDefault(f => string.Equals(f.Name, SelectedFormatName));
}
-
- SelectedFormat = Formats.FirstOrDefault(f => string.Equals(f.Name, SelectedFormatName));
if (SelectedFormat == null)
{
@@ -252,38 +256,26 @@ void SelectFormat()
void SyncTextEditor()
{
- if (_textEditor == null)
+ if (_textEditor == null || _hexBox == null)
{
return;
}
- _textEditor.Text = _content;
-
if (SelectedFormat == null)
{
return;
}
- if (_textMateInstallation == null)
- {
- return;
- }
+ _textMateInstallation?.SetGrammar(SelectedFormat.Grammar);
- // Avoid updating the language all the time even without a change!
- if (string.Equals(_currentTextEditorLanguage, SelectedFormat.Grammar))
- {
- return;
- }
-
- _currentTextEditorLanguage = SelectedFormat.Grammar;
+ // It is important to set the content after the grammar so that
+ // the highlighting gets applied properly!
+ _textEditor.Text = _content;
- if (SelectedFormat.Grammar == null)
- {
- _textMateInstallation.SetGrammar(_currentTextEditorLanguage);
- }
- else
+ if (SelectedFormat.Name == "RAW")
{
- _textMateInstallation.SetGrammar(_textEditorRegistryOptions.GetScopeByLanguageId(_currentTextEditorLanguage));
+ // Only fill the data of the hex box when it is actually used!
+ _hexBox.Value = Buffer;
}
}
}
\ No newline at end of file
diff --git a/Source/Controls/HexBox.axaml b/Source/Controls/HexBox.axaml
index bc07a15..9610853 100644
--- a/Source/Controls/HexBox.axaml
+++ b/Source/Controls/HexBox.axaml
@@ -41,7 +41,7 @@
+ Design.Width="800"
+ x:DataType="main:MainViewModel">
@@ -41,7 +42,7 @@
-
@@ -49,7 +50,7 @@
-
@@ -57,7 +58,7 @@
-
@@ -65,7 +66,7 @@
-
@@ -73,7 +74,7 @@
-
@@ -81,7 +82,7 @@
-
@@ -89,7 +90,7 @@
-
@@ -97,7 +98,7 @@
-
@@ -117,14 +118,14 @@
+ IsVisible="{CompiledBinding !ConnectionPage.IsConnected}" />
+ IsVisible="{CompiledBinding ConnectionPage.IsConnected}" />
+ Text="{CompiledBinding ConnectionPage.DisconnectedReason.Reason}" />
+ Text="{CompiledBinding ConnectionPage.DisconnectedReason.AdditionalInformation}" />
@@ -132,18 +133,18 @@
-
+
-
+
diff --git a/Source/Main/MainViewModel.cs b/Source/Main/MainViewModel.cs
index 8b34f35..dfa40d1 100644
--- a/Source/Main/MainViewModel.cs
+++ b/Source/Main/MainViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
using mqttMultimeter.Common;
using mqttMultimeter.Pages.Connection;
using mqttMultimeter.Pages.Inflight;
@@ -8,12 +9,17 @@
using mqttMultimeter.Pages.Publish;
using mqttMultimeter.Pages.Subscriptions;
using mqttMultimeter.Pages.TopicExplorer;
+using mqttMultimeter.Services.Mqtt;
+using MQTTnet.Client;
using ReactiveUI;
namespace mqttMultimeter.Main;
public sealed class MainViewModel : BaseViewModel
{
+ readonly MqttClientService _mqttClientService;
+
+ int _counter;
object? _overlayContent;
public MainViewModel(ConnectionPageViewModel connectionPage,
@@ -23,8 +29,12 @@ public MainViewModel(ConnectionPageViewModel connectionPage,
TopicExplorerPageViewModel topicExplorerPage,
PacketInspectorPageViewModel packetInspectorPage,
InfoPageViewModel infoPage,
- LogPageViewModel logPage)
+ LogPageViewModel logPage,
+ MqttClientService mqttClientService)
{
+ _mqttClientService = mqttClientService ?? throw new ArgumentNullException(nameof(mqttClientService));
+ _mqttClientService.ApplicationMessageReceived += IncreaseCounter;
+
ConnectionPage = AttachEvents(connectionPage);
PublishPage = AttachEvents(publishPage);
SubscriptionsPage = AttachEvents(subscriptionsPage);
@@ -42,6 +52,12 @@ public MainViewModel(ConnectionPageViewModel connectionPage,
public ConnectionPageViewModel ConnectionPage { get; }
+ public int Counter
+ {
+ get => _counter;
+ set => this.RaiseAndSetIfChanged(ref _counter, value);
+ }
+
public InflightPageViewModel InflightPage { get; }
public InfoPageViewModel InfoPage { get; }
@@ -67,4 +83,11 @@ TPage AttachEvents(TPage page) where TPage : BasePageViewModel
page.ActivationRequested += (_, __) => ActivatePageRequested?.Invoke(page, EventArgs.Empty);
return page;
}
+
+ Task IncreaseCounter(MqttApplicationMessageReceivedEventArgs _)
+ {
+ Counter++;
+
+ return Task.CompletedTask;
+ }
}
\ No newline at end of file
diff --git a/Source/Pages/Connection/ConnectItemListItem.axaml b/Source/Pages/Connection/ConnectItemListItem.axaml
index 46bff65..395c6fb 100644
--- a/Source/Pages/Connection/ConnectItemListItem.axaml
+++ b/Source/Pages/Connection/ConnectItemListItem.axaml
@@ -28,21 +28,21 @@
diff --git a/Source/Pages/Connection/ConnectionItemView.axaml b/Source/Pages/Connection/ConnectionItemView.axaml
index 293d5b2..c2423f6 100644
--- a/Source/Pages/Connection/ConnectionItemView.axaml
+++ b/Source/Pages/Connection/ConnectionItemView.axaml
@@ -8,6 +8,7 @@
+
@@ -32,7 +33,7 @@
Classes="image_button"
MinWidth="100"
Margin="0,0,10,0"
- Command="{Binding Connect}">
+ Command="{Binding Connect, Mode=OneTime}">
@@ -40,7 +41,7 @@
diff --git a/Source/Pages/Inflight/InflightPageViewModel.cs b/Source/Pages/Inflight/InflightPageViewModel.cs
index 51b17f5..df8c530 100644
--- a/Source/Pages/Inflight/InflightPageViewModel.cs
+++ b/Source/Pages/Inflight/InflightPageViewModel.cs
@@ -44,8 +44,6 @@ public InflightPageViewModel(MqttClientService mqttClientService, InflightPageIt
public event Action? RepeatMessageRequested;
- public long Counter => _counter;
-
public string? FilterText
{
get => _filterText;
@@ -73,10 +71,10 @@ public Task AppendMessage(MqttApplicationMessage message)
throw new ArgumentNullException(nameof(message));
}
- var newItem = CreateItemViewModel(message);
-
return Dispatcher.UIThread.InvokeAsync(() =>
{
+ var newItem = CreateItemViewModel(message);
+
_itemsSource.Add(newItem);
// TODO: Move to configuration.
@@ -121,7 +119,6 @@ Func BuildFilter(string? searchText)
InflightPageItemViewModel CreateItemViewModel(MqttApplicationMessage applicationMessage)
{
var counter = Interlocked.Increment(ref _counter);
- this.RaisePropertyChanged(nameof(Counter));
var itemViewModel = InflightPageItemViewModelFactory.Create(applicationMessage, counter);
diff --git a/Source/Pages/Log/LogPageView.axaml b/Source/Pages/Log/LogPageView.axaml
index ff67564..5a74e30 100644
--- a/Source/Pages/Log/LogPageView.axaml
+++ b/Source/Pages/Log/LogPageView.axaml
@@ -23,7 +23,7 @@
diff --git a/Source/Pages/PacketInspector/PacketInspectorPageView.axaml b/Source/Pages/PacketInspector/PacketInspectorPageView.axaml
index 20e363a..0f77cbd 100644
--- a/Source/Pages/PacketInspector/PacketInspectorPageView.axaml
+++ b/Source/Pages/PacketInspector/PacketInspectorPageView.axaml
@@ -4,11 +4,12 @@
xmlns:controls="clr-namespace:mqttMultimeter.Controls"
Design.Width="800"
Design.Height="450"
- x:Class="mqttMultimeter.Pages.PacketInspector.PacketInspectorPageView">
+ x:Class="mqttMultimeter.Pages.PacketInspector.PacketInspectorPageView"
+ x:DataType="packetInspector:PacketInspectorPageViewModel">
-
+
@@ -17,15 +18,15 @@
+ ToolTip.Tip="Remove all packet items"
+ Command="{CompiledBinding ClearItems, Mode=OneTime}">
@@ -47,7 +48,8 @@
+ x:DataType="packetInspector:PacketViewModel"
+ Text="{CompiledBinding Number, Mode=OneTime}" />
@@ -57,17 +59,22 @@
Width="Auto">
-
+
-
+ IsVisible="{CompiledBinding IsInbound, Mode=OneTime}">
+
-
+ IsVisible="{CompiledBinding !IsInbound, Mode=OneTime}">
+
@@ -84,7 +91,8 @@
+ x:DataType="packetInspector:PacketViewModel"
+ Text="{CompiledBinding Type, Mode=OneTime}" />
@@ -97,7 +105,8 @@
+ x:DataType="packetInspector:PacketViewModel"
+ Text="{CompiledBinding Length, StringFormat={}{0} bytes, Mode=OneTime}" />
@@ -105,7 +114,8 @@
-
+
+ Buffer="{CompiledBinding SelectedPacket.Data, FallbackValue={x:Null}}" />
diff --git a/Source/Pages/PacketInspector/PacketViewModel.cs b/Source/Pages/PacketInspector/PacketViewModel.cs
index 5d6762d..dabf401 100644
--- a/Source/Pages/PacketInspector/PacketViewModel.cs
+++ b/Source/Pages/PacketInspector/PacketViewModel.cs
@@ -1,6 +1,8 @@
-namespace mqttMultimeter.Pages.PacketInspector;
+using mqttMultimeter.Common;
-public sealed class PacketViewModel
+namespace mqttMultimeter.Pages.PacketInspector;
+
+public sealed class PacketViewModel : BaseViewModel
{
public byte[]? Data { get; init; }
diff --git a/Source/Pages/TopicExplorer/TopicExplorerItemMessageView.axaml b/Source/Pages/TopicExplorer/TopicExplorerItemMessageView.axaml
index 4ae89c7..54ab5bd 100644
--- a/Source/Pages/TopicExplorer/TopicExplorerItemMessageView.axaml
+++ b/Source/Pages/TopicExplorer/TopicExplorerItemMessageView.axaml
@@ -3,7 +3,8 @@
xmlns:topicExplorer="clr-namespace:mqttMultimeter.Pages.TopicExplorer"
Design.Width="800"
Design.Height="450"
- x:Class="mqttMultimeter.Pages.TopicExplorer.TopicExplorerItemMessageView">
+ x:Class="mqttMultimeter.Pages.TopicExplorer.TopicExplorerItemMessageView"
+ x:DataType="topicExplorer:TopicExplorerItemMessageViewModel">
@@ -11,7 +12,7 @@
+ Text="{CompiledBinding Timestamp, StringFormat={}{0:HH:mm:ss.fff}, Mode=OneTime}" />
+ Text="{CompiledBinding Payload, Mode=OneTime}" />
\ No newline at end of file
diff --git a/Source/Pages/TopicExplorer/TopicExplorerItemMessageViewModel.cs b/Source/Pages/TopicExplorer/TopicExplorerItemMessageViewModel.cs
index f82182f..ec71e18 100644
--- a/Source/Pages/TopicExplorer/TopicExplorerItemMessageViewModel.cs
+++ b/Source/Pages/TopicExplorer/TopicExplorerItemMessageViewModel.cs
@@ -23,15 +23,15 @@ public TopicExplorerItemMessageViewModel(DateTime timestamp, MqttApplicationMess
InflightItem = InflightPageItemViewModelFactory.Create(applicationMessage, 0);
}
- public TimeSpan Delay { get; init; }
+ public TimeSpan Delay { get; }
public InflightPageItemViewModel InflightItem { get; init; }
- public string Payload { get; init; }
+ public string Payload { get; }
public int PayloadLength { get; }
public bool Retain { get; }
- public DateTime Timestamp { get; init; }
+ public DateTime Timestamp { get; }
}
\ No newline at end of file
diff --git a/Source/Pages/TopicExplorer/TopicExplorerItemView.axaml b/Source/Pages/TopicExplorer/TopicExplorerItemView.axaml
index 63863cc..b545f51 100644
--- a/Source/Pages/TopicExplorer/TopicExplorerItemView.axaml
+++ b/Source/Pages/TopicExplorer/TopicExplorerItemView.axaml
@@ -5,11 +5,12 @@
xmlns:inflight="clr-namespace:mqttMultimeter.Pages.Inflight"
Design.Width="800"
Design.Height="450"
- x:Class="mqttMultimeter.Pages.TopicExplorer.TopicExplorerItemView">
+ x:Class="mqttMultimeter.Pages.TopicExplorer.TopicExplorerItemView"
+ x:DataType="topicExplorer:TopicExplorerItemViewModel">
-
+
@@ -19,22 +20,22 @@
+ Command="{CompiledBinding Clear, Mode=OneTime}">
@@ -43,7 +44,7 @@
@@ -56,7 +57,7 @@
@@ -69,8 +70,9 @@
-
+
@@ -83,7 +85,7 @@
@@ -97,7 +99,7 @@
+ DataContext="{CompiledBinding SelectedMessage.InflightItem, FallbackValue={x:Null}}" />
diff --git a/Source/Pages/TopicExplorer/TopicExplorerItemViewModel.cs b/Source/Pages/TopicExplorer/TopicExplorerItemViewModel.cs
index 8bb250a..00689d2 100644
--- a/Source/Pages/TopicExplorer/TopicExplorerItemViewModel.cs
+++ b/Source/Pages/TopicExplorer/TopicExplorerItemViewModel.cs
@@ -85,14 +85,25 @@ public void AddMessage(MqttApplicationMessage message)
}
string payload;
- try
+ if (message.PayloadSegment.Count == 0)
{
- payload = Encoding.UTF8.GetString(message.PayloadSegment);
+ payload = "[mqttMultimeter:EMPTY]";
}
- catch
+ else
{
- // Ignore error.
- payload = string.Empty;
+ try
+ {
+ var payloadBuilder = new StringBuilder();
+ payloadBuilder.Append(Encoding.UTF8.GetString(message.PayloadSegment));
+ payloadBuilder.Replace("\r\n", " ");
+ payloadBuilder.Replace("\n", " ");
+
+ payload = payloadBuilder.ToString();
+ }
+ catch
+ {
+ payload = "[mqttMultimeter:INVALID_UTF8]";
+ }
}
var timestamp = DateTime.Now;
@@ -108,8 +119,8 @@ public void AddMessage(MqttApplicationMessage message)
}
var viewModel = new TopicExplorerItemMessageViewModel(timestamp, message, payload, duration);
- viewModel.InflightItem.RepeatMessageRequested += (s, _) => _ownerPage.RepeatMessage((InflightPageItemViewModel)s!);
- viewModel.InflightItem.DeleteRetainedMessageRequested += (s, _) => _ownerPage.DeleteRetainedMessage((InflightPageItemViewModel)s!);
+ viewModel.InflightItem.RepeatMessageRequested += OnInflightItemOnRepeatMessageRequested;
+ viewModel.InflightItem.DeleteRetainedMessageRequested += OnInflightItemOnDeleteRetainedMessageRequested;
Messages.Add(viewModel);
@@ -128,6 +139,16 @@ public void Clear()
SelectedMessage = null;
}
+ void OnInflightItemOnDeleteRetainedMessageRequested(object? s, EventArgs _)
+ {
+ _ownerPage.DeleteRetainedMessage((InflightPageItemViewModel)s!);
+ }
+
+ void OnInflightItemOnRepeatMessageRequested(object? s, EventArgs _)
+ {
+ _ownerPage.RepeatMessage((InflightPageItemViewModel)s!);
+ }
+
void OnMessagesChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
CurrentPayload = Messages.LastOrDefault()?.Payload ?? string.Empty;
diff --git a/Source/Pages/TopicExplorer/TopicExplorerPageView.axaml b/Source/Pages/TopicExplorer/TopicExplorerPageView.axaml
index 0a514c6..ddf1b4d 100644
--- a/Source/Pages/TopicExplorer/TopicExplorerPageView.axaml
+++ b/Source/Pages/TopicExplorer/TopicExplorerPageView.axaml
@@ -5,13 +5,14 @@
xmlns:converters="clr-namespace:mqttMultimeter.Converters"
Design.Width="800"
Design.Height="450"
+ x:DataType="topicExplorer:TopicExplorerPageViewModel"
x:Class="mqttMultimeter.Pages.TopicExplorer.TopicExplorerPageView">
-
-
+
+
@@ -28,14 +29,14 @@
+ IsChecked="{CompiledBinding IsRecordingEnabled}"
+ ToolTip.Tip="Enable or disable recording">
+ Command="{CompiledBinding Clear, Mode=OneTime}">
@@ -43,23 +44,30 @@
+ IsChecked="{CompiledBinding HighlightChanges}"
+ ToolTip.Tip="Enable or disable highlighting">
-
+ Command="{CompiledBinding ExpandSelectedTree, Mode=OneTime}">
+ Command="{CompiledBinding CollapseAll, Mode=OneTime}">
+
+
+
+
+
-
-
+
+
+
+
+
+ Text="{CompiledBinding Name, Mode=OneTime}" />
@@ -48,26 +50,26 @@
Grid.Row="0"
Classes="code_text"
TextWrapping="NoWrap"
- Text="{Binding Item.CurrentPayload, FallbackValue=''}" />
+ Text="{CompiledBinding Item.CurrentPayload, FallbackValue=''}" />
+ IsVisible="{CompiledBinding !Item.Messages.IsEmpty, FallbackValue=False}">
-
-
-
-
+
+
+
+
diff --git a/Source/Pages/TopicExplorer/TopicExplorerTreeNodeViewModel.cs b/Source/Pages/TopicExplorer/TopicExplorerTreeNodeViewModel.cs
index fde3aec..229b1d8 100644
--- a/Source/Pages/TopicExplorer/TopicExplorerTreeNodeViewModel.cs
+++ b/Source/Pages/TopicExplorer/TopicExplorerTreeNodeViewModel.cs
@@ -22,6 +22,9 @@ public TopicExplorerTreeNodeViewModel(string name, TopicExplorerTreeNodeViewMode
NodesSource.Connect().Sort(SortExpressionComparer.Ascending(t => t.Name)).Bind(out var nodes).Subscribe();
Nodes = nodes;
+
+ Item = new TopicExplorerItemViewModel(OwnerPage);
+ Item.Messages.CollectionChanged += OnMessagesChanged;
}
public event EventHandler? MessagesChanged;
@@ -32,7 +35,7 @@ public bool IsExpanded
set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
}
- public TopicExplorerItemViewModel? Item { get; private set; }
+ public TopicExplorerItemViewModel Item { get; }
public string Name { get; }
@@ -46,15 +49,14 @@ public bool IsExpanded
public void AddMessage(MqttApplicationMessage message)
{
- if (Item == null)
- {
- Item = new TopicExplorerItemViewModel(OwnerPage);
- Item.Messages.CollectionChanged += OnMessagesChanged;
- }
-
Item.AddMessage(message);
}
+ public void Clear()
+ {
+ Item.Clear();
+ }
+
void OnMessagesChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
Parent?.OnMessagesChanged(sender, e);
diff --git a/Source/Program.cs b/Source/Program.cs
index 61d4b6c..92025ec 100644
--- a/Source/Program.cs
+++ b/Source/Program.cs
@@ -14,7 +14,7 @@ public static void Main(string[] args)
// Do not remove this method! It is required for the Designer.
static AppBuilder BuildAvaloniaApp()
{
- var appBuilder = AppBuilder.Configure().UseReactiveUI().UsePlatformDetect();
+ var appBuilder = AppBuilder.Configure().UseReactiveUI().UsePlatformDetect().WithInterFont();
if (Debugger.IsAttached)
{
diff --git a/Source/Services/Mqtt/MqttClientService.cs b/Source/Services/Mqtt/MqttClientService.cs
index 8d1bcec..e9fd902 100644
--- a/Source/Services/Mqtt/MqttClientService.cs
+++ b/Source/Services/Mqtt/MqttClientService.cs
@@ -86,7 +86,10 @@ public async Task Connect(ConnectionItemViewModel item)
}
else
{
- clientOptionsBuilder.WithWebSocketServer(item.ServerOptions.Host);
+ clientOptionsBuilder.WithWebSocketServer(o =>
+ {
+ o.WithUri(item.ServerOptions.Host);
+ });
}
if (item.ServerOptions.SelectedTlsVersion.Value != SslProtocols.None)
@@ -266,9 +269,17 @@ public async Task Unsubscribe(SubscriptionItemViewM
return await _mqttClient.UnsubscribeAsync(subscriptionItem.Topic).ConfigureAwait(false);
}
- Task OnApplicationMessageReceived(MqttApplicationMessageReceivedEventArgs eventArgs)
+ async Task OnApplicationMessageReceived(MqttApplicationMessageReceivedEventArgs eventArgs)
{
- return _applicationMessageReceivedEvent.InvokeAsync(eventArgs);
+ // We have to insert a small delay here because this is an UI application. If we
+ // have no delay the application will freeze as soon as there is much traffic.
+ await Task.Delay(50);
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ },
+ DispatcherPriority.Render);
+
+ await _applicationMessageReceivedEvent.InvokeAsync(eventArgs);
}
Task OnDisconnected(MqttClientDisconnectedEventArgs eventArgs)
diff --git a/Source/mqttMultimeter.csproj b/Source/mqttMultimeter.csproj
index 9c71b8e..a914e32 100644
--- a/Source/mqttMultimeter.csproj
+++ b/Source/mqttMultimeter.csproj
@@ -36,6 +36,7 @@
+