Тройное дублирование текста в TextBox

Хочу сделать небольшой редактор текста: один textbox, две кнопки (для bold и italic) и ничего больше. Важное уточнение: никакого отображения стилизованного текста, только его сырое представление, надеюсь, понятно. Задача тривиальная, но применение стилей ведет себя странно, делая текст больше, чем он был изначально. Пример:

  1. Исходный текст: hello
  2. Нажатие кнопки Bold
  3. Ожидание: **hello**
  4. Реальность: hello**hello**hello

Код MyWindows.axaml.cs:

private void BtnItalicOnClick(object? sender, RoutedEventArgs e)
    {
        if (_isProcessing) return;
        _isProcessing = true;

        try
        {
            // Устанавливаем фокус на TextBox
            tbPost.Focus();

            // Проверяем, есть ли выделенный текст
            if (string.IsNullOrWhiteSpace(tbPost.SelectedText))
            {
                return;
            }

            int selectionStart = tbPost.SelectionStart;
            int selectionEnd = tbPost.SelectionEnd;
            string selectedText = tbPost.SelectedText;

            // Запрещаем форматирование, если выделение содержит переносы строк
            if (selectedText.Contains("\n") || selectedText.Contains("\r"))
            {
                return;
            }

            // Проверяем, не отформатирован ли уже текст
            string beforeText = selectionStart >= 2 ? tbPost.Text[(selectionStart - 2)..selectionStart] : "";
            string afterText = selectionEnd < tbPost.Text.Length - 2 ? tbPost.Text[selectionEnd..(selectionEnd + 2)] : "";
            if (beforeText == "__" && afterText == "__")
            {
                return;
            }

            // Формируем новый текст
            string newText = $"{tbPost.Text[..selectionStart]}__{selectedText}__{tbPost.Text[selectionEnd..]}";

            // Обновляем текст через ViewModel
            _vm.PostText = newText;

            // Восстанавливаем выделение
            tbPost.SelectionStart = selectionStart + 2;
            tbPost.SelectionEnd = selectionEnd + 2;
        }
        finally
        {
            _isProcessing = false;
        }
    }

    private void BtnBoldOnClick(object? sender, RoutedEventArgs e)
    {
        if (_isProcessing) return;
        _isProcessing = true;

        try
        {
            // Устанавливаем фокус на TextBox
            tbPost.Focus();

            // Проверяем, есть ли выделенный текст
            if (string.IsNullOrWhiteSpace(tbPost.SelectedText))
            {
                return;
            }

            int selectionStart = tbPost.SelectionStart;
            int selectionEnd = tbPost.SelectionEnd;
            string selectedText = tbPost.SelectedText;

            // Запрещаем форматирование, если выделение содержит переносы строк
            if (selectedText.Contains("\n") || selectedText.Contains("\r"))
            {
                return;
            }

            // Проверяем, не отформатирован ли уже текст
            string beforeText = selectionStart >= 2 ? tbPost.Text[(selectionStart - 2)..selectionStart] : "";
            string afterText = selectionEnd < tbPost.Text.Length - 2 ? tbPost.Text[selectionEnd..(selectionEnd + 2)] : "";
            if (beforeText == "**" && afterText == "**")
            {
                return;
            }

            // Формируем новый текст
            string newText = $"{tbPost.Text[..selectionStart]}**{selectedText}**{tbPost.Text[selectionEnd..]}";

            // Обновляем текст через ViewModel
            _vm.PostText = newText;

            // Восстанавливаем выделение
            tbPost.SelectionStart = selectionStart + 2;
            tbPost.SelectionEnd = selectionEnd + 2;
        }
        finally
        {
            _isProcessing = false;
        }
    }

Код TextBox в MyWindows.axaml:

        <TextBox Grid.Row="1" 
                 Name="tbPost"
                 Text="{Binding PostText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                 AcceptsReturn="True"
                 ClearSelectionOnLostFocus="False"/>

Код свойства из MyWindowsViewModel.cs:

private string _postText;
public string PostText
{
    get => _postText; 
    set => this.RaiseAndSetIfChanged(ref this._postText, value);
}

P.S. Изначально обработчики кнопок были проще, но в попытках исправить проблему добавил много лишних проверок и т.д.

UPD. После комментариев дополняю вопрос

Avalonia 11.2.1, .NET 9

Первоначальная версия методов (писал сам) для простоты понимания:

private void BtnItalicOnClick(object? sender, RoutedEventArgs e)
    {
        if (string.IsNullOrWhiteSpace(tbPost.SelectedText)) return;

        int from = tbPost.SelectionStart;
        int to = tbPost.SelectionEnd;
        _vm.PostText = $"{tbPost.Text[..from]}**{tbPost.SelectedText}**{tbPost.Text[to..]}";
    }

    private void BtnBoldOnClick(object? sender, RoutedEventArgs e)
    {
        if (string.IsNullOrWhiteSpace(tbPost.SelectedText)) return;

        int from = tbPost.SelectionStart;
        int to = tbPost.SelectionEnd;
        _vm.PostText = $"{tbPost.Text[..from]}__{tbPost.SelectedText}__{tbPost.Text[to..]}";
    }

Остальной код MyWindow.cs

// единственный конструктор
  public MyWindow()
    {
        InitializeComponent();
        Loaded += OnLoaded;
        btnBold.Click += BtnBoldOnClick;
        btnItalic.Click += BtnItalicOnClick;
    }

// получаю view model и подписываюсь на вставку в textbox
   private void OnLoaded(object? sender, RoutedEventArgs e)
    {
        _vm = (MyWindowViewMode)DataContext!;
        tbPost.PastingFromClipboard += TbPostOnPastingFromClipboard;
    }

// отслеживаю вставку Ctrl + V и ищу в буфере изображение
 private async void TbPostOnPastingFromClipboard(object? sender, RoutedEventArgs e)
    {
        var clipboard = this.Clipboard;
        var dataObject =
            await clipboard.GetDataAsync("image/png")
            ?? await clipboard.GetDataAsync("image/jpeg")
            ?? await clipboard.GetDataAsync("PNG")
            ?? await clipboard.GetDataAsync("Bitmap");
        if (dataObject is null)
            return;

        _vm.PastImage(dataObject);
        e.Handled = true;
    }

Ответы (0 шт):