Format seperated into seperate file,

worked on MultiFormatTextBox,
added SendData to SendReceiveViewModel
master
Jonas Arnold 3 years ago
parent 90ee6d2fa0
commit c87e20c871
  1. 35
      MultiTerm.Core/Model/MultiFormatString.cs
  2. 19
      MultiTerm.Core/Types/FormatType.cs
  3. 10
      MultiTerm.Core/ViewModel/SendReceiveViewModel.cs
  4. 55
      MultiTerm.Wpf.CustomControl/MultiFormatTextBox/Format.cs
  5. 260
      MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs
  6. 4
      MultiTerm.Wpf.CustomControl/MultiTerm.Wpf.CustomControl.csproj
  7. 2
      MultiTerm.Wpf/View/SendReceiveView.xaml

@ -0,0 +1,35 @@
using MultiTerm.Core.Types;
namespace MultiTerm.Core.Model;
public class MultiFormatString
{
public List<Tuple<FormatType, string>> FormatValuePairs { get; private set; } = new();
public void Add(FormatType format, string value)
{
// TODO check if value is valid
this.FormatValuePairs.Add(Tuple.Create(format, value));
}
public void Remove(int amount)
{
// guard amount <= 0
if (amount <= 0) throw new ArgumentException($"{nameof(amount)} cannot be <= 0");
// limit amount to maximum
int listCount = this.FormatValuePairs.Count;
if (amount > listCount)
{
amount = listCount;
}
// remove range
this.FormatValuePairs.RemoveRange( (listCount - amount) , amount);
}
public override string ToString()
{
throw new NotImplementedException();
}
}

@ -0,0 +1,19 @@
namespace MultiTerm.Core.Types;
public enum FormatType
{
/// <summary>
/// UTF-16 encoded character
/// </summary>
Character,
/// <summary>
/// Hex type
/// </summary>
Hexadecimal,
/// <summary>
/// Binary type
/// </summary>
Binary
}

@ -1,4 +1,6 @@
using MultiTerm.Core.Types; using CommunityToolkit.Mvvm.ComponentModel;
using MultiTerm.Core.Model;
using MultiTerm.Core.Types;
namespace MultiTerm.Core.ViewModel; namespace MultiTerm.Core.ViewModel;
@ -13,5 +15,9 @@ public partial class SendReceiveViewModel : TerminalViewModel
} }
public override TerminalViewType ViewType => TerminalViewType.SendReceive; public override TerminalViewType ViewType => TerminalViewType.SendReceive;
/// <summary>
/// Send data model.
/// </summary>
[ObservableProperty]
private MultiFormatString sendData = new();
} }

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;
using MultiTerm.Core.Types;
namespace MultiTerm.Wpf.CustomControl;
internal class Format
{
private readonly string? backgroundColorResourceName;
public string Name { get; set; }
public Brush BackgroundBrush { get; set; }
public FormatType AssociatedFormatType { get; set; }
public Predicate<Key> IsKeyValid { get; set; }
public Format(string name, Brush backgroundBrush, FormatType associatedFormatType, Predicate<Key> keyValidator)
{
this.Name = name;
this.BackgroundBrush = backgroundBrush;
this.AssociatedFormatType = associatedFormatType;
this.IsKeyValid = keyValidator;
}
public Format(string name, string backgroundColorResourceName, FormatType associatedFormatType, Predicate<Key> keyValidator)
{
this.Name = name;
this.backgroundColorResourceName = backgroundColorResourceName;
this.BackgroundBrush = Brushes.White; // set background brush to white
this.AssociatedFormatType = associatedFormatType;
this.IsKeyValid = keyValidator;
}
public static List<string> GetListOfNames(IEnumerable<Format> formats)
{
return formats.Select(item => item.Name).ToList();
}
public static void UpdateBackgroundBrushesFromResources(FrameworkElement fwElement, IEnumerable<Format> formats)
{
if (fwElement == null) throw new ArgumentNullException(nameof(fwElement));
foreach (var format in formats)
{
// if resource name not set => skip
if (format.backgroundColorResourceName == null) continue;
// get background brush color from resources
try
{
format.BackgroundBrush = (SolidColorBrush)fwElement.FindResource(format.backgroundColorResourceName);
}
catch (Exception) { continue; } // ignore
}
}
}

@ -1,6 +1,9 @@
using System; using MultiTerm.Core.Model;
using MultiTerm.Core.Types;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.ComponentModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
@ -40,69 +43,55 @@ namespace MultiTerm.Wpf.CustomControl;
/// <MyNamespace:MultiFormatTextBox/> /// <MyNamespace:MultiFormatTextBox/>
/// ///
/// </summary> /// </summary>
internal class Format
{
private readonly string? backgroundColorResourceName;
public string Name { get; set; }
public Brush BackgroundBrush { get; set; }
public Predicate<Key> IsKeyValid { get; set; }
public Format(string name, Brush backgroundBrush, Predicate<Key> keyValidator)
{
this.Name = name;
this.BackgroundBrush = backgroundBrush;
this.IsKeyValid = keyValidator;
}
public Format(string name, string backgroundColorResourceName, Predicate<Key> keyValidator)
{
this.Name = name;
this.backgroundColorResourceName = backgroundColorResourceName;
this.BackgroundBrush = Brushes.White; // set background brush to white
this.IsKeyValid = keyValidator;
}
public static List<string> GetListOfNames(IEnumerable<Format> formats)
{
return formats.Select(item => item.Name).ToList();
}
public static void UpdateBackgroundBrushesFromResources(FrameworkElement fwElement, IEnumerable<Format> formats)
{
if (fwElement == null) throw new ArgumentNullException(nameof(fwElement));
foreach (var format in formats)
{
// if resource name not set => skip
if (format.backgroundColorResourceName == null) continue;
// get background brush color from resources
try
{
format.BackgroundBrush = (SolidColorBrush)fwElement.FindResource(format.backgroundColorResourceName);
}
catch (Exception) { continue; } // ignore
}
}
}
public class MultiFormatTextBox : Control public class MultiFormatTextBox : Control
{ {
#region static content
private static readonly Brush defaultBackgroundBrush = Brushes.White; private static readonly Brush defaultBackgroundBrush = Brushes.White;
private static readonly List<Format> formats = new() private static readonly List<Format> formats = new()
{ {
// character input, accepts all keys except space // character input, accepts all keys
//new Format("CHAR", Brushes.LightSkyBlue, delegate(Key k) { return (k != Key.Space); }), new Format("CHAR", "MultiFormatTextBox.CHAR.Background", Core.Types.FormatType.Character,
new Format("CHAR", "MultiFormatTextBox.CHAR.Background", delegate(Key k) { return (k != Key.Space); }), delegate(Key k) { return true; }),
// hex input, ignores all keys that are not inbetween 0 and F // hex input, ignores all keys that are not inbetween 0 and F
//new Format("HEX", Brushes.LightGreen, delegate(Key k) { return (k >= Key.D0 && k <= Key.F); }), new Format("HEX", "MultiFormatTextBox.HEX.Background", Core.Types.FormatType.Hexadecimal,
new Format("HEX", "MultiFormatTextBox.HEX.Background", delegate(Key k) { return (k >= Key.D0 && k <= Key.F); }), delegate(Key k) { return (k >= Key.D0 && k <= Key.F); }),
// binary input, ignores all keys except 0 and 1 // binary input, ignores all keys except 0 and 1
//new Format("BIN", Brushes.LightPink, delegate(Key k) { return (k == Key.D0 || k == Key.D1); }) new Format("BIN", "MultiFormatTextBox.BIN.Background", Core.Types.FormatType.Binary,
new Format("BIN", "MultiFormatTextBox.BIN.Background", delegate(Key k) { return (k == Key.D0 || k == Key.D1); }) delegate(Key k) { return (k == Key.D0 || k == Key.D1); })
}; };
private ComboBox comboBox; #endregion
private RichTextBox richTextBox;
private Format currentlySelectedFormat; #region private content
private const string comboBoxTemplateKey = "comboBox";
private const string richTextBoxTemplateKey = "richTextBox";
private ComboBox? comboBox;
private RichTextBox? richTextBox;
private Format? currentlySelectedFormat;
private int offsetContentStartToFormatStart; private int offsetContentStartToFormatStart;
private object lockObj = new();
#endregion
#region Dependency Properties
public static readonly DependencyProperty CurrentMultiFormatStringProperty =
DependencyProperty.Register("CurrentMultiFormatString",
typeof(MultiFormatString), typeof(MultiFormatTextBox),
new PropertyMetadata(null, OnCurrentMultiFormatStringChanged));
/// <summary>
/// .NET Property for CurrentMultiFormatString.
/// </summary>
[Bindable(true)]
public MultiFormatString CurrentMultiFormatString
{
get { return (MultiFormatString)GetValue(CurrentMultiFormatStringProperty); }
set { SetValue(CurrentMultiFormatStringProperty, value); }
}
#endregion
static MultiFormatTextBox() static MultiFormatTextBox()
{ {
@ -116,36 +105,53 @@ public class MultiFormatTextBox : Control
// initialize format background brushes // initialize format background brushes
Format.UpdateBackgroundBrushesFromResources(this, formats); Format.UpdateBackgroundBrushesFromResources(this, formats);
// set initially selected format // set initially selected format
this.currentlySelectedFormat = formats!.First(); this.currentlySelectedFormat = formats.First();
// get comboBox from template // get comboBox from template
var comboBox = GetTemplateChild("comboBox") as ComboBox; if (GetTemplateChild(comboBoxTemplateKey) is ComboBox comboBox)
if (comboBox != null)
{ {
this.comboBox = comboBox; this.comboBox = comboBox;
this.comboBox.ItemsSource = Format.GetListOfNames(formats); this.comboBox.ItemsSource = Format.GetListOfNames(formats);
this.comboBox.SelectedItem = currentlySelectedFormat.Name; this.comboBox.SelectedItem = currentlySelectedFormat.Name;
this.comboBox.SelectionChanged += ComboBox_SelectionChanged; this.comboBox.SelectionChanged += ComboBox_SelectionChanged;
} }
else
{
throw new Exception($"Implementation fault, {comboBoxTemplateKey} not found in template.");
}
// get richTextBox from template // get richTextBox from template
var richTextBox = GetTemplateChild("richTextBox") as RichTextBox; if (GetTemplateChild(richTextBoxTemplateKey) is RichTextBox richTextBox)
if (richTextBox != null)
{ {
this.richTextBox = richTextBox; this.richTextBox = richTextBox;
this.richTextBox.AcceptsReturn = false;
this.richTextBox.KeyDown += RichTextBox_KeyDown; this.richTextBox.KeyDown += RichTextBox_KeyDown;
this.richTextBox.TextChanged += RichTextBox_TextChanged; this.richTextBox.TextChanged += RichTextBox_TextChanged;
this.offsetContentStartToFormatStart = 0; this.offsetContentStartToFormatStart = 0;
} }
else
{
throw new Exception($"Implementation fault, {richTextBoxTemplateKey} not found in template.");
}
}
private static void OnCurrentMultiFormatStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// extract instance and guard null
if (d is not MultiFormatTextBox mftb) { return; }
// nothing to do
} }
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lock (lockObj) // lock so no richtextbox textchanged event can happen
{ {
// match all formats with the name selected in the combobox // match all formats with the name selected in the combobox
var matchingFormats = formats.Where(format => format.Name == (string)this.comboBox!.SelectedItem); var matchingFormats = formats.Where(format => format.Name == (string)this.comboBox!.SelectedItem);
// check if exactly one format was matched // check if exactly one format was matched
if (matchingFormats.Count() != 1) if (matchingFormats.Count() != 1)
{ {
@ -157,49 +163,46 @@ public class MultiFormatTextBox : Control
// insert space to separate formats // insert space to separate formats
this.InsertSeparation(); this.InsertSeparation();
// set new start position
this.StoreCaretPosition();
// focus textbox // focus textbox
this.richTextBox.Focus(); this.richTextBox!.Focus();
}
} }
private void InsertSeparation() private void InsertSeparation()
{ {
// disable event handler // disable event handler
this.richTextBox.TextChanged -= RichTextBox_TextChanged; this.richTextBox!.TextChanged -= RichTextBox_TextChanged;
// store caret position before
this.StoreCaretPosition();
// insert // insert separator, manually updating caret position because wpf somehow does not do it....
int caretIndexBefore = this.richTextBox.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition);
Debug.WriteLine($"caret position before inserting separator: offset={this.richTextBox.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition)}");
this.richTextBox.AppendText(" "); this.richTextBox.AppendText(" ");
this.richTextBox.CaretPosition = this.richTextBox.Document.ContentStart.GetPositionAtOffset(caretIndexBefore + 1);
Debug.WriteLine($"caret position after inserting separator: offset={this.richTextBox.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition)}");
// change background // change background of last one character
RtbChangeTextBackground(this.richTextBox, RtbChangeTextBackground(this.richTextBox,
start: this.richTextBox.Document.ContentStart.GetPositionAtOffset(-this.offsetContentStartToFormatStart), start: this.richTextBox!.Document.ContentStart.GetPositionAtOffset(caretIndexBefore),
end: this.richTextBox.Document.ContentEnd, end: this.richTextBox.CaretPosition,
color: defaultBackgroundBrush); color: defaultBackgroundBrush);
// store new caret position after
this.StoreCaretPosition();
// reenable // reenable
this.richTextBox.TextChanged += RichTextBox_TextChanged; this.richTextBox.TextChanged += RichTextBox_TextChanged;
} }
private void StoreCaretPosition()
{
this.offsetContentStartToFormatStart = this.richTextBox.CaretPosition.GetOffsetToPosition(this.richTextBox.Document.ContentStart);
}
private void RichTextBox_KeyDown(object sender, KeyEventArgs e) private void RichTextBox_KeyDown(object sender, KeyEventArgs e)
{ {
// guard combobox null // guard combobox null
if (this.comboBox == null) throw new Exception($"{nameof(this.comboBox)} cannot be null"); if (this.comboBox == null) throw new Exception($"{nameof(this.comboBox)} cannot be null");
// if key is invalid for this format => ignore it (handled = true) // if key is invalid for this format => ignore it (handled = true)
if(this.currentlySelectedFormat.IsKeyValid(e.Key) == false) if (this.currentlySelectedFormat!.IsKeyValid(e.Key) == false)
{
e.Handled = true;
}
// ignore enter
if(e.Key == Key.Enter)
{ {
e.Handled = true; e.Handled = true;
} }
@ -207,34 +210,105 @@ public class MultiFormatTextBox : Control
private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e) private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{ {
if (e.Changes.Count > 1 || e.Changes.Count == 0) { return; } // no changes => exit
if (e.Changes.Count == 0) return;
lock (lockObj) // lock so no concurrent combobox change can happen
{
foreach (var change in e.Changes)
{
// print new text
Debug.WriteLine($"{nameof(RichTextBox_TextChanged)} changed to '{new TextRange(this.richTextBox!.Document.ContentStart, this.richTextBox.Document.ContentEnd).Text}'");
Debug.WriteLine($"{nameof(RichTextBox_TextChanged)} caret position '{this.richTextBox!.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition)}'");
// ignore changes that replace something
if (change.RemovedLength == change.AddedLength) continue;
// if something was added => change background color // if something was added => change background color
if (e.Changes.First().AddedLength > 0) if (change.AddedLength > 0)
{ {
RtbChangeTextBackground(this.richTextBox, Debug.WriteLine($"{nameof(RichTextBox_TextChanged)} offset of change = {change.Offset}");
start: this.richTextBox.Document.ContentStart.GetPositionAtOffset(-this.offsetContentStartToFormatStart),
end: this.richTextBox.Document.ContentEnd, // disable event handler so the update does not trigger any TextChanged events (which it does, interestingly)
color: this.currentlySelectedFormat.BackgroundBrush); this.richTextBox!.TextChanged -= RichTextBox_TextChanged;
// update color
RtbChangeTextBackground(this.richTextBox!,
start: this.richTextBox!.Document.ContentStart.GetPositionAtOffset(change.Offset),
end: this.richTextBox!.Document.ContentStart.GetPositionAtOffset(change.Offset + change.AddedLength),
color: this.currentlySelectedFormat!.BackgroundBrush);
// reenable event handler
this.richTextBox.TextChanged += RichTextBox_TextChanged;
} }
// if something was removed => update start of content to current location }
else if(e.Changes.First().RemovedLength > 0)
{ // update CurrentMultiFormatString
this.StoreCaretPosition(); this.CurrentMultiFormatString = this.ConvertRtbContentToMultiFormatString(this.richTextBox!);
} }
} }
private static void RtbChangeTextBackground(RichTextBox rtb, TextPointer start, TextPointer end, Brush color) private static void RtbChangeTextBackground(RichTextBox rtb, TextPointer start, TextPointer end, Brush color)
{ {
// Get text selection // print offsets to start and end position
TextSelection textRange = rtb.Selection; Debug.WriteLine($"Changing background of textbox offset {rtb.Document.ContentStart.GetOffsetToPosition(start)} until {rtb.Document.ContentStart.GetOffsetToPosition(end)} to {color}");
// get editable selection
var textRange = rtb.Selection;
textRange.Select(start, end); textRange.Select(start, end);
// Apply property to the selection: // Apply property to the selection:
textRange.ApplyPropertyValue(TextElement.BackgroundProperty, color); textRange.ApplyPropertyValue(TextElement.BackgroundProperty, color);
// deselect // deselect everything (set to end)
rtb.Selection.Select(rtb.Document.ContentEnd, rtb.Document.ContentEnd); rtb.Selection.Select(end, end);
} }
private MultiFormatString ConvertRtbContentToMultiFormatString(RichTextBox rtb)
{
MultiFormatString multiFormatString = new();
// store current caret position
int offsetToCaretPosition = rtb.Document.ContentStart.GetOffsetToPosition(rtb.CaretPosition);
// get number of symbols
var textLength = rtb.Document.ContentStart.GetOffsetToPosition(rtb.Document.ContentEnd);
// get full content as TextSelection object
var textRange = rtb.Selection;
// loop through every character
for (int offset = 0; offset < textLength; offset++)
{
// select one character
textRange.Select(rtb.Document.ContentStart.GetPositionAtOffset(offset), rtb.Document.ContentStart.GetPositionAtOffset(offset + 1));
//skip empty textRange
if (textRange.IsEmpty)
{ continue; }
// extract background brush
var brush = (Brush)textRange.GetPropertyValue(TextElement.BackgroundProperty);
// ignore this symbol, since its a separator
if (brush == defaultBackgroundBrush)
{ continue; }
// search if it is a brush of a format
foreach (var format in formats)
{
// format has this background brush => add to string with format and text
if (brush == format.BackgroundBrush)
{
multiFormatString.Add(format.AssociatedFormatType, textRange.Text);
break; // end loop
}
}
}
// deselect everything (set to end)
textRange.Select(
rtb.Document.ContentStart.GetPositionAtOffset(offsetToCaretPosition),
rtb.Document.ContentStart.GetPositionAtOffset(offsetToCaretPosition));
return multiFormatString;
}
} }

@ -6,4 +6,8 @@
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MultiTerm.Core\MultiTerm.Core.csproj" />
</ItemGroup>
</Project> </Project>

@ -39,7 +39,7 @@
<DockPanel DockPanel.Dock="Top" LastChildFill="True"> <DockPanel DockPanel.Dock="Top" LastChildFill="True">
<!--<TextBox Width="700"></TextBox>--> <!--<TextBox Width="700"></TextBox>-->
<Button DockPanel.Dock="Right" Content="Send"/> <Button DockPanel.Dock="Right" Content="Send"/>
<custom_controls:MultiFormatTextBox DockPanel.Dock="Left"></custom_controls:MultiFormatTextBox> <custom_controls:MultiFormatTextBox DockPanel.Dock="Left" CurrentMultiFormatString="{Binding SendData}"></custom_controls:MultiFormatTextBox>
</DockPanel> </DockPanel>
<Separator/> <Separator/>
<RichTextBox DockPanel.Dock="Bottom"> <RichTextBox DockPanel.Dock="Bottom">

Loading…
Cancel
Save