From 9aeff6dfec31238699653c54f2dd4aa9e2f70973 Mon Sep 17 00:00:00 2001 From: Jonas Arnold Date: Tue, 6 Jun 2023 19:32:22 +0200 Subject: [PATCH] implemented arrow-up and arrow-down functionality to recall messages from the history --- MultiTerm.Core/ViewModel/ConsoleViewModel.cs | 16 +++++++- .../ViewModel/SendReceiveViewModel.cs | 17 ++++++-- MultiTerm.Core/ViewModel/TerminalViewModel.cs | 41 +++++++++++++++++-- .../MultiFormatTextBox/MultiFormatTextBox.cs | 40 ++++++++++++++++++ MultiTerm.Wpf/View/ConsoleView.xaml | 1 + MultiTerm.Wpf/View/ConsoleView.xaml.cs | 18 +++++++- MultiTerm.Wpf/View/SendReceiveView.xaml | 13 ++++++ 7 files changed, 137 insertions(+), 9 deletions(-) diff --git a/MultiTerm.Core/ViewModel/ConsoleViewModel.cs b/MultiTerm.Core/ViewModel/ConsoleViewModel.cs index adc2215..95e78d1 100644 --- a/MultiTerm.Core/ViewModel/ConsoleViewModel.cs +++ b/MultiTerm.Core/ViewModel/ConsoleViewModel.cs @@ -93,9 +93,21 @@ public partial class ConsoleViewModel : TerminalViewModel } } - protected override void HandleInsertElementFromHistory(object element) + protected override void HandleInsertElementFromHistory(object? element) { - if(element is not StringHistoryElement elementString) { throw new Exception($"'{HandleInsertElementFromHistory}()' in {nameof(ConsoleViewModel)} got wrong type of element. Got Type: {element.GetType()}"); } + // null element received => insert empty + if (element == null) + { + this.SendableMessage = string.Empty; + return; + } + + // otherwise check type + if (element is not StringHistoryElement elementString) + { + throw new Exception($"'{HandleInsertElementFromHistory}()' in {nameof(ConsoleViewModel)} got wrong type of element. Got Type: {element.GetType()}"); + } + // overwrite sendable message with history element this.SendableMessage = elementString.Text; } diff --git a/MultiTerm.Core/ViewModel/SendReceiveViewModel.cs b/MultiTerm.Core/ViewModel/SendReceiveViewModel.cs index db26204..46b9e26 100644 --- a/MultiTerm.Core/ViewModel/SendReceiveViewModel.cs +++ b/MultiTerm.Core/ViewModel/SendReceiveViewModel.cs @@ -60,11 +60,22 @@ public partial class SendReceiveViewModel : TerminalViewModel } } - protected override void HandleInsertElementFromHistory(object element) + protected override void HandleInsertElementFromHistory(object? element) { - if(element is not MultiFormatString mfs) { throw new Exception($"'{HandleInsertElementFromHistory}()' in {nameof(SendReceiveViewModel)} got wrong type of element. Got Type: {element.GetType()}"); } + // null element received => insert empty + if (element == null) + { + this.SendableData = new MultiFormatString(); + return; + } + + // otherwise check type + if(element is not MultiFormatString mfs) + { + throw new Exception($"'{HandleInsertElementFromHistory}()' in {nameof(SendReceiveViewModel)} got wrong type of element. Got Type: {element.GetType()}"); + } - // overwrite + // overwrite if type is valid this.SendableData = mfs; } diff --git a/MultiTerm.Core/ViewModel/TerminalViewModel.cs b/MultiTerm.Core/ViewModel/TerminalViewModel.cs index bb50f1f..31701c4 100644 --- a/MultiTerm.Core/ViewModel/TerminalViewModel.cs +++ b/MultiTerm.Core/ViewModel/TerminalViewModel.cs @@ -20,11 +20,12 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie private const string defaultSendNewlineSeparatorAppSettingsKey = "DefaultSendNewlineSeparator"; private readonly IAppSettingsProvider appSettings; private readonly IMessenger messenger; - + private readonly RecurringTimer displayNewDataTimer; // Timer for data updates private const int displayNewDataTimerIntervalMs = 20; // after x milliseconds new data is fed forward to the UI private readonly ConcurrentQueue> receivedDataQueue = new(); // Queue to buffer received data from communication protocol private readonly ConcurrentQueue> sentDataQueue = new(); // Queue to buffer sent data (as reported from communication protocol) + private int recallHistoryCount = -1; // counter to remember position in history when recalling. -1 = current entry, 0 = last sent element, ... public abstract TerminalViewType ViewType { get; } @@ -359,14 +360,18 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie // add to list at the beginning this.SentMessagesHistory.Insert(0, copy); + + // reset counter for recalling to current entry (-1) + this.recallHistoryCount = -1; } /// /// Shall handle the insertion of an element from the /// into the current sendable message area. + /// If the object is null, an empty element shall be inserted. /// - /// copy of element as object - protected abstract void HandleInsertElementFromHistory(object element); + /// copy of element as object or null + protected abstract void HandleInsertElementFromHistory(object? element); /// /// Allows to insert an element from the history into the current send message. @@ -381,5 +386,35 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie // let inheriting classes handle it this.HandleInsertElementFromHistory(copy); } + + /// + /// Recalls an element from the . + /// Either recalls the previous or the next from the list according to . + /// If the current element is reached ( = -1) an empty element is inserted. + /// + /// true if previous element should be loaded, false if the next element should be loaded + [RelayCommand] + public void RecallElementFromHistory(bool previous) + { + int offset = previous ? +1 : -1; + + // -1 or smaller = current entry = not stored => insert empty + if(this.recallHistoryCount + offset <= -1) + { + // fix to -1 + this.recallHistoryCount = -1; + // insert null element (empty) + this.HandleInsertElementFromHistory(null); + } + // element at this location exists + else if (this.SentMessagesHistory.ElementAtOrDefault(recallHistoryCount + offset) != null) + { + // apply offset + this.recallHistoryCount += offset; + // insert element (includes cloning it) + this.InsertElementFromHistory(this.SentMessagesHistory.ElementAt(recallHistoryCount)); + } + // else just leave everything as it is + } #endregion } diff --git a/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs b/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs index 62590ee..8718910 100644 --- a/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs +++ b/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs @@ -50,6 +50,8 @@ public class MultiFormatTextBox : Control typeMetadata: new FrameworkPropertyMetadata(null, OnCurrentMultiFormatStringChanged)); public static readonly RoutedEvent EnterPressedEvent; + public static readonly RoutedEvent ArrowUpPressedEvent; + public static readonly RoutedEvent ArrowDownPressedEvent; /// /// .NET Property for @@ -60,6 +62,24 @@ public class MultiFormatTextBox : Control remove { this.RemoveHandler(EnterPressedEvent, value); } } + /// + /// .NET Property for + /// + public event RoutedEventHandler ArrowUpPressed + { + add { this.AddHandler(ArrowUpPressedEvent, value); } + remove { this.RemoveHandler(ArrowUpPressedEvent, value); } + } + + /// + /// .NET Property for + /// + public event RoutedEventHandler ArrowDownPressed + { + add { this.AddHandler(ArrowDownPressedEvent, value); } + remove { this.RemoveHandler(ArrowDownPressedEvent, value); } + } + /// /// .NET Property for CurrentMultiFormatString. /// @@ -78,6 +98,12 @@ public class MultiFormatTextBox : Control EnterPressedEvent = EventManager.RegisterRoutedEvent("EnterPressed", RoutingStrategy.Bubble, typeof(RoutedEventArgs), typeof(MultiFormatTextBox)); + ArrowUpPressedEvent = EventManager.RegisterRoutedEvent("ArrowUpPressed", + RoutingStrategy.Bubble, typeof(RoutedEventArgs), + typeof(MultiFormatTextBox)); + ArrowDownPressedEvent = EventManager.RegisterRoutedEvent("ArrowDownPressed", + RoutingStrategy.Bubble, typeof(RoutedEventArgs), + typeof(MultiFormatTextBox)); } public override void OnApplyTemplate() @@ -306,6 +332,20 @@ public class MultiFormatTextBox : Control RoutedEventArgs args = new(EnterPressedEvent); RaiseEvent(args); } + else if(e.Key == Key.Up) + { + e.Handled = true; + // raise event + RoutedEventArgs args = new(ArrowUpPressedEvent); + RaiseEvent(args); + } + else if (e.Key == Key.Down) + { + e.Handled = true; + // raise event + RoutedEventArgs args = new(ArrowDownPressedEvent); + RaiseEvent(args); + } else if(e.Key == Key.Space) { e.Handled = true; diff --git a/MultiTerm.Wpf/View/ConsoleView.xaml b/MultiTerm.Wpf/View/ConsoleView.xaml index e5a6ad6..c485f75 100644 --- a/MultiTerm.Wpf/View/ConsoleView.xaml +++ b/MultiTerm.Wpf/View/ConsoleView.xaml @@ -111,6 +111,7 @@ Style="{StaticResource DataContainerFontStyle}"/> recall previous history element + else if (e.Key == Key.Up) + { + // do not forward key => is handled + e.Handled = true; + this.viewModel!.RecallElementFromHistory(previous: true); + this.sendableMessageTextBox.CaretIndex = this.sendableMessageTextBox.Text.Length; // set cursor to end + } + // key down => recall next history element + else if (e.Key == Key.Down) + { + // do not forward key => is handled + e.Handled = true; + this.viewModel!.RecallElementFromHistory(previous: false); + this.sendableMessageTextBox.CaretIndex = this.sendableMessageTextBox.Text.Length; // set cursor to end + } } private void DataTextBox_TextChanged(object sender, TextChangedEventArgs e) diff --git a/MultiTerm.Wpf/View/SendReceiveView.xaml b/MultiTerm.Wpf/View/SendReceiveView.xaml index fa20db9..1204bcb 100644 --- a/MultiTerm.Wpf/View/SendReceiveView.xaml +++ b/MultiTerm.Wpf/View/SendReceiveView.xaml @@ -28,6 +28,10 @@ + + + True + False @@ -103,9 +107,18 @@ CurrentMultiFormatString="{Binding Path=SendableData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> + + + + + + + + +