diff --git a/MultiTerm.Core/Helpers/ByteDataViewModelHelpers.cs b/MultiTerm.Core/Helpers/ByteDataViewModelHelpers.cs new file mode 100644 index 0000000..2d7836f --- /dev/null +++ b/MultiTerm.Core/Helpers/ByteDataViewModelHelpers.cs @@ -0,0 +1,78 @@ +using MultiTerm.Core.ViewModel; +using System.Text; + +namespace MultiTerm.Core.Helpers; + +public static class ByteDataViewModelHelpers +{ + public static string GetCharacterStringOfBytesDataViewModels(IEnumerable collection) + { + StringBuilder stringBuilder = new StringBuilder(); + int prevLineIdentifier = collection.First().LineIdentifier; + + foreach (var item in collection) + { + // add newline if line identifier increased + if(item.LineIdentifier > prevLineIdentifier) + { + stringBuilder.Append(Environment.NewLine); + } + prevLineIdentifier = item.LineIdentifier; + // add item as character + stringBuilder.Append(item.DisplayStringChar); + } + return stringBuilder.ToString(); + } + + public static string GetHexadecimalStringOfBytesDataViewModels(IEnumerable collection) + { + StringBuilder stringBuilder = new StringBuilder(); + int prevLineIdentifier = collection.First().LineIdentifier; + + foreach (var item in collection) + { + // add newline if line identifier increased + if (item.LineIdentifier > prevLineIdentifier) + { + stringBuilder.Append(Environment.NewLine); + } + prevLineIdentifier = item.LineIdentifier; + + // add item as hex byte + stringBuilder.Append(item.DisplayStringHex); + + // add spacing if it is not the last item + if (item != collection.Last()) + { + stringBuilder.Append(' '); + } + } + return stringBuilder.ToString(); + } + + public static string GetBinaryStringOfEBytesDataViewModels(IEnumerable collection) + { + StringBuilder stringBuilder = new StringBuilder(); + int prevLineIdentifier = collection.First().LineIdentifier; + + foreach (var item in collection) + { + // add newline if line identifier increased + if (item.LineIdentifier > prevLineIdentifier) + { + stringBuilder.Append(Environment.NewLine); + } + prevLineIdentifier = item.LineIdentifier; + + // add item as binary byte + stringBuilder.Append(item.DisplayStringBin); + + // add spacing if it is not the last item + if (item != collection.Last()) + { + stringBuilder.Append(' '); + } + } + return stringBuilder.ToString(); + } +} diff --git a/MultiTerm.Core/ViewModel/ByteDataViewModel.cs b/MultiTerm.Core/ViewModel/ByteDataViewModel.cs index 02a6ee7..3b4334c 100644 --- a/MultiTerm.Core/ViewModel/ByteDataViewModel.cs +++ b/MultiTerm.Core/ViewModel/ByteDataViewModel.cs @@ -1,15 +1,14 @@ using CommunityToolkit.Mvvm.ComponentModel; using MultiTerm.Protocols.Model; -using System.Text; namespace MultiTerm.Core.ViewModel; -public partial class ByteDataViewModel : ObservableObject, IDataViewModel, IComparable +public partial class ByteDataViewModel : ObservableObject, IDataViewModel, IComparable, ICloneable { /// - /// Object of data model. + /// Object of data model. /// - private readonly ExtendedByte data; + internal readonly ExtendedByte Data; #region IDataViewModel Implementation @@ -30,11 +29,6 @@ public partial class ByteDataViewModel : ObservableObject, IDataViewModel, IComp #endregion - /// - /// Allows access to the low level data byte. - /// - public byte Byte => this.data.Byte; - /// /// Instanciates new . /// Initializes internal variables with data according to . @@ -43,7 +37,7 @@ public partial class ByteDataViewModel : ObservableObject, IDataViewModel, IComp /// identifies line in which this byte is located public ByteDataViewModel(ExtendedByte extendedByte, int lineIdentifier) { - this.data = extendedByte; + this.Data = extendedByte; this.lineIdentifier = lineIdentifier; this.DisplayStringChar = extendedByte.ToCharacterString(); this.DisplayStringHex = extendedByte.ToHexString(); @@ -71,4 +65,11 @@ public partial class ByteDataViewModel : ObservableObject, IDataViewModel, IComp } #endregion + + #region ICloneable Implementation + public object Clone() + { + return this.MemberwiseClone(); + } + #endregion } diff --git a/MultiTerm.Core/ViewModel/MultiFormatDataViewModel.cs b/MultiTerm.Core/ViewModel/MultiFormatDataViewModel.cs index 1055366..1d9c017 100644 --- a/MultiTerm.Core/ViewModel/MultiFormatDataViewModel.cs +++ b/MultiTerm.Core/ViewModel/MultiFormatDataViewModel.cs @@ -2,6 +2,7 @@ using Common.Helpers; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using MultiTerm.Core.Helpers; using MultiTerm.Core.Types; using MultiTerm.Protocols.Model; using System.Collections.ObjectModel; @@ -149,6 +150,26 @@ public partial class MultiFormatDataViewModel : ObservableObject } } + public string GetSelectedDataAsString(FormatType format) + { + // nothing selected => return empty string + if(this.Selected == null || this.Selected.Count == 0) { return string.Empty; } + + // shallow copy of the selected items + var selectedCopy = this.Selected.Select(item => (ByteDataViewModel)item.Clone()).ToList(); + + string dataAsString = string.Empty; + dataAsString = format switch + { + FormatType.Character => ByteDataViewModelHelpers.GetCharacterStringOfBytesDataViewModels(selectedCopy), + FormatType.Hexadecimal => ByteDataViewModelHelpers.GetHexadecimalStringOfBytesDataViewModels(selectedCopy), + FormatType.Binary => ByteDataViewModelHelpers.GetBinaryStringOfEBytesDataViewModels(selectedCopy), + _ => throw new ArgumentException($"'{GetSelectedDataAsString}()' does not have handling implemented for {nameof(FormatType)} {format}") + }; + + return dataAsString; + } + #region Collection Manipulation /// /// Function that reorders a given collection with item type using the given . @@ -173,7 +194,7 @@ public partial class MultiFormatDataViewModel : ObservableObject newCollection.Add(item); stringBuilder.Append(item.DisplayStringChar); - switch (ShouldIntroduceNewlineAfterThisByte(item.Byte, previousBytes, newlineSeparatorType)) + switch (ShouldIntroduceNewlineAfterThisByte(item.Data.Byte, previousBytes, newlineSeparatorType)) { case ShouldIntroduceNewlineAfterThisByteResult.NoNewline: // a decision could be made, previousBytes can be cleared @@ -194,7 +215,7 @@ public partial class MultiFormatDataViewModel : ObservableObject // first time that more bytes are required => create new list previousBytes ??= new List(); // add current byte to list - previousBytes.Add(item.Byte); + previousBytes.Add(item.Data.Byte); break; default: diff --git a/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs b/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs index 9ffd4f6..57542c5 100644 --- a/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs +++ b/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Collections.Specialized; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Windows.Input; namespace MultiTerm.Wpf.CustomControl; @@ -57,6 +58,9 @@ public class MultiFormatDataView : Control public static readonly RoutedEvent ClearRequestedEvent; + public static readonly DependencyProperty CopyCommandProperty = + DependencyProperty.Register("CopyCommand", + typeof(RoutedCommand), typeof(MultiFormatDataView)); /// /// .NET Property for . @@ -114,8 +118,29 @@ public class MultiFormatDataView : Control add { this.AddHandler(ClearRequestedEvent, value); } remove { this.RemoveHandler(ClearRequestedEvent, value); } } + + private static RoutedCommand? copyCommand; + /// + /// .NET Property for + /// + public static RoutedCommand? CopyCommand + { + get { return copyCommand; } + } #endregion + public MultiFormatDataView() + { + // register copy command + copyCommand = new RoutedCommand("CopyCommand", typeof(MultiFormatDataView)); + var binding = new CommandBinding + { + Command = copyCommand + }; + binding.Executed += CopyCommand_Executed; ; + CommandBindings.Add(binding); + } + static MultiFormatDataView() { DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiFormatDataView), new FrameworkPropertyMetadata(typeof(MultiFormatDataView))); @@ -229,6 +254,24 @@ public class MultiFormatDataView : Control RaiseEvent(args); } + private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e) + { + switch (e.Parameter.ToString()) + { + case "Character": + Clipboard.SetText(this.DataSource.GetSelectedDataAsString(Core.Types.FormatType.Character)); + break; + case "Hexadecimal": + Clipboard.SetText(this.DataSource.GetSelectedDataAsString(Core.Types.FormatType.Hexadecimal)); + break; + case "Binary": + Clipboard.SetText(this.DataSource.GetSelectedDataAsString(Core.Types.FormatType.Binary)); + break; + default: + throw new ArgumentException($"'{CopyCommand_Executed}()' does not have handling implemented for CommandParameter={e.Parameter}"); + } + } + #region Selected Items handling private void ItemsControl_SelectionChanged(object sender, SelectionChangedEventArgs e) { diff --git a/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml b/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml index 31388f7..c15b344 100644 --- a/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml +++ b/MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.xaml @@ -21,6 +21,13 @@ + + + + + + + @@ -162,7 +169,7 @@ - + + ScrollViewer.PanningMode="VerticalOnly" + ContextMenu="{StaticResource CopyDifferentFormatsContextMenu}"> @@ -189,7 +197,6 @@ - @@ -203,7 +210,8 @@ VirtualizingPanel.CacheLengthUnit="Item" VirtualizingPanel.VirtualizationMode="Standard" ItemTemplate="{StaticResource dataContainerTemplate}" - SelectionMode="Extended"> + SelectionMode="Extended" + ContextMenu="{StaticResource CopyDifferentFormatsContextMenu}">