introduced TextBox in MultiFormatDataView to improve performance when only Characters are displayed,

changed newline characters display to unicode control symbols
master
Jonas Arnold 3 years ago
parent 268b23bef4
commit 9ad7ae3430
  1. 36
      MultiTerm.Protocols/Model/ExtendedChar.cs
  2. 91
      MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs
  3. 52
      MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml
  4. 62
      MultiTerm.Wpf.CustomControl/ValueConverter/DataViewModelToStringConverter.cs

@ -49,23 +49,29 @@ public partial class ExtendedChar
{ {
string characterString; 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 // conver to unicode Control Picture (see https://en.wikipedia.org/wiki/Control_Pictures)
{ characterString = ((char)('\u2400' + this.Character)).ToString();
'\t' => "\\t",
' ' => " ", // TODO Remove
'\n' => "\\n", //characterString = this.Character switch
'\r' => "\\r", //{
'\v' => "\\v", // '\t' => "\\t",
'\f' => "\\f", // ' ' => " ",
_ => this.Character.ToString() // '\n' => "\u240A",
}; // '\r' => "\u240D",
} // '\v' => "\\v",
else if (char.IsControl(this.Character)) // '\f' => "\\f",
{ // _ => this.Character.ToString()
characterString = "<CTRL>"; //};
} }
// TODO Remove
//else if (char.IsControl(this.Character))
//{
// characterString = "<CTRL>";
//}
else else
{ {
characterString = this.Character.ToString(); characterString = this.Character.ToString();

@ -6,6 +6,9 @@ using System.ComponentModel;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data; using System.Windows.Data;
using System.Linq;
using MultiTerm.Wpf.CustomControl.ValueConverter;
using System.Diagnostics.Metrics;
namespace MultiTerm.Wpf.CustomControl; namespace MultiTerm.Wpf.CustomControl;
@ -15,7 +18,9 @@ public class MultiFormatDataView : Control
private const string itemsControlTemplateName = "itemsControl"; private const string itemsControlTemplateName = "itemsControl";
private const string buttonClearTemplateName = "btnClear"; private const string buttonClearTemplateName = "btnClear";
private const string selectorTemplateName = "comboBoxSelector"; private const string selectorTemplateName = "comboBoxSelector";
private const string textBoxCharOnlyViewTemplateName = "textBoxCharactersOnlyView";
private ListBox? itemsControl; private ListBox? itemsControl;
private TextBox? tbCharOnlyView;
#region Dependency Properties #region Dependency Properties
public static readonly DependencyProperty DataSourceProperty = public static readonly DependencyProperty DataSourceProperty =
@ -183,22 +188,62 @@ public class MultiFormatDataView : Control
{ {
button.Click += OnClearButtonClicked; ; 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) private static void OnDataSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
// extract instance and guard null // extract instance and guard null
if (d is not MultiFormatDataView mfdv) { return; } 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 // extract instance of new Value and check if correct type
foreach (var item in enumerable) if (e.NewValue is not IEnumerable<DataViewModel> newDataSource)
{ {
if (item is not DataViewModel) throw new ArgumentException($"{nameof(MultiFormatDataView)}: {nameof(DataSourceProperty)} must be of type {nameof(IEnumerable<DataViewModel>)}");
{ throw new ArgumentException($"{nameof(DataSourceProperty)} must be of type {nameof(DataViewModel)}"); }
} }
// 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); CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(e.NewValue);
PropertyGroupDescription groupDescription = new(nameof(DataViewModel.LineIdentifier)); PropertyGroupDescription groupDescription = new(nameof(DataViewModel.LineIdentifier));
view.GroupDescriptions.Add(groupDescription); view.GroupDescriptions.Add(groupDescription);
@ -217,6 +262,9 @@ public class MultiFormatDataView : Control
// if there are no changes => return // if there are no changes => return
if (e.AddedItems.Count <= 0 && e.RemovedItems.Count <= 0) { 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 // otherwise update internal list
foreach (DataViewModel item in e.RemovedItems) 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<DataViewModel>();
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<DataViewModel>)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) private static void OnSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
// NOP // NOP

@ -3,13 +3,19 @@
xmlns:local="clr-namespace:MultiTerm.Wpf.CustomControl" xmlns:local="clr-namespace:MultiTerm.Wpf.CustomControl"
xmlns:vm="clr-namespace:MultiTerm.Core.ViewModel;assembly=MultiTerm.Core" xmlns:vm="clr-namespace:MultiTerm.Core.ViewModel;assembly=MultiTerm.Core"
xmlns:wpftk="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel" xmlns:wpftk="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
xmlns:conv="clr-namespace:MultiTerm.Wpf.CustomControl.ValueConverter"
xmlns:sys="clr-namespace:System;assembly=mscorlib"> xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style TargetType="{x:Type local:MultiFormatDataView}"> <Style TargetType="{x:Type local:MultiFormatDataView}">
<Style.Resources> <Style.Resources>
<!-- Brushes -->
<SolidColorBrush x:Key="CHAR_Background" Color="#B4FFFF"/> <SolidColorBrush x:Key="CHAR_Background" Color="#B4FFFF"/>
<SolidColorBrush x:Key="HEX_Background" Color="#C8C8FF"/> <SolidColorBrush x:Key="HEX_Background" Color="#C8C8FF"/>
<SolidColorBrush x:Key="BIN_Background" Color="#B4FFB4"/> <SolidColorBrush x:Key="BIN_Background" Color="#B4FFB4"/>
<!-- Value Converters -->
<conv:DataViewModelToStringConverter x:Key="DataViewModelToStringConverter"/>
<DataTemplate x:Key="dataContainerTemplate" DataType="vm:DataViewModel"> <DataTemplate x:Key="dataContainerTemplate" DataType="vm:DataViewModel">
<DataTemplate.Resources> <DataTemplate.Resources>
<!-- Styling for all labels (data items) --> <!-- Styling for all labels (data items) -->
@ -152,8 +158,37 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Simple Text View -->
<TextBox Name="textBoxCharactersOnlyView"
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}}"-->
<!-- Show only when other checkboxes than "Characters" is checked -->
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbCharacter,Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=cbBin,Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=cbHex,Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<!-- Main Items Control --> </TextBox>
<!-- MultiFormat Items Control -->
<ListView Name="itemsControl" <ListView Name="itemsControl"
Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
ItemsSource="{TemplateBinding DataSource}" ItemsSource="{TemplateBinding DataSource}"
@ -167,6 +202,21 @@
ItemTemplate="{StaticResource dataContainerTemplate}" ItemTemplate="{StaticResource dataContainerTemplate}"
SelectionMode="Extended"> SelectionMode="Extended">
<!-- Hide when only "Characters" checkbox is checked -->
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbBin,Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=cbHex,Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<!-- TEMP possible starting point for multiple boxes per item --> <!-- TEMP possible starting point for multiple boxes per item -->
<!--<ListView.ItemTemplate> <!--<ListView.ItemTemplate>
<DataTemplate> <DataTemplate>

@ -0,0 +1,62 @@
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"); }
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();
}
}
Loading…
Cancel
Save