completely overhauled CommunicationDataViewModel,

clearly defined data structure with interfaces,
implemented MultiFormatDataView so that it cooperates with a CommunicationDataViewModel,
implemented changes in SendReceiveView and TerminalViewModel,
removed DataViewModelToStringConverter since it is not used anymore
master
Jonas Arnold 3 years ago
parent 236111f94e
commit 4524a975b8
  1. 35
      MultiTerm.Core/ViewModel/CharacterDataViewModel.cs
  2. 289
      MultiTerm.Core/ViewModel/CommunicationDataViewModel.cs
  3. 35
      MultiTerm.Core/ViewModel/ICommunicationDataViewModel.cs
  4. 30
      MultiTerm.Core/ViewModel/IDataViewModel.cs
  5. 14
      MultiTerm.Core/ViewModel/SendReceiveViewModel.cs
  6. 135
      MultiTerm.Core/ViewModel/TerminalViewModel.cs
  7. 169
      MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs
  8. 34
      MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml
  9. 65
      MultiTerm.Wpf.CustomControl/ValueConverter/DataViewModelToStringConverter.cs
  10. 13
      MultiTerm.Wpf/View/SendReceiveView.xaml

@ -3,45 +3,44 @@ using MultiTerm.Protocols.Model;
namespace MultiTerm.Core.ViewModel;
public partial class DataViewModel : ObservableObject
public partial class CharacterDataViewModel : ObservableObject, IDataViewModel
{
/// <summary>
/// Identifier for the frontend to group lines.
/// Object of data model.
/// </summary>
private readonly ExtendedChar character;
#region IDataViewModel Implementation
[ObservableProperty]
private int lineIdentifier;
/// <summary>
/// Object of data model.
/// </summary>
[ObservableProperty]
private ExtendedChar character;
private TimeOnly time;
/// <summary>
/// Property that hosts a displayable string of the character (UTF-16 encoded).
/// </summary>
[ObservableProperty]
private string displayStringUtf16 = String.Empty;
private string displayString = String.Empty;
/// <summary>
/// Property that hosts a string of the character (hexadecimal format).
/// </summary>
[ObservableProperty]
private string displayStringHex = String.Empty;
/// <summary>
/// Property that hosts a string of the character (binary format).
/// </summary>
[ObservableProperty]
private string displayStringBin = String.Empty;
#endregion
/// <summary>
/// Allows access to character of this instance.
/// </summary>
public char Character { get { return this.character.Character; } }
public DataViewModel(ExtendedChar character, int lineIdentifier)
public CharacterDataViewModel(ExtendedChar character, int lineIdentifier)
{
this.character = character;
this.lineIdentifier = lineIdentifier;
this.DisplayStringUtf16 = character.ToUtf16String();
this.DisplayString = character.ToUtf16String();
this.DisplayStringHex = character.ToHexString();
this.DisplayStringBin = character.ToBinaryString();
this.Time = character.Time;
}
}

@ -1,146 +1,111 @@
using Common;
using Common.Helpers;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MultiTerm.Core.Types;
using MultiTerm.Protocols;
using MultiTerm.Protocols.Model;
using System.Collections.ObjectModel;
namespace MultiTerm.Core.ViewModel;
public partial class CommunicationDataViewModel : ObservableObject
public partial class CommunicationDataViewModel : ObservableObject, ICommunicationDataViewModel<CharacterDataViewModel, ExtendedChar>
{
private ICommunicationProtocol? communicationProtocol;
private readonly IContext uiContext;
private NewlineSeparatorType currentReceiveNewlineSeparatorType = NewlineSeparatorType.None;
private NewlineSeparatorType currentSentNewlineSeparatorType = NewlineSeparatorType.None;
/// <summary>
/// Represents the collection of received characters from a communication protocol.
/// </summary>
private int dataCharacterCount = 0;
private List<char>? listOfPreviousCharacters = null;
#region ICommunicationDataViewModel Implementation
[ObservableProperty]
private ObservableCollection<DataViewModel> receivedData = new();
private ObservableCollection<CharacterDataViewModel> data = new();
/// <summary>
/// Represents the collection of the selection of received characters.
/// </summary>
[ObservableProperty]
private List<DataViewModel>? selectedReceivedData;
private string dataAsString = string.Empty;
/// <summary>
/// Represents the collection of sent characters to a communication protocol.
/// </summary>
[ObservableProperty]
private ObservableCollection<DataViewModel> sentData = new();
private ObservableCollection<CharacterDataViewModel>? selected = new();
/// <summary>
/// Represents the collection of the selection of sent characters.
/// </summary>
[ObservableProperty]
private List<DataViewModel>? selectedSentData;
NewlineSeparatorType newlineSeparator = NewlineSeparatorType.None;
#endregion
public CommunicationDataViewModel(ICommunicationProtocol? communicationProtocol, IContext context)
public CommunicationDataViewModel(IContext context)
{
this.uiContext = context;
this.communicationProtocol = communicationProtocol;
if (this.communicationProtocol != null)
{
this.communicationProtocol.ReceivedDataEvent += CommunicationProtocol_ReceivedDataEvent;
this.communicationProtocol.SentDataEvent += CommunicationProtocol_SentDataEvent;
}
else // 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 character in exampleData)
{
if(++counter > 100 || character == '\n')
{
//this.ReceivedData.Add();
//listOfChars = new List<ExtendedChar>();
counter = 0;
lineNumber += 1;
}
var extdChar = new ExtendedChar(character);
//listOfChars.Add(extdChar);
this.ReceivedData.Add(new DataViewModel(extdChar, lineNumber));
}
}
// 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 character in exampleData)
// {
// if(++counter > 100 || character == '\n')
// {
// //this.ReceivedData.Add();
// //listOfChars = new List<ExtendedChar>();
// counter = 0;
// lineNumber += 1;
// }
// var extdChar = new ExtendedChar(character);
// //listOfChars.Add(extdChar);
// this.ReceivedData.Add(new DataViewModel(extdChar, lineNumber));
// }
//}
}
~CommunicationDataViewModel()
public void HandleNewData(IEnumerable<ExtendedChar> newRawData)
{
if (this.communicationProtocol != null)
// update collection and string, invoke ui thread if necessary
ContextHelpers.InvokeIfNecessary(this.uiContext, (Action)delegate
{
this.communicationProtocol.ReceivedDataEvent -= CommunicationProtocol_ReceivedDataEvent;
this.communicationProtocol.SentDataEvent -= CommunicationProtocol_SentDataEvent;
this.communicationProtocol = null;
}
InsertNewNewCharactersIntoCollection(this.Data, ref this.dataCharacterCount, ref this.listOfPreviousCharacters, this.NewlineSeparator, newRawData);
this.DataAsString = InsertNewCharactersIntoString(this.DataAsString, newRawData);
});
}
/// <summary>
/// Overwrites local variables for current newline separators and triggers recration of datacollections according to new newline separators (only if they changed).
/// After changing the newline separator, the data needs to be reordered.
/// </summary>
/// <param name="receiveNewlineSeparatorType">new receive newline separator type or null</param>
/// <param name="sendNewlineSeparatorType">new send newline separator type or null</param>
public void ConfigureNewlineSeparators(NewlineSeparatorType? receiveNewlineSeparatorType, NewlineSeparatorType? sendNewlineSeparatorType)
partial void OnNewlineSeparatorChanged(NewlineSeparatorType value)
{
if (receiveNewlineSeparatorType != null)
{
// if it is the same as the current newline separator type => ignore
if(receiveNewlineSeparatorType == this.currentReceiveNewlineSeparatorType) { return; }
// set new newline separator type and overwrite data collection (triggers property changed)
this.currentReceiveNewlineSeparatorType = (NewlineSeparatorType)receiveNewlineSeparatorType;
// update collection, invoke ui thread if necessary
ContextHelpers.InvokeIfNecessary(this.uiContext, (Action)delegate
{
this.ReceivedData = ReorderDataCollection(this.ReceivedData, this.currentReceiveNewlineSeparatorType);
this.Data = ReorderCollection(this.Data, value);
this.DataAsString = ReorderString(this.Data, value);
});
}
if (sendNewlineSeparatorType != null)
{
// if it is the same as the current newline separator type => ignore
if (sendNewlineSeparatorType == this.currentSentNewlineSeparatorType) { return; }
// set new newline separator type and overwrite data collection (triggers property changed)
this.currentSentNewlineSeparatorType = (NewlineSeparatorType)sendNewlineSeparatorType;
// update collection, invoke ui thread if necessary
ContextHelpers.InvokeIfNecessary(this.uiContext, (Action)delegate
{
this.SentData = ReorderDataCollection(this.SentData, this.currentSentNewlineSeparatorType);
});
}
}
#region Collection Manipulation
/// <summary>
/// Function that reorders a given collection using the given <paramref name="newlineSeparatorType"/>.
/// Function that reorders a given collection with item type <see cref="CharacterDataViewModel"/> using the given <paramref name="newlineSeparatorType"/>.
/// Reordered collection is returned, but LineIdentifier is also overwritten in the <paramref name="currentCollection"/> parameter.
/// </summary>
/// <param name="currentCollection">items in this collection will be reordered</param>
/// <param name="newlineSeparatorType">separator between lines</param>
/// <returns>reordered collection</returns>
private static ObservableCollection<DataViewModel> ReorderDataCollection(ObservableCollection<DataViewModel> currentCollection, NewlineSeparatorType newlineSeparatorType)
private static ObservableCollection<CharacterDataViewModel> ReorderCollection(ObservableCollection<CharacterDataViewModel> currentCollection, NewlineSeparatorType newlineSeparatorType)
{
ObservableCollection<DataViewModel> newCollection = new();
ObservableCollection<CharacterDataViewModel> newCollection = new();
int lineCounter = 0;
List<char>? previousCharacters = null;
foreach (var item in currentCollection)
// iterate through items
foreach (CharacterDataViewModel item in currentCollection)
{
item.LineIdentifier = lineCounter;
newCollection.Add(item);
switch (ShouldIntroduceNewlineAfterThisCharacter(item.Character.Character, previousCharacters, newlineSeparatorType))
switch (ShouldIntroduceNewlineAfterThisCharacter(item.Character, previousCharacters, newlineSeparatorType))
{
case IntroduceNewlineAfterThisCharacterResult.NoNewline:
// a decision could be made, previousCharacters can be cleared
if(previousCharacters != null) { previousCharacters = null; }
if (previousCharacters != null) { previousCharacters = null; }
// nothing to do
break;
@ -155,17 +120,69 @@ public partial class CommunicationDataViewModel : ObservableObject
// first time that more characters are required => create new list
previousCharacters ??= new List<char>();
// add current character to list
previousCharacters.Add(item.Character.Character);
previousCharacters.Add(item.Character);
break;
default:
throw new Exception($"'{nameof(ReorderDataCollection)}()' failed because of error when checking if a newline should be introduced.");
throw new Exception($"'{nameof(ReorderCollection)}()' failed because of error when checking if a newline should be introduced.");
}
}
return newCollection;
}
/// <summary>
/// Function that handles a collection of new characters that should end up in the collection <paramref name="dataCollection"/>.
/// In case a new line is required, according to the given <paramref name="newlineSeparatorType"/>, it is automatically introduced.
/// Following parameters need to be referenced and stored outside: <paramref name="collectionLineCounter"/> and <paramref name="previousCharacters"/>.
/// </summary>
/// <param name="dataCollection">collection to add the characters to</param>
/// <param name="collectionLineCounter">current line count</param>
/// <param name="previousCharacters">list of previous character, newest at the last position of the list, null if nothing is stored</param>
/// <param name="newlineSeparatorType">separator between seperate lines</param>
/// <param name="newCharacters">characters to add to the <paramref name="dataCollection"/></param>
/// <exception cref="Exception">in case of any error</exception>
private static void InsertNewNewCharactersIntoCollection(ObservableCollection<CharacterDataViewModel> dataCollection,
ref int collectionLineCounter,
ref List<char>? previousCharacters,
NewlineSeparatorType newlineSeparatorType,
IEnumerable<ExtendedChar> newCharacters)
{
// go through every character
foreach (var newExtdChar in newCharacters)
{
// add to collection with the current counter, invoking UI context if necssary
var currentLineCounter = collectionLineCounter;
dataCollection.Add(new CharacterDataViewModel(newExtdChar, currentLineCounter));
switch (ShouldIntroduceNewlineAfterThisCharacter(newExtdChar.Character, previousCharacters, newlineSeparatorType))
{
case IntroduceNewlineAfterThisCharacterResult.NoNewline:
// a decision could be made, previousCharacters can be cleared
if (previousCharacters != null) { previousCharacters = null; }
// nothing to do
break;
case IntroduceNewlineAfterThisCharacterResult.IntroduceNewline:
// a decision could be made, previousCharacters can be cleared
if (previousCharacters != null) { previousCharacters = null; }
// increase line count and break
collectionLineCounter++;
break;
case IntroduceNewlineAfterThisCharacterResult.RequiresMoreCharacters:
// first time that more characters are required => create new list
previousCharacters ??= new List<char>();
// add current character to list
previousCharacters.Add(newExtdChar.Character);
break;
default:
throw new Exception($"'{nameof(InsertNewNewCharactersIntoCollection)}()' failed because of error when checking if a newline should be introduced.");
}
}
}
/// <summary>
/// Result type for <see cref="ShouldIntroduceNewlineAfterThisCharacter"/>
/// </summary>
@ -206,7 +223,7 @@ public partial class CommunicationDataViewModel : ObservableObject
break;
case NewlineSeparatorType.CR:
if(character == '\r')
if (character == '\r')
{
result = IntroduceNewlineAfterThisCharacterResult.IntroduceNewline;
}
@ -226,7 +243,7 @@ public partial class CommunicationDataViewModel : ObservableObject
}
if (character == '\n')
{
if(previousCharacters != null && previousCharacters.Last() == '\r')
if (previousCharacters != null && previousCharacters.Last() == '\r')
{
result = IntroduceNewlineAfterThisCharacterResult.IntroduceNewline;
}
@ -240,85 +257,29 @@ public partial class CommunicationDataViewModel : ObservableObject
return result;
}
private int receivedDataCharacterCount = 0;
private List<char>? listOfPreviouslyReceivedCharacters = null;
private void CommunicationProtocol_ReceivedDataEvent(object? sender, ReceivedDataEventArgs e)
{
HandleNewCharacters(this.ReceivedData, ref this.receivedDataCharacterCount, ref this.listOfPreviouslyReceivedCharacters, this.currentReceiveNewlineSeparatorType, e.ReceivedCharacters);
}
private int sentDataCharacterCount = 0;
private List<char>? listOfPreviouslySentCharacters = null;
private void CommunicationProtocol_SentDataEvent(object? sender, SentDataEventArgs e)
{
HandleNewCharacters(this.SentData, ref this.sentDataCharacterCount, ref this.listOfPreviouslySentCharacters, this.currentSentNewlineSeparatorType, e.SentCharacters);
}
#endregion
/// <summary>
/// Function that handles a list of new characters that should end up in the collection <paramref name="dataCollection"/>.
/// In case a new line is required, according to the given <paramref name="newlineSeparatorType"/>, it is automatically introduced.
/// Following parameters need to be referenced and stored outside: <paramref name="collectionLineCounter"/> and <paramref name="previousCharacters"/>.
/// </summary>
/// <param name="dataCollection">collection to add the characters to</param>
/// <param name="collectionLineCounter">current line count</param>
/// <param name="previousCharacters">list of previous character, newest at the last position of the list, null if nothing is stored</param>
/// <param name="newlineSeparatorType">separator between seperate lines</param>
/// <param name="characters">characters to add to the <paramref name="dataCollection"/></param>
/// <exception cref="Exception">in case of any error</exception>
private void HandleNewCharacters(ObservableCollection<DataViewModel> dataCollection,
ref int collectionLineCounter,
ref List<char>? previousCharacters,
NewlineSeparatorType newlineSeparatorType,
IEnumerable<ExtendedChar> characters)
{
// go through every character
foreach (var newExtdChar in characters)
{
// add to collection with the current counter, invoking UI context if necssary
var currentLineCounter = collectionLineCounter;
ContextHelpers.InvokeIfNecessary(this.uiContext, (Action)delegate
{
dataCollection.Add(new DataViewModel(newExtdChar, currentLineCounter));
});
#region String Manipulation
switch (ShouldIntroduceNewlineAfterThisCharacter(newExtdChar.Character, previousCharacters, newlineSeparatorType))
// TODO Implement Line Handling
private static string InsertNewCharactersIntoString(string dataAsString, IEnumerable<ExtendedChar> newRawData)
{
case IntroduceNewlineAfterThisCharacterResult.NoNewline:
// a decision could be made, previousCharacters can be cleared
if (previousCharacters != null) { previousCharacters = null; }
// nothing to do
break;
string newDataAsString = dataAsString;
case IntroduceNewlineAfterThisCharacterResult.IntroduceNewline:
// a decision could be made, previousCharacters can be cleared
if (previousCharacters != null) { previousCharacters = null; }
// increase line count and break
collectionLineCounter++;
break;
case IntroduceNewlineAfterThisCharacterResult.RequiresMoreCharacters:
// first time that more characters are required => create new list
previousCharacters ??= new List<char>();
// add current character to list
previousCharacters.Add(newExtdChar.Character);
break;
default:
throw new Exception($"'{nameof(HandleNewCharacters)}()' failed because of error when checking if a newline should be introduced.");
}
}
foreach (ExtendedChar character in newRawData)
{
newDataAsString += character.Character;
}
[RelayCommand]
private void ClearReceivedData()
{
this.ReceivedData = new ObservableCollection<DataViewModel>();
return newDataAsString;
}
[RelayCommand]
private void ClearSentData()
private static string ReorderString(ObservableCollection<CharacterDataViewModel> collection, NewlineSeparatorType value)
{
this.SentData = new ObservableCollection<DataViewModel>();
// Not yet implemented
return "Not implemented";
}
#endregion
}

@ -0,0 +1,35 @@
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);
}

@ -0,0 +1,30 @@
namespace MultiTerm.Core.ViewModel;
public interface IDataViewModel
{
/// <summary>
/// Allows frontent to group items by line identifier.
/// Can be set, e.g. to reorder items.
/// </summary>
int LineIdentifier { get; set; }
/// <summary>
/// Time when this data was produced.
/// </summary>
TimeOnly Time { get; }
/// <summary>
/// Hosts a displayable string of the character (UTF-16 encoded).
/// </summary>
string DisplayString { get; }
/// <summary>
/// Hosts a string of the character (hexadecimal format).
/// </summary>
string DisplayStringHex { get; }
/// <summary>
/// Hosts a string of the character (binary format).
/// </summary>
string DisplayStringBin { get; }
}

@ -17,13 +17,13 @@ public partial class SendReceiveViewModel : TerminalViewModel
/// Send data model.
/// </summary>
[ObservableProperty]
private MultiFormatString sendData = new();
private MultiFormatString sendableData = new();
/// <summary>
/// Temporary sent data property, for testing purposes.
/// </summary>
[ObservableProperty]
private string sentData = string.Empty;
private string tempSentDataString = string.Empty;
/// <summary>
/// Constructor.
@ -38,13 +38,13 @@ public partial class SendReceiveViewModel : TerminalViewModel
private void Send()
{
// Temp
var items = this.CommunicationData.SelectedReceivedData;
Debugger.Break();
this.SentData = this.SendData.ToString();
//var items = this.CommunicationData.SelectedReceivedData;
//Debugger.Break();
this.TempSentDataString = this.SendableData.ToString();
// send data
this.SendToCommunicationProtocol(this.SendData.ToString());
this.SendToCommunicationProtocol(this.SendableData.ToString());
// clear textbox
this.SendData = new MultiFormatString();
this.SendableData = new MultiFormatString();
}
}

@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Core.Types;
using MultiTerm.Protocols;
using MultiTerm.Protocols.Model;
using MultiTerm.Protocols.Types;
using System.Text;
@ -58,66 +59,97 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
#endregion
/// <summary>
/// Holds communication data, meaning data that was sent to or received over the communication protocol.
/// Holds communication data that was received via the communication protocol.
/// </summary>
[ObservableProperty]
private CommunicationDataViewModel? communicationData;
private ICommunicationDataViewModel<CharacterDataViewModel, ExtendedChar> receivedData;
/// <summary>
/// Newline Separator Type that is selected for receival data of this Terminal. Defaults to none.
/// Holds communication data that was sent via the communication protocol.
/// </summary>
[ObservableProperty]
private NewlineSeparatorType receiveNewlineSeparatorType = NewlineSeparatorType.None;
private ICommunicationDataViewModel<CharacterDataViewModel, ExtendedChar> sentData;
/// <summary>
/// Newline Separator Type that is selected for sent out data of this Terminal. Defaults to none.
/// Defines at which newline sequence the displayed data is wrapped. Defaults to none.
/// </summary>
[ObservableProperty]
private NewlineSeparatorType dataDisplayNewlineSeparatorType = NewlineSeparatorType.None;
/// <summary>
/// Defines which Newline sequence is attached to data when it is sent. Defaults to none.
/// </summary>
[ObservableProperty]
private NewlineSeparatorType sendNewlineSeparatorType = NewlineSeparatorType.None;
[ObservableProperty]
private ICommunicationProtocol? communicationProtocol;
public ICommunicationProtocol? CommunicationProtocol
{
get { return this.communicationProtocol; }
set
{
// store communication protocol
this.communicationProtocol = value;
// register communication protocol in the Communication Data View Model
this.CommunicationData = new CommunicationDataViewModel(this.communicationProtocol, this.context);
[ObservableProperty]
private IProtocolSettingsViewModel? protocolSettings;
// initializes default newline separators, requires communicationData to be not null
this.InitializeNewlineSeparatorsFromAppSettings();
// initialize both newline Separators. initializes with null if they are not available.
this.CommunicationData.ConfigureNewlineSeparators(this.ReceiveNewlineSeparatorType, this.SendNewlineSeparatorType);
public TerminalViewModel(IAppSettingsProvider appSettings, IMessenger messenger, IContext context)
{
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()
{
if (this.CommunicationProtocol != null)
{
this.CommunicationProtocol.ReceivedDataEvent -= CommunicationProtocol_ReceivedDataEvent;
this.CommunicationProtocol.SentDataEvent -= CommunicationProtocol_SentDataEvent;
this.CommunicationProtocol = null;
}
}
private IProtocolSettingsViewModel? protocolSettings;
public IProtocolSettingsViewModel? ProtocolSettings
#region Communication Protocol
partial void OnCommunicationProtocolChanged(ICommunicationProtocol? value)
{
get { return this.protocolSettings; }
set
this.InitializeCommunicationProtocol();
}
private void InitializeCommunicationProtocol()
{
this.protocolSettings = value;
// register event handler for connection request from viewmodel
if(value != null)
if (this.CommunicationProtocol != null)
{
this.protocolSettings!.ConnectRequested += OnViewModelRequestedConnect;
this.protocolSettings!.DisconnectRequested += OnViewModelRequestedDisconnect;
this.CommunicationProtocol.ReceivedDataEvent += CommunicationProtocol_ReceivedDataEvent;
this.CommunicationProtocol.SentDataEvent += CommunicationProtocol_SentDataEvent;
}
// initializes default newline separators, updates them directly inside ReceivedData and SentData objects
this.InitializeNewlineSeparatorsFromAppSettings();
// update newline settings in data objects
this.ReceivedData.NewlineSeparator = this.DataDisplayNewlineSeparatorType;
this.SentData.NewlineSeparator = this.DataDisplayNewlineSeparatorType;
}
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.ReceivedCharacters);
}
public TerminalViewModel(IAppSettingsProvider appSettings, IMessenger messenger, IContext context)
private void CommunicationProtocol_SentDataEvent(object? sender, SentDataEventArgs e)
{
this.appSettings = appSettings;
this.messenger = messenger;
this.context = context;
// 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.SentCharacters);
}
#endregion
/// <summary>
/// Sends the given string to this instances <see cref="CommunicationProtocol"/>, UTF-16 encoded.
/// Appends configured <see cref="SendNewlineSeparatorType"/> to the end of string.
@ -125,7 +157,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
/// <param name="data">data string</param>
protected void SendToCommunicationProtocol(string data)
{
// guard communication protocol null
// guard null values
if(this.CommunicationProtocol == null) { throw new NullReferenceException($"'{nameof(SendToCommunicationProtocol)}()' was called but {nameof(CommunicationProtocol)} is null."); }
// inform user and quit if communication protocol is not connected
@ -154,6 +186,18 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
this.CommunicationProtocol.SendBytes(byteArray);
}
#region Protocol Settings
partial void OnProtocolSettingsChanged(IProtocolSettingsViewModel? value)
{
// register event handler for connection request from viewmodel
if (value != null)
{
this.ProtocolSettings!.ConnectRequested += OnViewModelRequestedConnect;
this.ProtocolSettings!.DisconnectRequested += OnViewModelRequestedDisconnect;
}
}
#endregion
#region Connect/Disconnect
private void OnViewModelRequestedConnect(object? sender, ConnectionRequestEventArgs e)
{
@ -196,22 +240,20 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
#region Newline Separator handling
/// <summary>
/// Gets the default settings for both newline separators from the AppSettings Provider.
/// Writes them to the local variables <see cref="ReceiveNewlineSeparatorType"/> and <see cref="SendNewlineSeparatorType"/>.
/// Writes them to the local variables <see cref="DataDisplayNewlineSeparatorType"/> and <see cref="SendNewlineSeparatorType"/>.
/// In case there are no default settings, it does not overwrite the local variables.
/// In case the Communication Data is null, nothing is done.
/// </summary>
private void InitializeNewlineSeparatorsFromAppSettings()
{
// guard null objects
if(this.CommunicationData == null) { return; }
// get newline separators from persistent settings, if not available catch exceptions
// get newline separators from persistent settings, if not available catch exceptions. apply to displayed data variable.
this.appSettings.TryReadSetting(defaultReceiveNewlineSeparatorAppSettingsKey, out string settingValueReceiveNLSep);
try
{
this.ReceiveNewlineSeparatorType = EnumHelpers.ParseEnum<NewlineSeparatorType>(settingValueReceiveNLSep);
this.DataDisplayNewlineSeparatorType = EnumHelpers.ParseEnum<NewlineSeparatorType>(settingValueReceiveNLSep);
}
catch { }
// apply to send newline separator
this.appSettings.TryReadSetting(defaultSendNewlineSeparatorAppSettingsKey, out string settingValueSendNLSep);
try
{
@ -220,16 +262,17 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
catch { }
}
partial void OnReceiveNewlineSeparatorTypeChanged(NewlineSeparatorType value)
partial void OnDataDisplayNewlineSeparatorTypeChanged(NewlineSeparatorType value)
{
// triggers rearranging the received data with the new NewlineSeparatorType
this.CommunicationData?.ConfigureNewlineSeparators(value, null);
// triggers rearranging the displayed data with the new NewlineSeparatorType
if (this.ReceivedData != null)
{
this.ReceivedData.NewlineSeparator = value;
}
partial void OnSendNewlineSeparatorTypeChanged(NewlineSeparatorType value)
if (this.SentData != null)
{
// triggers rearranging the sent data with the new NewlineSeparatorType
this.CommunicationData?.ConfigureNewlineSeparators(null, value);
this.SentData.NewlineSeparator = value;
}
}
#endregion

@ -7,35 +7,29 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Linq;
using MultiTerm.Wpf.CustomControl.ValueConverter;
using System.Diagnostics.Metrics;
using System.Collections.Specialized;
using MultiTerm.Protocols.Model;
using System.Collections.ObjectModel;
namespace MultiTerm.Wpf.CustomControl;
public class MultiFormatDataView : Control
{
private static readonly Dictionary<StackPanel, MultiFormatDataView> itemParentPairs = new();
private const string itemsControlTemplateName = "itemsControl";
private const string itemsControlTemplateName = "PART_ItemsControl";
private const string buttonClearTemplateName = "btnClear";
private const string selectorTemplateName = "comboBoxSelector";
private const string textBoxCharOnlyViewTemplateName = "textBoxCharactersOnlyView";
private const string textBoxCharOnlyViewTemplateName = "PART_CharOnlyTextBox";
private ListBox? itemsControl;
private TextBox? tbCharOnlyView;
private ICollectionView? collectionView;
#region Dependency Properties
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource",
typeof(IEnumerable), typeof(MultiFormatDataView),
typeof(ICommunicationDataViewModel<CharacterDataViewModel, ExtendedChar>), typeof(MultiFormatDataView),
new PropertyMetadata(null, OnDataSourcePropertyChanged));
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems",
typeof(IList), typeof(MultiFormatDataView),
new FrameworkPropertyMetadata(OnSelectedItemsPropertyChanged)
{
BindsTwoWayByDefault = false
});
public static readonly DependencyProperty SelectorItemsSourceProperty =
DependencyProperty.Register("SelectorItemsSource",
typeof(IEnumerable), typeof(MultiFormatDataView),
@ -70,24 +64,15 @@ public class MultiFormatDataView : Control
public static readonly RoutedEvent ClearRequestedEvent;
/// <summary>
/// .NET Property for DataSource.
/// </summary>
[Bindable(true)]
public IEnumerable DataSource
{
get { return (IEnumerable)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
/// <summary>
/// .NET Property for SelectedItems.
/// .NET Property for DataSourceProperty.
/// </summary>
[Bindable(true)]
public IList SelectedItems
public ICommunicationDataViewModel<CharacterDataViewModel, ExtendedChar> DataSource
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
get { return (ICommunicationDataViewModel<CharacterDataViewModel, ExtendedChar>)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
/// <summary>
@ -170,7 +155,7 @@ public class MultiFormatDataView : Control
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectedItems = new List<DataViewModel>();
this.Unloaded += MultiFormatDataView_Unloaded;
// get itemsControl from template
if (GetTemplateChild(itemsControlTemplateName) is ListBox listBox)
@ -183,17 +168,22 @@ public class MultiFormatDataView : Control
throw new Exception($"Implementation fault, {itemsControlTemplateName} not found in template.");
}
// get button from template, ignore if it does not exist
if (GetTemplateChild(buttonClearTemplateName) is Button button)
{
button.Click += OnClearButtonClicked; ;
}
// get textBox from template, ignore if it does not exist
// get textBox from template
if (GetTemplateChild(textBoxCharOnlyViewTemplateName) is TextBox tb)
{
this.tbCharOnlyView = tb;
this.tbCharOnlyView.SelectionChanged += TextBoxCharOnlyView_SelectionChanged;
this.tbCharOnlyView.TextChanged += TextBoxCharOnlyView_TextChanged;
}
else
{
throw new Exception($"Implementation fault, {textBoxCharOnlyViewTemplateName} not found in template.");
}
// get button from template, ignore if it does not exist
if (GetTemplateChild(buttonClearTemplateName) is Button button)
{
button.Click += OnClearButtonClicked; ;
}
}
@ -202,58 +192,62 @@ public class MultiFormatDataView : Control
// extract instance and guard null
if (d is not MultiFormatDataView mfdv) { return; }
// extract instance of new Value and check if correct type
if (e.NewValue is not IEnumerable<DataViewModel> newDataSource)
// manually create collection view
ICollectionView cv = CollectionViewSource.GetDefaultView(mfdv.DataSource.Data);
// add grouping
PropertyGroupDescription groupDescription = new(nameof(CharacterDataViewModel.LineIdentifier));
cv.GroupDescriptions.Add(groupDescription);
// add live grouping
if (cv is ICollectionViewLiveShaping cvLiveShaping && cvLiveShaping.CanChangeLiveGrouping)
{
throw new ArgumentException($"{nameof(MultiFormatDataView)}: {nameof(DataSourceProperty)} must be of type {nameof(IEnumerable<DataViewModel>)}");
cvLiveShaping.LiveGroupingProperties.Add(nameof(CharacterDataViewModel.LineIdentifier));
cvLiveShaping.IsLiveGrouping = true;
}
// save collection view
mfdv.collectionView = cv;
// apply collection view as itemssource
mfdv.itemsControl!.ItemsSource = cv;
// register to collection changed event
if (mfdv.DataSource.Data is INotifyCollectionChanged incc)
{
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);
}
// TODO REMOVE
//// validate that no characters were removed
//if(oldDataSource != null && oldDataSource.Count() > newDataSource.Count())
//{
// throw new NotImplementedException($"{nameof(MultiFormatDataView)} cannot handle removing single items from DataSource. " +
// $"Only adding and clearing ({nameof(DataSourceProperty)} = null) are supported.");
//}
//// iterate through data, adding content to textbox
//int prevCounter = newDataSource.First().LineIdentifier;
//for(int i = 0; i < newDataSource.Count(); i++)
private void Data_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// TEMP scroll to added item if not null
//if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && e.NewItems[0] != null)
//{
// DataViewModel item = newDataSource.ElementAt(i);
// DataViewModel? oldItem = oldDataSource?.ElementAtOrDefault(i);
// // if old item at this position exists and it equals the new item => skip
// if(oldItem != null && oldItem.Equals(item))
// {
// continue;
// }
// // new item found: add it as content to textbox
// if (mfdv.tbCharOnlyView != null)
// {
// // newline if previous counter is lower than this
// if (item.LineIdentifier > prevCounter)
// {
// mfdv.tbCharOnlyView.Text += "\n";
// prevCounter = item.LineIdentifier;
// }
// // add character (text)
// mfdv.tbCharOnlyView.Text += item.DisplayStringUtf16;
// }
// this.itemsControl!.ScrollIntoView(e.NewItems[0]);
//}
}
// add group property to support grouping of VirtualizingWrapPanel
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(e.NewValue);
PropertyGroupDescription groupDescription = new(nameof(DataViewModel.LineIdentifier));
view.GroupDescriptions.Add(groupDescription);
/// <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
RoutedEventArgs args = new(ClearRequestedEvent);
RaiseEvent(args);
this.DataSource.Data.Clear();
}
#region Selected Items handling
@ -266,26 +260,25 @@ public class MultiFormatDataView : Control
if (this.tbCharOnlyView != null && this.tbCharOnlyView.SelectionLength > 0) { this.tbCharOnlyView.Select(0, 0); }
// otherwise update internal list
foreach (DataViewModel item in e.RemovedItems)
foreach (CharacterDataViewModel item in e.RemovedItems)
{
this.SelectedItems.Remove(item);
this.DataSource.Selected.Remove(item);
}
foreach (DataViewModel item in e.AddedItems)
foreach (CharacterDataViewModel item in e.AddedItems)
{
this.SelectedItems.Add(item);
this.DataSource.Selected.Add(item);
}
}
private void TextBoxCharOnlyView_SelectionChanged(object sender, RoutedEventArgs e)
{
var newSelection = new List<DataViewModel>();
var newSelection = new ObservableCollection<CharacterDataViewModel>();
int selectionStartIndex = this.tbCharOnlyView!.SelectionStart;
// TEMP OLD
// extract text from the beginning to the start of the selected text
var textFromBeginningToStartOfSelection = this.tbCharOnlyView!.Text.Substring(0, selectionStartIndex);
// var textFromBeginningToStartOfSelection = this.tbCharOnlyView!.Text.Substring(0, selectionStartIndex);
// count amount of manually introduced newline sequences in this text section (these to not exist in the data source!)
var foundManuallyIntroducedNewlineSequences = textFromBeginningToStartOfSelection.Count((x) => x == DataViewModelToStringConverter.NewlineSequence);
// convert datasource
var collection = ((IEnumerable<DataViewModel>)this.DataSource);
var foundManuallyIntroducedNewlineSequences = 0; // = textFromBeginningToStartOfSelection.Count((x) => x == DataViewModelToStringConverter.NewlineSequence);
// iterate through length of selection
for (int i = 0; i < this.tbCharOnlyView!.SelectionLength; i++)
@ -293,21 +286,21 @@ public class MultiFormatDataView : Control
// subtracting the counted newline sequences and adding i (length)
int elementPositionInCollection = selectionStartIndex - foundManuallyIntroducedNewlineSequences + i;
// add element to new selection list
newSelection.Add(collection.ElementAt(elementPositionInCollection));
newSelection.Add(this.DataSource.Data.ElementAt(elementPositionInCollection));
// next item does not exist => break loop
if(collection.ElementAtOrDefault(elementPositionInCollection + 1) == null)
if(this.DataSource.Data.ElementAtOrDefault(elementPositionInCollection + 1) == null)
{
break;
}
}
// update property
this.SelectedItems = newSelection;
this.DataSource.Selected = newSelection;
}
private static void OnSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
private void TextBoxCharOnlyView_TextChanged(object sender, TextChangedEventArgs e)
{
// NOP
this.tbCharOnlyView?.ScrollToEnd();
}
#endregion

@ -3,7 +3,6 @@
xmlns:local="clr-namespace:MultiTerm.Wpf.CustomControl"
xmlns:vm="clr-namespace:MultiTerm.Core.ViewModel;assembly=MultiTerm.Core"
xmlns:wpftk="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
xmlns:conv="clr-namespace:MultiTerm.Wpf.CustomControl.ValueConverter"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style TargetType="{x:Type local:MultiFormatDataView}">
@ -13,10 +12,9 @@
<SolidColorBrush x:Key="HEX_Background" Color="#C8C8FF"/>
<SolidColorBrush x:Key="BIN_Background" Color="#B4FFB4"/>
<!-- Value Converters -->
<conv:DataViewModelToStringConverter x:Key="DataViewModelToStringConverter"/>
<!-- n/a -->
<DataTemplate x:Key="dataContainerTemplate" DataType="vm:DataViewModel">
<DataTemplate x:Key="dataContainerTemplate" DataType="vm:IDataViewModel">
<DataTemplate.Resources>
<!-- Styling for all labels (data items) -->
<Style TargetType="Label">
@ -58,7 +56,7 @@
</StackPanel.Triggers>
<!-- Character -->
<Border BorderThickness="0">
<Label Content="{Binding DisplayStringUtf16}" Padding="0" Margin="2 0" VerticalAlignment="Center">
<Label Content="{Binding DisplayString}" Padding="0" Margin="2 0" VerticalAlignment="Center">
<Label.Style>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>
@ -159,13 +157,12 @@
</Grid.ColumnDefinitions>
<!-- Simple Text View -->
<TextBox Name="textBoxCharactersOnlyView"
<TextBox Name="PART_CharOnlyTextBox"
Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
IsReadOnly="True"
Text="{Binding Path=DataSource, RelativeSource={RelativeSource TemplatedParent},
Mode=OneWay, UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource ResourceKey=DataViewModelToStringConverter}}">
<!--Text="{TemplateBinding DataSource, Converter={StaticResource ResourceKey=DataViewModelToStringConverter}}"-->
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="true"
ScrollViewer.PanningMode="VerticalOnly">
<!-- Show only when other checkboxes than "Characters" is checked -->
<TextBox.Style>
@ -187,15 +184,13 @@
</TextBox>
<!-- MultiFormat Items Control -->
<ListView Name="itemsControl"
<ListView Name="PART_ItemsControl"
Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
ItemsSource="{TemplateBinding DataSource}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="true"
ScrollViewer.PanningMode="VerticalOnly"
HorizontalAlignment="Left"
HorizontalAlignment="Stretch"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.CacheLengthUnit="Item"
VirtualizingPanel.VirtualizationMode="Standard"
@ -217,17 +212,6 @@
</Style>
</ListView.Style>
<!-- TEMP possible starting point for multiple boxes per item -->
<!--<ListView.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1" IsItemsHost="True">
<UniformGrid.Style>
<StaticResource ResourceKey="dataContainerTemplate"/>
</UniformGrid.Style>
</UniformGrid>
</DataTemplate>
</ListView.ItemTemplate>-->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />

@ -1,65 +0,0 @@
using MultiTerm.Core.ViewModel;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace MultiTerm.Wpf.CustomControl.ValueConverter;
[ValueConversion(typeof(IEnumerable<DataViewModel>), typeof(string))]
public class DataViewModelToStringConverter : IValueConverter
{
/// <summary>
/// Newline Sequence that is introduced whenever a new line begins in the DataViewModel.
/// </summary>
public static readonly char NewlineSequence = '\n';
/// <summary>
/// Convert from IEnumerable<DataViewModel> to string
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// guard null
if(value is null) { return string.Empty; }
// guard type
if (value is not IEnumerable<DataViewModel> enumerable) { throw new ArgumentException($"Can only convert from {nameof(IEnumerable<DataViewModel>)} value"); }
// guard empty enumerable
if (enumerable.Count() == 0) { return string.Empty; }
string returnString = string.Empty;
// iterate through data, adding content to textbox
int prevCounter = enumerable.First().LineIdentifier;
for (int i = 0; i < enumerable.Count(); i++)
{
DataViewModel item = enumerable.ElementAt(i);
// newline if previous counter is lower than this
if (item.LineIdentifier > prevCounter)
{
returnString += NewlineSequence;
prevCounter = item.LineIdentifier;
}
// add character (text)
returnString += item.DisplayStringUtf16;
}
return returnString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

@ -50,19 +50,18 @@
</GroupBox>
<GroupBox Header="Receive" Grid.Row="1" Padding="5">
<custom_controls:MultiFormatDataView x:Name="mfdvReceive"
DataSource="{Binding Path=CommunicationData.ReceivedData, Mode=OneWay}"
SelectedItems="{Binding Path=CommunicationData.SelectedReceivedData, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
DataSource="{Binding ReceivedData}"
SelectorItemsSource="{Binding Source={StaticResource NewlineSeparatorTypeValues}, Converter={StaticResource EnumValToDescConverter}}"
SelectorSelectedItem="{Binding Path=ReceiveNewlineSeparatorType, Mode=TwoWay,
SelectorSelectedItem="{Binding Path=ReceivedData.NewlineSeparator, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource EnumValToDescConverter}}"
SelectorDescription="Newline Separator">
<behaviors:Interaction.Triggers>
<!--<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="ClearRequested" SourceObject="{Binding ElementName=mfdvReceive}">
<behaviors:InvokeCommandAction Command="{Binding CommunicationData.ClearReceivedDataCommand}" />
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
</behaviors:Interaction.Triggers>-->
</custom_controls:MultiFormatDataView>
</GroupBox>
@ -71,10 +70,10 @@
<!-- Send text box -->
<DockPanel DockPanel.Dock="Top" LastChildFill="True">
<Button DockPanel.Dock="Right" Content="Send" Command="{Binding SendCommand}"/>
<custom_controls:MultiFormatTextBox DockPanel.Dock="Left" CurrentMultiFormatString="{Binding Path=SendData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></custom_controls:MultiFormatTextBox>
<custom_controls:MultiFormatTextBox DockPanel.Dock="Left" CurrentMultiFormatString="{Binding Path=SendableData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></custom_controls:MultiFormatTextBox>
</DockPanel>
<Separator/>
<TextBox DockPanel.Dock="Bottom" Text="{Binding SentData}">
<TextBox DockPanel.Dock="Bottom" Text="{Binding TempSentDataString}">
</TextBox>
</DockPanel>

Loading…
Cancel
Save