Загрузка данных


<Window x:Class="CalculatorProPlusBoosted.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Calculator" Height="400" Width="300">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- Поле ввода -->
        <TextBox x:Name="InputBox"
                 Grid.Row="0"
                 FontSize="18"
                 Margin="0,0,0,5"
                 TextChanged="InputBox_TextChanged"
                 PreviewTextInput="InputBox_PreviewTextInput"/>

        <!-- Поле результата -->
        <TextBox x:Name="ResultBox"
                 Grid.Row="1"
                 FontSize="18"
                 IsReadOnly="True"
                 Margin="0,0,0,10"/>

        <!-- Кнопки -->
        <UniformGrid Grid.Row="2" Columns="4">
            <!-- Числа -->
            <Button Content="7" Click="Digit_Click"/>
            <Button Content="8" Click="Digit_Click"/>
            <Button Content="9" Click="Digit_Click"/>
            <Button Content="+" Click="Operator_Click"/>

            <Button Content="4" Click="Digit_Click"/>
            <Button Content="5" Click="Digit_Click"/>
            <Button Content="6" Click="Digit_Click"/>
            <Button Content="-" Click="Operator_Click"/>

            <Button Content="1" Click="Digit_Click"/>
            <Button Content="2" Click="Digit_Click"/>
            <Button Content="3" Click="Digit_Click"/>
            <Button Content="*" Click="Operator_Click"/>

            <Button Content="0" Click="Digit_Click"/>
            <Button Content="." Click="Dot_Click"/>
            <Button Content="C" Click="Clear_Click"/>
            <Button Content="/" Click="Operator_Click"/>

            <Button Content="^" Click="Operator_Click"/>
        </UniformGrid>
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace CalculatorProPlusBoosted
{
	public class Calculator
	{
		public Zone? MainZone = null;
		public string Input = string.Empty;

		public CalculateResult Calculate(string input)
		{
			Input = input;
			CalculateResult result = Parse();

			if (!result.Success) // Если не удалось конвертировать
				return result;

			MainZone.InitializeZone();

			string validateResult = MainZone.Validate(null, -1);

			if (validateResult != null && validateResult != string.Empty)
				return new CalculateResult(validateResult, -1);


			MainZone.Calculate(-1);
			var numResult = MainZone.GetByIndex(0) as Number;
			return new CalculateResult(numResult.Num);
		}

		public CalculateResult Parse()
		{
			Zone mainZone = new Zone();
			var current = mainZone;
			string numBuffer = string.Empty;

			Input = Input.Replace(" ", "").Replace("	", "");

			for (int i = 0; i < Input.Length; i++)
			{
				char c = Input[i];
				if (isDigit(c))
				{
					numBuffer += c;
					if (i == Input.Length - 1)
					{
						if (numBuffer != string.Empty) // Если буфер не пустой, выгружаем буфер и добавляем число в зону
						{
							current.Add(new Number(Convert.ToSingle(numBuffer, CultureInfo.InvariantCulture)));
							numBuffer = string.Empty;
						}
					}
					continue;
				}
				if (!isDigit(c))
				{
					if (numBuffer != string.Empty) // Если буфер не пустой, выгружаем буфер и добавляем число в зону
					{
						current.Add(new Number(Convert.ToSingle(numBuffer)));
						numBuffer = string.Empty;
					}

				}
				switch (c)
				{
					case '(':
						var newZone = new Zone();
						current.Add(newZone);
						newZone.MyZone = current;
						current = newZone;
						break;
					case ')':
						try { current = current.MyZone; }
						catch { }
						break;
					case '+':
						current.Add(new Addition());
						break;
					case '-':
						if (i == 0 || (!isDigit(Input[i - 1]) && isDigit(Input[i + 1])))
						{
							numBuffer += '-';
							break;
						}
						current.Add(new Difference());
						break;
					case '×':
					case '*':
						current.Add(new Multiplication());
						break;
					case '/':
						current.Add(new Division());
						break;
					case '^':
						current.Add(new Exponentiation());
						break;
					default:
						return new CalculateResult("Недопустимый символ", i);
				}
			}

			MainZone = mainZone;
			return new CalculateResult(-1);
		}

		public static bool IsValidSymbol(char c)
        {
			string symbols = "0123456789.,+-*/^";
			if (symbols.Contains(c))
				return true;
			return false;
		}
		public static bool isDigit(char c)
		{
			string allowedChars = "0123456789.,";

			if (allowedChars.Contains(c))
				return true;
			return false;
		}
	}

	public class CalculateResult
	{
		public string? Error { get; } = null;
		public int? ErrorSymbolId { get; } = null;

		public float? Result { get; } = null;
		public bool Success { get; }

		/// <summary> Создание результата с ошибкой </summary>
		public CalculateResult(string error, int errorSymbolId, Zone? mainZone = null)
		{
			Error = error;
			ErrorSymbolId = ErrorSymbolId;
			Success = false;
		}

		/// <summary> Создание успешного результата </summary>
		public CalculateResult(float result)
		{
			Result = result;
			Success = true;
		}
	}

	public abstract class CalculatableObject
	{
		public Zone MyZone = null;
		public virtual void InitializeZone(Zone zone)
		{
			MyZone = zone;
		}
	}

	public class Number : CalculatableObject
	{
		public float Num;
		public Number(float num)
		{
			Num = num;
		}
	}

	public abstract class Operator : CalculatableObject
	{
		public abstract void Calculate(int index);

		/// <summary> Возвращает null, если успешно. Возвращает строку с ошибкой, если ошибка.</summary>
		public virtual string? Validate(Zone zone, int index)
		{
			var beforeNum = zone.GetByIndex(index - 1);
			var afterNum = zone.GetByIndex(index + 1);

			if (beforeNum is null || afterNum is null) return "Оператор не может быть в конце или в начале выражения";

			// Если прошлый и следующий объект - числа или зоны(которые в будущем будут числами), значит всё верно
			if ((beforeNum is Number || beforeNum is Zone) && (afterNum is Number || afterNum is Zone)) return null;

			return "Оператор не может производить вычисления на оператор: ";
		}
	}

	public class Zone : Operator
	{
		List<CalculatableObject> objects;
		public Zone(List<CalculatableObject> objects)
		{
			this.objects = objects;
		}
		public Zone(params CalculatableObject[] objects)
		{
			this.objects = objects.ToList();
		}
		public override void InitializeZone(Zone zone = null)
		{
			if (zone != null)
				MyZone = zone;
			foreach (var obj in objects)
			{
				obj.InitializeZone(this);
			}
		}
		public override string? Validate(Zone zone, int index)
		{
			for (int i = 0; i < objects.Count; i++)
			{
				if (objects[i] is Operator op)
				{
					string validateResult = op.Validate(this, i);
					if (validateResult != null && validateResult != string.Empty)
						return validateResult;
				}
			}
			return null;
		}
		public int GetIndexOf(CalculatableObject obj)
		{
			return objects.IndexOf(obj);
		}
		public CalculatableObject? GetByIndex(int index)
		{
			if (index >= objects.Count || index < 0) return null;
			return objects[index];
		}
		public int Count() => objects.Count;
		public void Replace(CalculatableObject replaceable, CalculatableObject newObj, params CalculatableObject[] delete)
		{
			int replaceableIndex = objects.IndexOf(replaceable);

			objects[replaceableIndex] = newObj;

			foreach (var delObj in delete)
				objects.Remove(delObj);
		}
		public void Add(CalculatableObject obj, int? index = null)
		{
			if (index != null)
				objects.Insert((int)index, obj);
			else
				objects.Add(obj);
		}
		public string LocalToString()
		{
			if (MyZone != null)
				return MyZone.LocalToString();
			return ToString();
		}
		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			if (MyZone != null)
			{
				sb.Append('(');
			}
			foreach (var obj in objects)
			{
				if (obj is Number num)
				{
					if (num.Num < 0)
						sb.Append($"({num.Num})");
					else sb.Append(num.Num);
				}
				else if (obj is Addition)
					sb.Append('+');
				else if (obj is Difference)
					sb.Append('-');
				else if (obj is Multiplication)
					sb.Append('*');
				else if (obj is Division)
					sb.Append('/');
				else if (obj is Exponentiation)
					sb.Append('^');
				else if (obj is Zone z)
					sb.Append(z.ToString());
			}
			if (MyZone != null) sb.Append(')');
			return sb.ToString();
		}
		public override void Calculate(int index)
		{
			CalculateOperators(typeof(Zone));
			CalculateOperators(typeof(Division), typeof(Multiplication), typeof(Exponentiation));
			CalculateOperators(typeof(Addition), typeof(Difference));

			if (MyZone != null)
				MyZone.Replace(this, objects[0]);
			return;
		}
		private void CalculateOperators(params Type[] types)
		{
			while (true)
			{
				bool isEnd = true;
				for (int i = 0; i < objects.Count(); i++)
				{
					var objType = objects[i].GetType();
					if (objects[i] is Operator op && types.Any(type => type == objType))
					{
						op.Calculate(i);
						isEnd = false;
						break;
					}
				}
				if (isEnd)
					return;
			}
		}
	}
	public class Addition : Operator
	{
		public override void Calculate(int index)
		{
			var beforeObj = MyZone.GetByIndex(index - 1) as Number;
			var afterObj = MyZone.GetByIndex(index + 1) as Number;

			MyZone.Replace(this, new Number(beforeObj.Num + afterObj.Num), beforeObj, afterObj);
		}
	}
	public class Difference : Operator
	{
		public override void Calculate(int index)
		{
			var beforeObj = MyZone.GetByIndex(index - 1) as Number;
			var afterObj = MyZone.GetByIndex(index + 1) as Number;

			MyZone.Replace(this, new Number(beforeObj.Num - afterObj.Num), beforeObj, afterObj);
		}
	}
	public class Multiplication : Operator
	{
		public override void Calculate(int index)
		{
			var beforeObj = MyZone.GetByIndex(index - 1) as Number;
			var afterObj = MyZone.GetByIndex(index + 1) as Number;

			MyZone.Replace(this, new Number(beforeObj.Num * afterObj.Num), beforeObj, afterObj);
		}
	}
	public class Division : Operator
	{
		public override void Calculate(int index)
		{
			var beforeObj = MyZone.GetByIndex(index - 1) as Number;
			var afterObj = MyZone.GetByIndex(index + 1) as Number;

			MyZone.Replace(this, new Number(beforeObj.Num / afterObj.Num), beforeObj, afterObj);
		}
	}
	public class Exponentiation : Operator
	{
		public override void Calculate(int index)
		{
			Console.WriteLine("EXP");
			var beforeObj = MyZone.GetByIndex(index - 1) as Number;
			var afterObj = MyZone.GetByIndex(index + 1) as Number;

			MyZone.Replace(this, new Number((float)MathF.Pow(beforeObj.Num, afterObj.Num)), beforeObj, afterObj);
		}
	}
}


using System;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace CalculatorProPlusBoosted
{
	public partial class MainWindow : Window
	{
		public MainWindow()
		{
			InitializeComponent();
		}

		// =========================
		// Обработка кнопок
		// =========================

		private void Digit_Click(object sender, RoutedEventArgs e)
		{
			var button = sender as Button;
			InsertText(button.Content.ToString());
		}

		private void Operator_Click(object sender, RoutedEventArgs e)
		{
			var button = sender as Button;
			InsertText(button.Content.ToString());
		}

		private void Dot_Click(object sender, RoutedEventArgs e)
		{
			InsertText(".");
		}

		private void Clear_Click(object sender, RoutedEventArgs e)
		{
			InputBox.Text = "";
			ResultBox.Text = "";
		}

		// =========================
		// Пересчёт результата
		// =========================

		private void InputBox_TextChanged(object sender, TextChangedEventArgs e)
		{
			try
			{
				string input = InputBox.Text;

				if (string.IsNullOrWhiteSpace(input))
				{
					ResultBox.Text = "";
					return;
				}

				// Вызов твоего класса
				var calc = new Calculator();
				var result = calc.Calculate(input);
				if (result.Success)
					ResultBox.Text = result.Result.ToString();
				else
					ResultBox.Text = result.Result.ToString();
			}
			catch (Exception ex)
			{
				ResultBox.Text = $"Ошибка: {ex}";
			}
		}


		private void InputBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
		{
			var textBox = sender as TextBox;

			bool isValid = IsValidInput(textBox.Text, textBox.CaretIndex, e.Text);

			e.Handled = !isValid;
		}

		private void InsertText(string text)
        {
			if (!IsValidInput(InputBox.Text, InputBox.CaretIndex, text))
				return;

			int caret = InputBox.CaretIndex;

			InputBox.Text = InputBox.Text.Insert(caret, text);
			InputBox.CaretIndex = caret + text.Length;
		}

		private bool IsValidInput(string currentText, int caretIndex, string newText)
		{
			// 1. Разрешаем только 1 символ
			if (string.IsNullOrEmpty(newText) || newText.Length != 1)
				return false;

			char ch = newText[0];

			// Разрешённые символы
			string allowed = "0123456789+-*/^.,";
			if (!allowed.Contains(ch))
				return false;

			if (ch == ',') ch = '.';

			// Будущая строка после вставки
			string newString = currentText.Insert(caretIndex, newText);

			// Символ слева от курсора
			char? left = caretIndex > 0 ? currentText[caretIndex - 1] : (char?)null;

			// Символ справа от курсора
			char? right = caretIndex < currentText.Length ? currentText[caretIndex] : (char?)null;

			// -------------------------
			// Операторы
			// -------------------------
			if ("+-*/^".Contains(ch))
			{
				if ((left != null && "+-*/^".Contains(left.Value)) ||
					(right != null && "+-*/^".Contains(right.Value)))
				{
					return false;
				}
			}

			// -------------------------
			// Точка
			// -------------------------
			if (ch == '.')
			{
				// Нельзя две точки подряд
				if ((left != null && left == '.') || (right != null && right == '.'))
					return false;

				// Найти границы текущего числа
				int start = caretIndex;
				while (start > 0 && char.IsDigit(currentText[start - 1]) ||
					   (start > 0 && currentText[start - 1] == '.'))
				{
					start--;
				}

				int end = caretIndex;
				while (end < currentText.Length && char.IsDigit(currentText[end]) ||
					   (end < currentText.Length && currentText[end] == '.'))
				{
					end++;
				}

				string numberPart = currentText.Substring(start, end - start);

				// Если уже есть точка в этом числе
				if (numberPart.Contains('.'))
					return false;
			}

			return true;
		}
	}
}