How to set RichTextBox Font for the next text to be written?

Néstor Sánchez A. picture Néstor Sánchez A. · Dec 6, 2009 · Viewed 8.2k times · Source

I need to set the font family for the next text to be written in a RichTextBox. I tried setting that with...

<RichTextBox x:Name="RichTextEditor" MaxWidth="1000" SpellCheck.IsEnabled="True"
             FontFamily="{Binding ElementName=TextFontComboBox, Path=SelectedItem}"
             FontSize="{Binding ElementName=TextSizeComboBox, Path=SelectedValue}"
             Width="Auto" Height="Auto" HorizontalScrollBarVisibility="Auto" 
VerticalScrollBarVisibility="Auto" />

...but it changed the whole text. I suppose that with the Selection property I can restrict the change to be applied just to the selected area. But how for the next -not yet typed- text?

Answer

Zamboni picture Zamboni · Aug 24, 2010

In order to set the FontFamily based on the cursor position you need to define a custom control with a dependency property that helps insert a new Run section by overriding the OnTextInput method.

I included most of the code, you'll need to modify the namespaces to fit your development environment.

The code uses a ViewModel to manage the available fonts and manage if the font changed. This code is only a prototype and does not deal with focusing issues between the two controls.

To use this code:
1- Type some text in the RichTectBox.
2- Change the font in the ComboBox.
3- Tab back to the RichTextBox.
4- Type some more text.

Here is the custom RichTextBox control:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace RichTextboxFont.Views
{
  public class RichTextBoxCustom : RichTextBox
  {
    public static readonly DependencyProperty CurrentFontFamilyProperty =
            DependencyProperty.Register("CurrentFontFamily", 
            typeof(FontFamily), typeof  
            (RichTextBoxCustom), 
            new FrameworkPropertyMetadata(new FontFamily("Tahoma"), 
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            new PropertyChangedCallback(OnCurrentFontChanged)));

    public FontFamily CurrentFontFamily
    {
       get
       {
         return (FontFamily)GetValue(CurrentFontFamilyProperty);
       }
       set
       {
         SetValue(CurrentFontFamilyProperty, value);
       }
    }

    private static void OnCurrentFontChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {}

    protected override void OnTextInput(TextCompositionEventArgs e)
    {
      ViewModels.MainViewModel mwvm = this.DataContext as ViewModels.MainViewModel;
      if ((mwvm != null) && (mwvm.FontChanged))
      {
        TextPointer textPointer = this.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
        Run run = new Run(e.Text, textPointer);
        run.FontFamily = this.CurrentFontFamily;
        this.CaretPosition = run.ElementEnd;
        mwvm.FontChanged = false;
      }
      else
      {
         base.OnTextInput(e);
      }
    }
  } 
}

Here is the XAML:

<Window x:Class="RichTextboxFont.Views.MainView"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:RichTextboxFont.Views" 
  xmlns:ViewModels="clr-namespace:RichTextboxFont.ViewModels" 
  Title="Main Window" 
  Height="400" Width="800">
  <DockPanel>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <ComboBox ItemsSource="{Binding Path=Fonts}" 
                  SelectedItem="{Binding Path=SelectedFont, Mode=TwoWay}"/>
        <local:RichTextBoxCustom Grid.Row="1" 
                                 CurrentFontFamily="{Binding Path=SelectedFont, Mode=TwoWay}" 
                                 FontSize="30"/>
    </Grid>
  </DockPanel>
</Window>

Here is the ViewModel: If you do not use view models, let me know and I'll add the base class code too; otherwise, google/stackoverflow can help you too.

using System.Collections.ObjectModel;
using System.Windows.Media;

namespace RichTextboxFont.ViewModels
{
  public class MainViewModel : ViewModelBase
  {
    #region Constructor

    public MainViewModel()
    {
       FontFamily f1 = new FontFamily("Georgia");
       _fonts.Add(f1);
       FontFamily f2 = new FontFamily("Tahoma");
       _fonts.Add(f2);
    }

    private ObservableCollection<FontFamily> _fonts = new ObservableCollection<FontFamily>();
    public ObservableCollection<FontFamily> Fonts
    {
      get
      {
         return _fonts;
      }
      set
      {
        _fonts = value;
        OnPropertyChanged("Fonts");
      }
    }

    private FontFamily _selectedFont = new FontFamily("Tahoma");
    public FontFamily SelectedFont
    {
      get
      {
        return _selectedFont;
      }
      set
      {
        _selectedFont = value;
        FontChanged = true;
        OnPropertyChanged("SelectedFont");
      }
    }

    private bool _fontChanged = false;
    public bool FontChanged
    {
      get
      {
         return _fontChanged;
      }
      set
      {
        _fontChanged = value;
        OnPropertyChanged("FontChanged");
      }
    }

  #endregion
  }
}

Here is the Window code-behind where I initialise the ViewModel:

using System.Windows;

namespace RichTextboxFont.Views
{
  public partial class MainView : Window
  {
    public MainView()
    {
      InitializeComponent();
      this.DataContext = new ViewModels.MainViewModel();
    }
  }
}