implemented arrow-up and arrow-down functionality to recall messages from the history

master
Jonas Arnold 3 years ago
parent 3632fca13c
commit 9aeff6dfec
  1. 16
      MultiTerm.Core/ViewModel/ConsoleViewModel.cs
  2. 17
      MultiTerm.Core/ViewModel/SendReceiveViewModel.cs
  3. 39
      MultiTerm.Core/ViewModel/TerminalViewModel.cs
  4. 40
      MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs
  5. 1
      MultiTerm.Wpf/View/ConsoleView.xaml
  6. 18
      MultiTerm.Wpf/View/ConsoleView.xaml.cs
  7. 13
      MultiTerm.Wpf/View/SendReceiveView.xaml

@ -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;
}

@ -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;
}

@ -25,6 +25,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
private const int displayNewDataTimerIntervalMs = 20; // after x milliseconds new data is fed forward to the UI
private readonly ConcurrentQueue<IEnumerable<ExtendedByte>> receivedDataQueue = new(); // Queue to buffer received data from communication protocol
private readonly ConcurrentQueue<IEnumerable<ExtendedByte>> 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;
}
/// <summary>
/// Shall handle the insertion of an element from the <see cref="SentMessagesHistory"/>
/// into the current sendable message area.
/// If the object is null, an empty element shall be inserted.
/// </summary>
/// <param name="element">copy of element as object</param>
protected abstract void HandleInsertElementFromHistory(object element);
/// <param name="element">copy of element as object or null</param>
protected abstract void HandleInsertElementFromHistory(object? element);
/// <summary>
/// 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);
}
/// <summary>
/// Recalls an element from the <see cref="SentMessagesHistory"/>.
/// Either recalls the previous or the next from the list according to <see cref="recallHistoryCount"/>.
/// If the current element is reached (<see cref="this.recallHistoryCount"/> = -1) an empty element is inserted.
/// </summary>
/// <param name="previous">true if previous element should be loaded, false if the next element should be loaded</param>
[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
}

@ -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;
/// <summary>
/// .NET Property for <see cref="EnterPressedEvent"/>
@ -60,6 +62,24 @@ public class MultiFormatTextBox : Control
remove { this.RemoveHandler(EnterPressedEvent, value); }
}
/// <summary>
/// .NET Property for <see cref="ArrowUpPressedEvent"/>
/// </summary>
public event RoutedEventHandler ArrowUpPressed
{
add { this.AddHandler(ArrowUpPressedEvent, value); }
remove { this.RemoveHandler(ArrowUpPressedEvent, value); }
}
/// <summary>
/// .NET Property for <see cref="ArrowDownPressedEvent"/>
/// </summary>
public event RoutedEventHandler ArrowDownPressed
{
add { this.AddHandler(ArrowDownPressedEvent, value); }
remove { this.RemoveHandler(ArrowDownPressedEvent, value); }
}
/// <summary>
/// .NET Property for CurrentMultiFormatString.
/// </summary>
@ -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;

@ -111,6 +111,7 @@
Style="{StaticResource DataContainerFontStyle}"/>
<!-- Sendable Message Input -->
<TextBox Grid.Row="3" Padding="5 3" Margin="0 5"
x:Name="sendableMessageTextBox"
AllowDrop="False"
Text="{Binding Path=SendableMessage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource DataContainerFontStyle}"

@ -25,7 +25,7 @@ public partial class ConsoleView : UserControl
throw new InvalidOperationException($"{nameof(ConsoleView)} got wrong type of DataContext.");
}
// store locally
viewModel = dataContextVm;
this.viewModel = dataContextVm;
}
private void SendableMessageTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
@ -37,6 +37,22 @@ public partial class ConsoleView : UserControl
e.Handled = true;
this.viewModel!.Send();
}
// key up => 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)

@ -28,6 +28,10 @@
<x:Type TypeName="types:NewlineSeparatorType" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<!-- Boolean values -->
<sys:Boolean x:Key="BooleanTrue">True</sys:Boolean>
<sys:Boolean x:Key="BooleanFalse">False</sys:Boolean>
</UserControl.Resources>
<Grid>
@ -103,9 +107,18 @@
CurrentMultiFormatString="{Binding Path=SendableData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<XamlBehavioursWpf:Interaction.Triggers>
<!-- Send when enter is pressed -->
<XamlBehavioursWpf:EventTrigger EventName="EnterPressed" SourceObject="{Binding ElementName=sendTextBox}">
<XamlBehavioursWpf:InvokeCommandAction Command="{Binding SendCommand}" />
</XamlBehavioursWpf:EventTrigger>
<!-- Load previous history element (parameter = true) -->
<XamlBehavioursWpf:EventTrigger EventName="ArrowUpPressed" SourceObject="{Binding ElementName=sendTextBox}">
<XamlBehavioursWpf:InvokeCommandAction Command="{Binding RecallElementFromHistoryCommand}" CommandParameter="{StaticResource BooleanTrue}" />
</XamlBehavioursWpf:EventTrigger>
<!-- Load next history element (parameter = false) -->
<XamlBehavioursWpf:EventTrigger EventName="ArrowDownPressed" SourceObject="{Binding ElementName=sendTextBox}">
<XamlBehavioursWpf:InvokeCommandAction Command="{Binding RecallElementFromHistoryCommand}" CommandParameter="{StaticResource BooleanFalse}" />
</XamlBehavioursWpf:EventTrigger>
</XamlBehavioursWpf:Interaction.Triggers>
</custom_controls:MultiFormatTextBox>
</DockPanel>

Loading…
Cancel
Save