removed ICommunicationDataViewModel,

replaced generic CommunicationDataViewModel with specific for SendReceiveView (called MultiFormatDataView),
implemented handling data methods directly into TerminalViewModel,
implemented time display in MultiFormatDataView,
implemented time properties in MutliFormatDataView,
fixed selection order by sorting
master
Jonas Arnold 3 years ago
parent 82b8f4198d
commit bc33e30083
  1. 23
      MultiTerm.Core/ViewModel/ByteDataViewModel.cs
  2. 41
      MultiTerm.Core/ViewModel/ICommunicationDataViewModel.cs
  3. 99
      MultiTerm.Core/ViewModel/MultiFormatDataViewModel.cs
  4. 41
      MultiTerm.Core/ViewModel/SendReceiveViewModel.cs
  5. 51
      MultiTerm.Core/ViewModel/TerminalViewModel.cs
  6. 33
      MultiTerm.Wpf.CustomControl/MultiFormatDataView/BoolToVisibilityConverter.cs
  7. 144
      MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs
  8. 21
      MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml
  9. 4
      MultiTerm.Wpf/View/SendReceiveView.xaml

@ -4,7 +4,7 @@ using System.Text;
namespace MultiTerm.Core.ViewModel;
public partial class ByteDataViewModel : ObservableObject, IDataViewModel
public partial class ByteDataViewModel : ObservableObject, IDataViewModel, IComparable<ByteDataViewModel>
{
/// <summary>
/// Object of data model.
@ -50,4 +50,25 @@ public partial class ByteDataViewModel : ObservableObject, IDataViewModel
this.DisplayStringBin = extendedByte.ToBinaryString();
this.Time = extendedByte.Time;
}
#region IComparable Implementation
/// <summary>
/// IComparable method. To allow sorting <see cref="ByteDataViewModel"/>
/// </summary>
/// <param name="other">compared element</param>
/// <returns></returns>
public int CompareTo(ByteDataViewModel? other)
{
if(other == null)
return 1;
if (other.Time.Ticks > this.Time.Ticks)
return -1;
else if(this.Time.Ticks > other.Time.Ticks)
return 1;
else // equal
return 0;
}
#endregion
}

@ -1,41 +0,0 @@
using CommunityToolkit.Mvvm.Input;
using MultiTerm.Core.Types;
using System.Collections.ObjectModel;
namespace MultiTerm.Core.ViewModel;
public interface ICommunicationDataViewModel<T_Data, T_Raw> where T_Data : IDataViewModel
{
/// <summary>
/// Collection of data in context of a communication protocol.
/// </summary>
ObservableCollection<T_Data> Data { get; }
/// <summary>
/// String representation of <see cref="Data"/>.
/// </summary>
string DataAsString { get; }
/// <summary>
/// Collection of selected items of the <see cref="Data"/>.
/// May be set externally.
/// </summary>
ObservableCollection<T_Data> Selected { get; set; }
/// <summary>
/// Currently selected NewlineSeparator to group the <see cref="Data"/> into lines.
/// </summary>
NewlineSeparatorType NewlineSeparator { get; set; }
/// <summary>
/// Method to insert raw data into the <see cref="Data"/> Collection.
/// Also updates the <see cref="DataAsString"/> property.
/// </summary>
/// <param name="data">collection of new data to insert</param>
void HandleNewData(IEnumerable<T_Raw> data);
/// <summary>
/// Allows to clear the data.
/// </summary>
IRelayCommand ClearCommand { get; }
}

@ -5,11 +5,12 @@ using CommunityToolkit.Mvvm.Input;
using MultiTerm.Core.Types;
using MultiTerm.Protocols.Model;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text;
namespace MultiTerm.Core.ViewModel;
public partial class CommunicationDataViewModel : ObservableObject, ICommunicationDataViewModel<ByteDataViewModel, ExtendedByte>
public partial class MultiFormatDataViewModel : ObservableObject
{
private readonly IContext uiContext;
private int dataCharacterCount = 0;
@ -29,6 +30,11 @@ public partial class CommunicationDataViewModel : ObservableObject, ICommunicati
[ObservableProperty]
NewlineSeparatorType newlineSeparator = NewlineSeparatorType.None;
[ObservableProperty]
private string selectedDataFirstAbsoluteTime = string.Empty;
[ObservableProperty]
private string selectedDataTimediff = string.Empty;
#endregion
/// <summary>
@ -36,31 +42,9 @@ public partial class CommunicationDataViewModel : ObservableObject, ICommunicati
/// </summary>
public static string NewlineSequence = Environment.NewLine;
public CommunicationDataViewModel(IContext context)
public MultiFormatDataViewModel(IContext context)
{
this.uiContext = context;
// TEMP
//{
// //string exampleData = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pellentesque elit eget gravida cum sociis natoque penatibus et magnis. Purus sit amet volutpat consequat mauris nunc congue nisi vitae. Id ornare arcu odio ut sem. Neque ornare aenean euismod elementum nisi quis eleifend. Faucibus vitae aliquet nec ullamcorper sit. Fermentum iaculis eu non diam phasellus vestibulum lorem sed risus. Pellentesque nec nam aliquam sem et tortor consequat id porta. Diam sollicitudin tempor id eu nisl. Fames ac turpis egestas sed tempus urna et. Commodo odio aenean sed adipiscing diam donec adipiscing tristique risus. Id aliquet lectus proin nibh nisl condimentum id. Dolor sit amet consectetur adipiscing elit duis. Sed vulputate odio ut enim blandit. Neque convallis a cras semper auctor neque vitae.\r\n\r\nLacus laoreet non curabitur gravida arcu ac tortor. Volutpat maecenas volutpat blandit aliquam. Neque laoreet suspendisse interdum consectetur. Tincidunt augue interdum velit euismod in pellentesque massa placerat. Magna fringilla urna porttitor rhoncus dolor purus non enim praesent. Fermentum leo vel orci porta non pulvinar neque laoreet suspendisse. Scelerisque viverra mauris in aliquam sem fringilla. Nec feugiat nisl pretium fusce id velit ut. Urna cursus eget nunc scelerisque viverra mauris. Condimentum mattis pellentesque id nibh tortor id aliquet. Enim sed faucibus turpis in eu. Adipiscing elit pellentesque habitant morbi tristique. A pellentesque sit amet porttitor eget dolor morbi non arcu. Amet commodo nulla facilisi nullam vehicula ipsum a arcu. Hac habitasse platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Curabitur vitae nunc sed velit dignissim sodales ut. Malesuada fames ac turpis egestas maecenas pharetra. Tellus pellentesque eu tincidunt tortor aliquam.\r\n\r\nVitae proin sagittis nisl rhoncus mattis rhoncus urna neque viverra. Eget mi proin sed libero enim sed faucibus. Commodo nulla facilisi nullam vehicula ipsum. Proin libero nunc consequat interdum varius sit amet mattis vulputate. Morbi tristique senectus et netus. Feugiat scelerisque varius morbi enim nunc. Nulla aliquet enim tortor at auctor urna nunc. Non pulvinar neque laoreet suspendisse interdum consectetur libero id. Tellus orci ac auctor augue mauris augue neque gravida in. In egestas erat imperdiet sed euismod. Amet volutpat consequat mauris nunc congue nisi. Massa eget egestas purus viverra accumsan in. Eget duis at tellus at. Mi sit amet mauris commodo quis imperdiet. Nibh mauris cursus mattis molestie a iaculis at.\r\n\r\nNetus et malesuada fames ac turpis. Sit amet dictum sit amet justo donec. Euismod quis viverra nibh cras pulvinar mattis. Sit amet commodo nulla facilisi nullam vehicula ipsum a arcu. Scelerisque felis imperdiet proin fermentum. Ac tincidunt vitae semper quis lectus nulla at. Sit amet commodo nulla facilisi nullam vehicula. Enim lobortis scelerisque fermentum dui faucibus in ornare quam. Quam id leo in vitae turpis massa sed. Quam quisque id diam vel quam. A condimentum vitae sapien pellentesque. Neque aliquam vestibulum morbi blandit cursus risus at. Velit laoreet id donec ultrices tincidunt arcu non sodales neque. Tempus imperdiet nulla malesuada pellentesque elit eget gravida cum sociis. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor. Nec feugiat in fermentum posuere. Dui accumsan sit amet nulla. Lacus viverra vitae congue eu consequat ac felis. Etiam tempor orci eu lobortis. Fermentum leo vel orci porta non pulvinar.";
// string exampleData = "\r\nUnicode Latin Letter D: \u018A\n--------------------------------------------------------------\r\nSPLITFLAP\r\n--------------------------------------------------------------\r\nMcuShell ; Group of McuShell commands\r\n help|status ; Print help or status information\r\nSplitFlap ; Group of McuRTOS commands\r\n help ; Print help or status information\r\n setId <SetupId> <hwId> ; sets the position (setupId) of the sf and its id (hwId)\r\n initAll ; init all Splitflaps\r\n Display <string> ; displays as many chars of the string as sf are available\r\n addId <hwId> <offset> ; add new hwId with offset\r\nMcuRTOS ; Group of McuRTOS commands\r\n help|status ; Print help or status information\r\n tasklist ; Print tasklist\r\nMcuFlash ; Group of flash ini commands\r\n help|status ; Print help or status information\r\n dump <start> <size> ; Dump memory data\r\n erase <addr> <size> ; Erase memory at address\r\nini ; Group of flash ini commands\r\n help|status ; Print help or status information\r\n dump ; Dump data information\r\n erase ; Erase data information\r\nMcuMinINI ; Group of McuMinINI commands\r\n help|status ; Print help or status information\r\n read <f> <s> <k> ; Read a key from a section in a file\r\n write <f> <s> <k> <v> ; Write a key with value to a section in a file\r\n delkey <f> <s> <k> ; Delete a key in a section of file\r\n delsec <f> <s> ; Delete a section in a file\r\nnvmc ; Group of NVMC commands\r\n help|status ; Print help or status information\r\n flags <val> ; Set flags\r\nrs ; Group of RS-485 commands\r\n help|status ; Print help or status information\r\n addr <addr> ; Set RS-485 address\r\n send <text> ; Send a text to the RS-485 bus\r\n sendcmd <addr> <cmd> ; Send a shell command to the RS-485 address and check response\r\n log on|off ; Log RS-485 bus activity to McuLog\r\nMcuUart485 ; Group of RS-485 commands\r\n help|status ; Print help or status information\r\n clear <flags> ; Clear UART ISR flags\r\n\r\nCMD>";
// //string exampleData = "This is some example Text\nSecond line\nNow a very long line: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
// int counter = 0;
// int lineNumber = 1;
// //List<ExtendedChar> listOfChars = new();
// foreach (var dataByte in exampleData)
// {
// if(++counter > 100 || dataByte == '\n')
// {
// //this.ReceivedData.Add();
// //listOfChars = new List<ExtendedChar>();
// counter = 0;
// lineNumber += 1;
// }
// var extdChar = new ExtendedChar(dataByte);
// //listOfChars.Add(extdChar);
// this.ReceivedData.Add(new DataViewModel(extdChar, lineNumber));
// }
//}
}
public void HandleNewData(IEnumerable<ExtendedByte> newRawData)
@ -99,6 +83,73 @@ public partial class CommunicationDataViewModel : ObservableObject, ICommunicati
});
}
partial void OnSelectedChanging(ObservableCollection<ByteDataViewModel>? value)
{
// remove event handler when there was previously a collection of selected
if(this.Selected != null)
{
this.Selected.CollectionChanged -= this.Selected_CollectionChanged;
}
}
partial void OnSelectedChanged(ObservableCollection<ByteDataViewModel>? value)
{
// add event handler
if (this.Selected != null)
{
this.Selected.CollectionChanged += Selected_CollectionChanged;
}
// manually update
this.UpdateTimeProperties();
}
private void Selected_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
this.UpdateTimeProperties();
}
private void UpdateTimeProperties()
{
if (this.Selected == null || this.Selected.Count == 0)
{
this.SelectedDataFirstAbsoluteTime = string.Empty;
this.SelectedDataTimediff = string.Empty;
return;
}
if (this.Selected.Count == 1)
{
this.SelectedDataFirstAbsoluteTime = $"Time: {this.Selected.First().Time:HH:mm:ss.ff}";
this.SelectedDataTimediff = string.Empty;
}
if (this.Selected.Count > 1)
{
TimeSpan timediff;
TimeOnly lastSelected = this.Selected.Last().Time, firstSelected = this.Selected.First().Time;
Debug.WriteLine($"First time selected: {firstSelected:HH.mm:ss.ff} Ticks: {firstSelected.Ticks}");
Debug.WriteLine($"Last time selected: {lastSelected:HH.mm:ss.ff} Ticks: {lastSelected.Ticks}");
// calculate timedifference according to selection direction
// (preventive. selection should arrive sorted correctly)
if (lastSelected > firstSelected)
{
timediff = lastSelected - firstSelected;
}
else
{
timediff = firstSelected - lastSelected;
}
Debug.WriteLine($"Calculated timediff Ticks: {timediff.Ticks}");
this.SelectedDataFirstAbsoluteTime = $"Time (first selected): {firstSelected:HH:mm:ss.ff}";
this.SelectedDataTimediff = $"Difference (first to last): {timediff.TotalMilliseconds:n} ms";
}
}
#region Collection Manipulation
/// <summary>
/// Function that reorders a given collection with item type <see cref="ByteDataViewModel"/> using the given <paramref name="newlineSeparatorType"/>.

@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Core.Model;
using MultiTerm.Core.Types;
using MultiTerm.Protocols.Model;
namespace MultiTerm.Core.ViewModel;
@ -12,17 +13,36 @@ public partial class SendReceiveViewModel : TerminalViewModel
{
public override TerminalViewType ViewType => TerminalViewType.SendReceive;
#region Observable Properties
/// <summary>
/// Send data model.
/// Holds communication data that was received via the communication protocol.
/// </summary>
[ObservableProperty]
private MultiFormatDataViewModel receivedData;
/// <summary>
/// Holds communication data that was sent via the communication protocol.
/// </summary>
[ObservableProperty]
private MultiFormatDataViewModel sentData;
/// <summary>
/// Model for sendable data.
/// </summary>
[ObservableProperty]
private MultiFormatString sendableData = new();
#endregion
/// <summary>
/// Constructor.
/// </summary>
/// <param name="appSettings"></param>
public SendReceiveViewModel(IAppSettingsProvider appSettings, IMessenger messenger, IContext context) : base(appSettings, messenger, context) { }
public SendReceiveViewModel(IAppSettingsProvider appSettings, IMessenger messenger, IContext context) : base(appSettings, messenger)
{
// create new Communication data containers
this.receivedData = new(context);
this.sentData = new(context);
}
/// <summary>
/// Send command.
@ -39,4 +59,21 @@ public partial class SendReceiveViewModel : TerminalViewModel
this.SendableData.Clear();
}
}
public override void DisplayNewReceivedData(IEnumerable<ExtendedByte> newData)
{
this.ReceivedData!.HandleNewData(newData);
}
public override void DisplayNewSentData(IEnumerable<ExtendedByte> newData)
{
this.SentData!.HandleNewData(newData);
}
protected override void ActOnDataDisplayNewlineSeparatorTypeChanged(NewlineSeparatorType newType)
{
// triggers rearranging the displayed data with the new NewlineSeparatorType
this.ReceivedData!.NewlineSeparator = newType;
this.SentData!.NewlineSeparator = newType;
}
}

@ -18,7 +18,6 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
private const string defaultSendNewlineSeparatorAppSettingsKey = "DefaultSendNewlineSeparator";
private readonly IAppSettingsProvider appSettings;
private readonly IMessenger messenger;
private readonly IContext context;
public abstract TerminalViewType ViewType { get; }
@ -58,18 +57,6 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
#endregion
#region Observable Properties
/// <summary>
/// Holds communication data that was received via the communication protocol.
/// </summary>
[ObservableProperty]
private ICommunicationDataViewModel<ByteDataViewModel, ExtendedByte> receivedData;
/// <summary>
/// Holds communication data that was sent via the communication protocol.
/// </summary>
[ObservableProperty]
private ICommunicationDataViewModel<ByteDataViewModel, ExtendedByte> sentData;
/// <summary>
/// Defines at which newline sequence the displayed data is wrapped. Defaults to none.
/// </summary>
@ -105,15 +92,10 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
#endregion
public TerminalViewModel(IAppSettingsProvider appSettings, IMessenger messenger, IContext context)
public TerminalViewModel(IAppSettingsProvider appSettings, IMessenger messenger)
{
this.appSettings = appSettings;
this.messenger = messenger;
this.context = context;
// create new Communication data containers
this.receivedData = new CommunicationDataViewModel(this.context);
this.sentData = new CommunicationDataViewModel(this.context);
}
~TerminalViewModel()
@ -150,24 +132,25 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
this.InitializeNewlineSeparatorsFromAppSettings();
// update newline settings in data objects
this.ReceivedData.NewlineSeparator = this.DataDisplayNewlineSeparatorType;
this.SentData.NewlineSeparator = this.DataDisplayNewlineSeparatorType;
// TODO!!!
//this.ReceivedData.NewlineSeparator = this.DataDisplayNewlineSeparatorType;
//this.SentData.NewlineSeparator = this.DataDisplayNewlineSeparatorType;
}
public abstract void DisplayNewReceivedData(IEnumerable<ExtendedByte> newData);
private void CommunicationProtocol_ReceivedDataEvent(object? sender, ReceivedDataEventArgs e)
{
// guard null
if (this.ReceivedData == null) { throw new NullReferenceException($"{nameof(CommunicationProtocol_ReceivedDataEvent)} found {nameof(this.ReceivedData)} to be null."); }
// handover data
this.ReceivedData.HandleNewData(e.ReceivedData);
this.DisplayNewReceivedData(e.ReceivedData);
}
public abstract void DisplayNewSentData(IEnumerable<ExtendedByte> newData);
private void CommunicationProtocol_SentDataEvent(object? sender, SentDataEventArgs e)
{
// guard null
if (this.SentData == null) { throw new NullReferenceException($"{nameof(CommunicationProtocol_SentDataEvent)} found {nameof(this.SentData)} to be null."); }
// handover data
this.SentData.HandleNewData(e.SentData);
this.DisplayNewSentData(e.SentData);
}
private void CommunicationProtocol_ConnectionStateChangedEvent(object? sender, ProtocolConnectionState e)
@ -311,17 +294,11 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
catch { }
}
protected abstract void ActOnDataDisplayNewlineSeparatorTypeChanged(NewlineSeparatorType newType);
partial void OnDataDisplayNewlineSeparatorTypeChanged(NewlineSeparatorType value)
{
// triggers rearranging the displayed data with the new NewlineSeparatorType
if (this.ReceivedData != null)
{
this.ReceivedData.NewlineSeparator = value;
}
if (this.SentData != null)
{
this.SentData.NewlineSeparator = value;
}
{
this.ActOnDataDisplayNewlineSeparatorTypeChanged(value);
}
#endregion

@ -0,0 +1,33 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace MultiTerm.Wpf.CustomControl;
/// <summary>
/// Converts <see cref="bool"/> to <see cref="Visibility"/>.
/// If true = Visible, false = Colapsed
/// </summary>
[ValueConversion(typeof(bool), typeof(Visibility))]
internal class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is not bool boolVal) { throw new ArgumentException($"{nameof(BoolToVisibilityConverter)}.{nameof(Convert)} got type other than bool."); }
if (boolVal)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

@ -7,7 +7,6 @@ using System.Windows.Controls;
using System.Windows.Data;
using System.Linq;
using System.Collections.Specialized;
using MultiTerm.Protocols.Model;
using System.Collections.ObjectModel;
using System.Diagnostics;
@ -17,19 +16,28 @@ public class MultiFormatDataView : Control
{
private static readonly Dictionary<StackPanel, MultiFormatDataView> itemParentPairs = new();
private const string itemsControlTemplateName = "PART_ItemsControl";
private const string itemsTextBoxTemplateName = "PART_ItemsTextBox";
private const string buttonClearTemplateName = "PART_ButtonClear";
private const string textBoxCharOnlyViewTemplateName = "PART_CharOnlyTextBox";
private const string realizedItemsCountTemplateName = "PART_RealizedItemsCountDisplay";
private const string selectedAbsoluteTimeTextBlockTemplateName = "PART_SelectedAbsoluteTimeTextBlock";
private const string selectedTimediffTextBlockTemplateName = "PART_SelectedTimediffTextBlock";
private ListBox? itemsControl;
private TextBox? tbCharOnlyView;
private ICollectionView? collectionView;
private TextBox? itemsTextBox;
private TextBlock? selectedAbsoluteTimeTextBlock;
private TextBlock? selectedTimediffTextBlock;
#region Dependency Properties
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource",
typeof(ICommunicationDataViewModel<ByteDataViewModel, ExtendedByte>), typeof(MultiFormatDataView),
typeof(MultiFormatDataViewModel), typeof(MultiFormatDataView),
new PropertyMetadata(null, OnDataSourcePropertyChanged));
public static readonly DependencyProperty IsTimeDisplayedProperty =
DependencyProperty.Register("IsTimeDisplayed",
typeof(bool), typeof(MultiFormatDataView),
new PropertyMetadata(false));
public static readonly DependencyProperty RealizedItemsCountProperty =
DependencyProperty.Register("RealizedItemsCount",
typeof(uint), typeof(MultiFormatDataView),
@ -51,17 +59,27 @@ public class MultiFormatDataView : Control
/// <summary>
/// .NET Property for DataSourceProperty.
/// .NET Property for <see cref="DataSourceProperty"/>.
/// </summary>
[Bindable(true)]
public ICommunicationDataViewModel<ByteDataViewModel, ExtendedByte> DataSource
public MultiFormatDataViewModel DataSource
{
get { return (ICommunicationDataViewModel<ByteDataViewModel, ExtendedByte>)GetValue(DataSourceProperty); }
get { return (MultiFormatDataViewModel)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
/// <summary>
/// .NET Property for RealizedItemsCount.
/// .NET Property for <see cref="IsTimeDisplayedProperty"/>.
/// </summary>
[Bindable(true)]
public bool IsTimeDisplayed
{
get { return (bool)GetValue(IsTimeDisplayedProperty); }
set { SetValue(IsTimeDisplayedProperty, value); }
}
/// <summary>
/// .NET Property for <see cref="RealizedItemsCountProperty"/>.
/// </summary>
[Bindable(true)]
public uint RealizedItemsCount
@ -71,7 +89,7 @@ public class MultiFormatDataView : Control
}
/// <summary>
/// .NET Property for ItemLoaded.
/// .NET Property for <see cref="ItemLoadedProperty"/>.
/// </summary>
public bool ItemLoaded
{
@ -80,7 +98,7 @@ public class MultiFormatDataView : Control
}
/// <summary>
/// .NET Property for ItemUnloaded.
/// .NET Property for <see cref="RealizedItemsCountProperty"/>.
/// </summary>
public bool ItemUnloaded
{
@ -110,7 +128,6 @@ public class MultiFormatDataView : Control
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.Unloaded += MultiFormatDataView_Unloaded;
// get itemsControl from template
if (GetTemplateChild(itemsControlTemplateName) is ListBox listBox)
@ -123,19 +140,19 @@ public class MultiFormatDataView : Control
throw new Exception($"Implementation fault, {itemsControlTemplateName} not found in template.");
}
// get textBox from template
if (GetTemplateChild(textBoxCharOnlyViewTemplateName) is TextBox tb)
// get itemsTextBox from template
if (GetTemplateChild(itemsTextBoxTemplateName) is TextBox tb)
{
this.tbCharOnlyView = tb;
this.tbCharOnlyView.SelectionChanged += TextBoxCharOnlyView_SelectionChanged;
this.tbCharOnlyView.TextChanged += TextBoxCharOnlyView_TextChanged;
this.itemsTextBox = tb;
this.itemsTextBox.SelectionChanged += TextBoxCharOnlyView_SelectionChanged;
this.itemsTextBox.TextChanged += TextBoxCharOnlyView_TextChanged;
}
else
{
throw new Exception($"Implementation fault, {textBoxCharOnlyViewTemplateName} not found in template.");
throw new Exception($"Implementation fault, {itemsTextBoxTemplateName} not found in template.");
}
// get button from template, ignore if it does not exist
// register to button event
if (GetTemplateChild(buttonClearTemplateName) is Button button)
{
button.Click += OnClearButtonClicked; ;
@ -144,11 +161,21 @@ public class MultiFormatDataView : Control
// hide realized items count when not debugging
if (Debugger.IsAttached == false)
{
if(GetTemplateChild(realizedItemsCountTemplateName) is UIElement realizedItemsUiElement)
if (GetTemplateChild(realizedItemsCountTemplateName) is UIElement realizedItemsUiElement)
{
realizedItemsUiElement.Visibility = Visibility.Hidden;
}
}
// get time textblocks
if (GetTemplateChild(selectedAbsoluteTimeTextBlockTemplateName) is TextBlock satTb)
{
this.selectedAbsoluteTimeTextBlock = satTb;
}
if (GetTemplateChild(selectedTimediffTextBlockTemplateName) is TextBlock stdTb)
{
this.selectedTimediffTextBlock = stdTb;
}
}
private static void OnDataSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@ -180,16 +207,12 @@ public class MultiFormatDataView : Control
incc.CollectionChanged += mfdv.Data_CollectionChanged;
}
// create binding for textbox (DataAsString)
var binding = new Binding()
{
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = new PropertyPath($"{nameof(mfdv.DataSource)}.{nameof(mfdv.DataSource.DataAsString)}")
};
BindingOperations.SetBinding(mfdv.tbCharOnlyView, TextBox.TextProperty, binding);
// create bindings
CreateTemplateBinding($"{nameof(mfdv.DataSource)}.{nameof(mfdv.DataSource.DataAsString)}", mfdv.itemsTextBox, TextBox.TextProperty);
CreateTemplateBinding($"{nameof(mfdv.DataSource)}.{nameof(mfdv.DataSource.SelectedDataFirstAbsoluteTime)}", mfdv.selectedAbsoluteTimeTextBlock, TextBlock.TextProperty);
CreateTemplateBinding($"{nameof(mfdv.DataSource)}.{nameof(mfdv.DataSource.SelectedDataTimediff)}", mfdv.selectedTimediffTextBlock, TextBlock.TextProperty);
}
private void Data_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// TEMP scroll to added item if not null
@ -199,17 +222,6 @@ public class MultiFormatDataView : Control
//}
}
/// <summary>
/// On Unload remove event handler.
/// </summary>
private void MultiFormatDataView_Unloaded(object sender, RoutedEventArgs e)
{
if (DataSource is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= Data_CollectionChanged;
}
}
private void OnClearButtonClicked(object sender, RoutedEventArgs e)
{
// raise clear requested event
@ -220,41 +232,42 @@ public class MultiFormatDataView : Control
#region Selected Items handling
private void ItemsControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// if there are no changes => return
if (e.AddedItems.Count <= 0 && e.RemovedItems.Count <= 0) { return; }
// get sender as listbox
if(e.OriginalSource is not ListBox senderListBox) { throw new ArgumentException($"{nameof(ItemsControl_SelectionChanged)} got non expected type as sender {sender}"); }
// if there is something selected in the textbox => clear selection first
if (this.tbCharOnlyView != null && this.tbCharOnlyView.SelectionLength > 0) { this.tbCharOnlyView.Select(0, 0); }
if (this.itemsTextBox != null && this.itemsTextBox.SelectionLength > 0) { this.itemsTextBox.Select(0, 0); }
// otherwise update internal list
foreach (ByteDataViewModel item in e.RemovedItems)
{
this.DataSource.Selected.Remove(item);
}
foreach (ByteDataViewModel item in e.AddedItems)
{
this.DataSource.Selected.Add(item);
}
// get selected items from list box
var selectedItems = senderListBox.SelectedItems.OfType<ByteDataViewModel>().ToList();
// sort items according to time (uses IComparable of ByteDataViewModel)
// wpf does some odd sorting. described here: https://stackoverflow.com/questions/50155415/wpf-listview-selecteditems-returns-wrong-item-order-if-you-shift-select-from-b
// sorting the items by ticks provides a solution in this case
selectedItems.Sort();
// genereate new observable collection
this.DataSource.Selected = new ObservableCollection<ByteDataViewModel>(selectedItems);
}
private void TextBoxCharOnlyView_SelectionChanged(object sender, RoutedEventArgs e)
{
var newSelection = new ObservableCollection<ByteDataViewModel>();
int selectionStartIndex = this.tbCharOnlyView!.SelectionStart;
int selectionStartIndex = this.itemsTextBox!.SelectionStart;
// extract text from the beginning to the start of the selected text
var textFromBeginningToStartOfSelection = this.tbCharOnlyView!.Text.Substring(0, selectionStartIndex);
var textFromBeginningToStartOfSelection = this.itemsTextBox!.Text.Substring(0, selectionStartIndex);
// count amount of manually introduced newline sequences in this text section (these to not exist in the data source!)
var foundManuallyIntroducedNewlineSequenceCharacters = 0;
var foundAmountOfLines = textFromBeginningToStartOfSelection.Split(CommunicationDataViewModel.NewlineSequence).Length;
var foundAmountOfLines = textFromBeginningToStartOfSelection.Split(MultiFormatDataViewModel.NewlineSequence).Length;
// any newline sequences introduced (more than one lines found)
if (foundAmountOfLines > 1)
{
// calculated amount of characters that were introduced
foundManuallyIntroducedNewlineSequenceCharacters = (foundAmountOfLines - 1) * CommunicationDataViewModel.NewlineSequence.Length;
foundManuallyIntroducedNewlineSequenceCharacters = (foundAmountOfLines - 1) * MultiFormatDataViewModel.NewlineSequence.Length;
}
// iterate through length of selection
for (int i = 0; i < this.tbCharOnlyView!.SelectionLength; i++)
for (int i = 0; i < this.itemsTextBox!.SelectionLength; i++)
{
// subtracting the counted newline sequences and adding i (length)
int elementPositionInCollection = selectionStartIndex - foundManuallyIntroducedNewlineSequenceCharacters + i;
@ -273,7 +286,7 @@ public class MultiFormatDataView : Control
private void TextBoxCharOnlyView_TextChanged(object sender, TextChangedEventArgs e)
{
this.tbCharOnlyView?.ScrollToEnd();
this.itemsTextBox?.ScrollToEnd();
}
#endregion
@ -320,4 +333,23 @@ public class MultiFormatDataView : Control
}
}
#endregion
#region Helpers
private static void CreateTemplateBinding(string propertyPath, DependencyObject? dependencyObject, DependencyProperty dependencyProperty)
{
// ignore when dependency object is null
if(dependencyObject == null) { return; }
// create
var binding = new Binding()
{
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = new PropertyPath(propertyPath)
};
// bind
BindingOperations.SetBinding(dependencyObject, dependencyProperty, binding);
}
#endregion
}

@ -18,6 +18,9 @@
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="ExtraLight"/>
</Style>
<!-- Value Converters -->
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<!-- Data Template for Data Container -->
<DataTemplate x:Key="dataContainerTemplate" DataType="vm:IDataViewModel">
@ -127,8 +130,8 @@
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MultiFormatDataView}">
<DockPanel>
<!-- Top row with options and buttons -->
<GroupBox Header="Display Formats" DockPanel.Dock="Top">
<!-- Top row with options and buttons -->
<DockPanel LastChildFill="False" Margin="5 0">
<!-- Format selectors -->
<CheckBox DockPanel.Dock="Left" Content="Character" VerticalAlignment="Center" VerticalContentAlignment="Center" x:Name="cbCharacter" Padding="10 0 30 0" IsChecked="True"/>
@ -138,6 +141,18 @@
<Button DockPanel.Dock="Right" x:Name="PART_ButtonClear" Width="Auto" Padding="7 2" Content="Clear" />
</DockPanel>
</GroupBox>
<!-- Bottom row with Time display -->
<GroupBox DockPanel.Dock="Bottom"
BorderThickness="0"
Visibility="{ TemplateBinding IsTimeDisplayed,
Converter={StaticResource ResourceKey=BoolToVisibilityConverter}}">
<DockPanel LastChildFill="False" Margin="0">
<TextBlock DockPanel.Dock="Left" x:Name="PART_SelectedAbsoluteTimeTextBlock" />
<TextBlock DockPanel.Dock="Right" x:Name="PART_SelectedTimediffTextBlock" />
</DockPanel>
</GroupBox>
<!-- Grid that contains main control and a realited items count on top -->
<Grid>
<Grid.RowDefinitions>
@ -149,11 +164,11 @@
</Grid.ColumnDefinitions>
<!-- Simple Text View -->
<TextBox Name="PART_CharOnlyTextBox"
<TextBox Name="PART_ItemsTextBox"
Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
IsReadOnly="True"
TextWrapping="Wrap"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="true"
ScrollViewer.PanningMode="VerticalOnly">

@ -78,7 +78,7 @@
<!-- Data View -->
<GroupBox Header="Receive" Grid.Row="2" Padding="5">
<!-- Received Data View -->
<custom_controls:MultiFormatDataView x:Name="mfdvReceive" DataSource="{Binding ReceivedData}">
<custom_controls:MultiFormatDataView x:Name="mfdvReceive" DataSource="{Binding ReceivedData}" IsTimeDisplayed="True">
<XamlBehavioursWpf:Interaction.Triggers>
<XamlBehavioursWpf:EventTrigger EventName="ClearRequested" SourceObject="{Binding ElementName=mfdvReceive}">
@ -113,7 +113,7 @@
<Separator/>
<!-- Sent Data View -->
<custom_controls:MultiFormatDataView x:Name="mfdvSend" DataSource="{Binding SentData}">
<custom_controls:MultiFormatDataView x:Name="mfdvSend" DataSource="{Binding SentData}" IsTimeDisplayed="False">
<XamlBehavioursWpf:Interaction.Triggers>
<XamlBehavioursWpf:EventTrigger EventName="ClearRequested" SourceObject="{Binding ElementName=mfdvSend}">

Loading…
Cancel
Save