using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace MultiTerm.Wpf.CustomControl;
///
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:MultiTerm.Wpf.CustomControl"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:MultiTerm.Wpf.CustomControl.MultiFormatTextBox;assembly=MultiTerm.Wpf.CustomControl.MultiFormatTextBox"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Browse to and select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
///
///
///
internal class Format
{
private readonly string? backgroundColorResourceName;
public string Name { get; set; }
public Brush BackgroundBrush { get; set; }
public Predicate IsKeyValid { get; set; }
public Format(string name, Brush backgroundBrush, Predicate keyValidator)
{
this.Name = name;
this.BackgroundBrush = backgroundBrush;
this.IsKeyValid = keyValidator;
}
public Format(string name, string backgroundColorResourceName, Predicate keyValidator)
{
this.Name = name;
this.backgroundColorResourceName = backgroundColorResourceName;
this.BackgroundBrush = Brushes.White; // set background brush to white
this.IsKeyValid = keyValidator;
}
public static List GetListOfNames(IEnumerable formats)
{
return formats.Select(item => item.Name).ToList();
}
public static void UpdateBackgroundBrushesFromResources(FrameworkElement fwElement, IEnumerable 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
{
private static readonly Brush defaultBackgroundBrush = Brushes.White;
private static readonly List formats = new()
{
// character input, accepts all keys except space
//new Format("CHAR", Brushes.LightSkyBlue, delegate(Key k) { return (k != Key.Space); }),
new Format("CHAR", "MultiFormatTextBox.CHAR.Background", delegate(Key k) { return (k != Key.Space); }),
// 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", delegate(Key k) { return (k >= Key.D0 && k <= Key.F); }),
// 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", delegate(Key k) { return (k == Key.D0 || k == Key.D1); })
};
private ComboBox comboBox;
private RichTextBox richTextBox;
private Format currentlySelectedFormat;
private int offsetContentStartToFormatStart;
static MultiFormatTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiFormatTextBox), new FrameworkPropertyMetadata(typeof(MultiFormatTextBox)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// initialize format background brushes
Format.UpdateBackgroundBrushesFromResources(this, formats);
// set initially selected format
this.currentlySelectedFormat = formats!.First();
// get comboBox from template
var comboBox = GetTemplateChild("comboBox") as ComboBox;
if (comboBox != null)
{
this.comboBox = comboBox;
this.comboBox.ItemsSource = Format.GetListOfNames(formats);
this.comboBox.SelectedItem = currentlySelectedFormat.Name;
this.comboBox.SelectionChanged += ComboBox_SelectionChanged;
}
// get richTextBox from template
var richTextBox = GetTemplateChild("richTextBox") as RichTextBox;
if (richTextBox != null)
{
this.richTextBox = richTextBox;
this.richTextBox.KeyDown += RichTextBox_KeyDown;
this.richTextBox.TextChanged += RichTextBox_TextChanged;
this.offsetContentStartToFormatStart = 0;
}
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// match all formats with the name selected in the combobox
var matchingFormats = formats.Where(format => format.Name == (string)this.comboBox!.SelectedItem);
// check if exactly one format was matched
if(matchingFormats.Count() != 1)
{
throw new Exception($"{nameof(ComboBox_SelectionChanged)} could not match a correct amount of formats");
}
// set currently selected format, reset offset counter
this.currentlySelectedFormat = matchingFormats.First();
// insert space to separate formats
this.InsertSeparation();
// set new start position
this.StoreCaretPosition();
// focus textbox
this.richTextBox.Focus();
}
private void InsertSeparation()
{
// disable event handler
this.richTextBox.TextChanged -= RichTextBox_TextChanged;
// store caret position before
this.StoreCaretPosition();
// insert
this.richTextBox.AppendText(" ");
// change background
RtbChangeTextBackground(this.richTextBox,
start: this.richTextBox.Document.ContentStart.GetPositionAtOffset(-this.offsetContentStartToFormatStart),
end: this.richTextBox.Document.ContentEnd,
color: defaultBackgroundBrush);
// store new caret position after
this.StoreCaretPosition();
// reenable
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)
{
// guard combobox 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(this.currentlySelectedFormat.IsKeyValid(e.Key) == false)
{
e.Handled = true;
}
}
private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (e.Changes.Count > 1 || e.Changes.Count == 0) { return; }
// if something was added => change background color
if (e.Changes.First().AddedLength > 0)
{
RtbChangeTextBackground(this.richTextBox,
start: this.richTextBox.Document.ContentStart.GetPositionAtOffset(-this.offsetContentStartToFormatStart),
end: this.richTextBox.Document.ContentEnd,
color: this.currentlySelectedFormat.BackgroundBrush);
}
// if something was removed => update start of content to current location
else if(e.Changes.First().RemovedLength > 0)
{
this.StoreCaretPosition();
}
}
private static void RtbChangeTextBackground(RichTextBox rtb, TextPointer start, TextPointer end, Brush color)
{
// Get text selection
TextSelection textRange = rtb.Selection;
textRange.Select(start, end);
// Apply property to the selection:
textRange.ApplyPropertyValue(TextElement.BackgroundProperty, color);
// deselect
rtb.Selection.Select(rtb.Document.ContentEnd, rtb.Document.ContentEnd);
}
}