Here's a sketch on what approach I would choose. You still need to play around with
CaretIndex.
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Controls;
namespace WpfApplication1
{
public class TextBoxCustomNumber : TextBox
{
private const string _GROUP_NAME_IS_NEGATIVE = "isNegative";
private const string _GROUP_NAME_INTEGER = "integer";
private const string _GROUP_NAME_DOT = "dot";
private const string _GROUP_NAME_FRACTION = "fraction";
private static readonly Regex _RegexNumber = new Regex(@"\A(?<" + _GROUP_NAME_IS_NEGATIVE + @">-)?(?<" + _GROUP_NAME_INTEGER + @">[0-9]+)*(?<" + _GROUP_NAME_DOT + @">\.)?(?<" + _GROUP_NAME_FRACTION + @">[0-9]*)\z");
private string _oldText;
public TextBoxCustomNumber()
{
TextChanged += OnTextChanged;
}
private void ApplyResults(string numberFormattedStr, int caretIndex)
{
if (!Text.Equals(numberFormattedStr))
{
Text = numberFormattedStr;
CaretIndex = caretIndex;
_oldText = numberFormattedStr;
}
}
private void OnTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
{
var text = Text;
if (string.IsNullOrEmpty(text))
{
ApplyResults("", 0);
return;
}
var match = _RegexNumber.Match(text.Replace(",", ""));
if (!match.Success)
{
ApplyResults(_oldText, CaretIndex);
return;
}
var numberAndFormattedStr = ConstructAndFormat(
match.Groups[_GROUP_NAME_IS_NEGATIVE].Success,
match.Groups[_GROUP_NAME_INTEGER].Success ? match.Groups[_GROUP_NAME_INTEGER].Value : "",
match.Groups[_GROUP_NAME_DOT].Success,
match.Groups[_GROUP_NAME_FRACTION].Success ? match.Groups[_GROUP_NAME_FRACTION].Value : ""
);
var number = numberAndFormattedStr.Item1;
var numberFormattedStr = numberAndFormattedStr.Item2;
ApplyResults(numberFormattedStr, CaretIndex);
}
private static Tuple<decimal?, string> ConstructAndFormat(bool isNegative, string integerStr, bool dot, string fractionStr)
{
var numberStr = (isNegative ? "-" : "") + integerStr + (dot ? "." : "") + fractionStr;
decimal numberAbs;
if (!decimal.TryParse(numberStr, out numberAbs))
{
return new Tuple<decimal?, string>(null, numberStr);
}
numberAbs = Math.Abs(numberAbs);
var sbFractionFormatter = new StringBuilder();
for (var i = 0; i < fractionStr.Length; i++)
{
sbFractionFormatter.Append('0');
}
var numberFormattedStr = string.Format("{0}{1:#,##0." + sbFractionFormatter.ToString() + "}{2}",
isNegative ? "-" : "",
numberAbs,
string.IsNullOrEmpty(fractionStr) && dot ? "." : ""
);
return new Tuple<decimal?, string>(numberAbs, numberFormattedStr);
}
}
}