Search code examples
c#wpfrichtextbox

Format richtextbox without selection


I want to change the formatting in a richtextbox in two ways:

  • text is selected => text should re-size. This works fine.
  • text is not selected, just a caret cursor is set between a text. => font-size should change for further writing. It shouldn't change the already existing text before => this don't work.

Example for my second requierement: My richtextbox got the text

stackoverflow

and the caret cursor is between stackover and flow like this:

stackover|flow

Now I need to change the fontsize on the current caret position for example to 20. On further writing I just want the text on this position displayed bigger. I don't want to change the current existing text. Like:

stackovercoolflow

cool should be in fontsize 20 now and stackover flow in default font size (like before)

I found a lot of answers but nothing of them helped me.

Most hints were:

  • Focus() on richtextbox
  • Use Selection.Start and Selection.End
  • Use an own method for 'SetFontSize'

But nothing of them worked for me...

Mostly I oriented myself on the following question wpf-richtextbox-fontface-fontsize

Currently I ended up with the followed code, but it only changes text-selection.

<Window x:Class="WpfApp2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical">
    <RichTextBox Height="300" Width="300" x:Name="MyRichTextBox" />
    <ComboBox Width="300" x:Name="cmbbx" SelectedIndex="1" SelectionChanged="ComboBox_SelectionChanged" />
</StackPanel>
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace WpfApp2
{
    public partial class MainWindow : Window
    {
        public ObservableCollection<double> Werte { get; set; }

        public MainWindow()
        {
            Werte = new ObservableCollection<double>();
            Werte.Add(4);
            Werte.Add(10);
            Werte.Add(20);
            Werte.Add(55);
            InitializeComponent();

            cmbbx.ItemsSource = Werte;
        }


        private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var newSize = (double)cmbbx.SelectedValue;
            TextRange r = new TextRange(MyRichTextBox.Selection.Start, MyRichTextBox.Selection.End);
            r.ApplyPropertyValue(TextElement.FontSizeProperty, newSize);
        }

    }
}

This just changes text-selection. There is no formatting when nothing is selected (just cursor is set). I am working with .net 5


Solution

  • Probably you want something like below:

    <Window x:Class="WpfApp2.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"        
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>            
            </Grid.RowDefinitions>
            <ComboBox Grid.Row="0" Width="60" x:Name="cmbbx" SelectedIndex="1" SelectionChanged="ComboBox_SelectionChanged" HorizontalAlignment="Left"/>        
            <RichTextBox Grid.Row="1" x:Name="rtb" AllowDrop="True" VerticalScrollBarVisibility="Auto" Padding="2"                    
                         SelectionChanged="rtb_SelectionChanged"
                         TextChanged="rtb_TextChanged">
                <FlowDocument/>
            </RichTextBox>       
        </Grid>
    </Window>
    
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Collections.ObjectModel;
    
    namespace WpfApp2
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                var Werte = new ObservableCollection<double> { 4, 10, 16, 20, 26, 55 };
    
                InitializeComponent();
                cmbbx.ItemsSource = Werte;
            }
           
            private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                if (!rtb.Selection.IsEmpty)
                {
                    if (cmbbx.SelectedValue is double newSize)
                    {                   
                        var range = new TextRange(rtb.Selection.Start, rtb.Selection.End);
                        range.ApplyPropertyValue(TextElement.FontSizeProperty, newSize);
                    }
                }           
            }    
    
            // Use `TextChanged` event to apply the currently selected font size to the entered text
            private void rtb_TextChanged(object sender, TextChangedEventArgs e)
            {
                // Disable recursive calling this event handler 
                rtb.TextChanged -= rtb_TextChanged;
    
                foreach (var change in e.Changes)
                {
                    if (cmbbx != null)
                    {
                        TextPointer tp = rtb.Document.ContentStart.GetPositionAtOffset(change.Offset);
                        var range = new TextRange(tp, tp.GetPositionAtOffset(1, LogicalDirection.Backward));
                        if (!string.IsNullOrEmpty(range.Text) && cmbbx.SelectedValue is double fontsize)
                        {
                            range.ApplyPropertyValue(TextElement.FontSizeProperty, fontsize);
                        }
                    }
                }
                // Restore the event handler
                rtb.TextChanged += rtb_TextChanged;          
            }
    
            // The following `SelectionChanged` event handler is used to set a font size in the combobox 
            // depend on the current caret location.
            private void rtb_SelectionChanged(object sender, RoutedEventArgs e)
            {            
                if (rtb.Selection is TextSelection selection)
                {
                    if (selection.GetPropertyValue(TextElement.FontSizeProperty) is double fsize)
                    {
                        foreach (var it in cmbbx.Items)
                        {
                            if ((double)it == fsize)
                            {
                                // The currently selected document fragment is containing font with size presented in the ComboBox
                                cmbbx.SelectedItem = it;
                                return;
                            }
                        }                  
                    }
                    // The currently selected document fragment contains fonts 
                    // with different font sizes or the font size doesn't included to the the combobox 
                    cmbbx.SelectedItem = null;
                }
            }
        }
    }
    

    I added some notes in the code above. Let me know if an additional descriptions are required.