diff --git a/MultiTerm.Protocols/Model/ExtendedChar.cs b/MultiTerm.Protocols/Model/ExtendedChar.cs index 87ee369..70aa18a 100644 --- a/MultiTerm.Protocols/Model/ExtendedChar.cs +++ b/MultiTerm.Protocols/Model/ExtendedChar.cs @@ -49,23 +49,29 @@ public partial class ExtendedChar { string characterString; - if (char.IsWhiteSpace(this.Character)) + // character is ASCII encoded and is a control sequence + if (char.IsAscii(this.Character) && this.Character <= '\x001F') { - characterString = this.Character switch - { - '\t' => "\\t", - ' ' => " ", - '\n' => "\\n", - '\r' => "\\r", - '\v' => "\\v", - '\f' => "\\f", - _ => this.Character.ToString() - }; - } - else if (char.IsControl(this.Character)) - { - characterString = ""; + // conver to unicode Control Picture (see https://en.wikipedia.org/wiki/Control_Pictures) + characterString = ((char)('\u2400' + this.Character)).ToString(); + + // TODO Remove + //characterString = this.Character switch + //{ + // '\t' => "\\t", + // ' ' => " ", + // '\n' => "\u240A", + // '\r' => "\u240D", + // '\v' => "\\v", + // '\f' => "\\f", + // _ => this.Character.ToString() + //}; } + // TODO Remove + //else if (char.IsControl(this.Character)) + //{ + // characterString = ""; + //} else { characterString = this.Character.ToString(); diff --git a/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs b/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs index ed36590..c4c772a 100644 --- a/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs +++ b/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs @@ -6,6 +6,9 @@ using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; +using System.Linq; +using MultiTerm.Wpf.CustomControl.ValueConverter; +using System.Diagnostics.Metrics; namespace MultiTerm.Wpf.CustomControl; @@ -15,7 +18,9 @@ public class MultiFormatDataView : Control private const string itemsControlTemplateName = "itemsControl"; private const string buttonClearTemplateName = "btnClear"; private const string selectorTemplateName = "comboBoxSelector"; + private const string textBoxCharOnlyViewTemplateName = "textBoxCharactersOnlyView"; private ListBox? itemsControl; + private TextBox? tbCharOnlyView; #region Dependency Properties public static readonly DependencyProperty DataSourceProperty = @@ -183,22 +188,62 @@ public class MultiFormatDataView : Control { button.Click += OnClearButtonClicked; ; } + + // get textBox from template, ignore if it does not exist + if (GetTemplateChild(textBoxCharOnlyViewTemplateName) is TextBox tb) + { + this.tbCharOnlyView = tb; + this.tbCharOnlyView.SelectionChanged += TextBoxCharOnlyView_SelectionChanged; + } } + private static void OnDataSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // extract instance and guard null if (d is not MultiFormatDataView mfdv) { return; } - // extract instance of new Value and guard null - if (e.NewValue is not IEnumerable enumerable) { return; } - // check if enumerable items are all of correct type - foreach (var item in enumerable) + // extract instance of new Value and check if correct type + if (e.NewValue is not IEnumerable newDataSource) { - if (item is not DataViewModel) - { throw new ArgumentException($"{nameof(DataSourceProperty)} must be of type {nameof(DataViewModel)}"); } + throw new ArgumentException($"{nameof(MultiFormatDataView)}: {nameof(DataSourceProperty)} must be of type {nameof(IEnumerable)}"); } - // add group property + // 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++) + //{ + // 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; + // } + //} + + // 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); @@ -217,6 +262,9 @@ public class MultiFormatDataView : Control // if there are no changes => return if (e.AddedItems.Count <= 0 && e.RemovedItems.Count <= 0) { return; } + // if there is something selected in the textbox => clear selection first + if (this.tbCharOnlyView != null && this.tbCharOnlyView.SelectionLength > 0) { this.tbCharOnlyView.Select(0, 0); } + // otherwise update internal list foreach (DataViewModel item in e.RemovedItems) { @@ -228,6 +276,35 @@ public class MultiFormatDataView : Control } } + private void TextBoxCharOnlyView_SelectionChanged(object sender, RoutedEventArgs e) + { + var newSelection = new List(); + int selectionStartIndex = this.tbCharOnlyView!.SelectionStart; + // extract text from the beginning to the start of the selected text + 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)this.DataSource); + + // iterate through length of selection + for (int i = 0; i < this.tbCharOnlyView!.SelectionLength; i++) + { + // 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)); + // next item does not exist => break loop + if(collection.ElementAtOrDefault(elementPositionInCollection + 1) == null) + { + break; + } + } + + // update property + this.SelectedItems = newSelection; + } + private static void OnSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // NOP diff --git a/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml b/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml index 13fe103..c0b3a63 100644 --- a/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml +++ b/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml @@ -3,13 +3,19 @@ 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"> + - + + + + + + + + +