views:

146

answers:

3

Hi, 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?

A: 

This isn't exactly a trivial answer.

To do inline text formatting in a Rich TextBox like you want you will have to modify the Document property of the RichTextBox. Very simply, something like this will work

<RichTextBox >
  <RichTextBox.Document>
    <FlowDocument>
      <Paragraph>
        <Run>Something</Run>
        <Run FontWeight="Bold">Something Else</Run>
      </Paragraph>
    </FlowDocument>
  </RichTextBox.Document>
</RichTextBox>  

I think you could create a custom Control that creates a new block element and sets the font properties you need based on the user input.

For example, If the user types something then presses bold. You would want to wrap the previous text in a run and create a new run element setting the FontWeight to bold then the subsequent text will be wrapped in the bolded run.

Again, not a trivial solution but I can't think of any other way to accomplish what you are after.

Foovanadil
+2  A: 

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();
    }
  }
}
Zamboni
Very Nice solution.
Avatar
I think using an MVVM approach is too complicated. You can solve this problem in a few lines of code using an event handler in the code behind.
dthrasher
You give me the key part: Override the OnTextInput method. Thanks!
Néstor Sánchez A.
+1  A: 

There's a much easier way to do this: Implement a toolbar for your RichTextBox.

Unlike WinForms, the RichTextBox in WPF doesn't come with a toolbar by default, but it's really easy to create one yourself. The RichTextBox automatically handles many EditingCommands, so it's just a matter of creating a toolbar and some buttons. Microsoft has provided sample code for this at the bottom of the RichTextBox Overview on MSDN.

Unfortunately, those editing commands don't include setting the FontFace property of the selection, though you can create a ComboBox on the toolbar that can trigger the change with an event handler in the codebehind file.

That's the approach taken in this CodePlex article by Gregor Pross: WPF RichTextEditor

The project is commented in German, but the source itself is very clearly written. The codebehind used for his font selector ComboBox looks like this:

    private void Fonttype_DropDownClosed(object sender, EventArgs e)
    {            
        string fontName = (string)Fonttype.SelectedItem;

        if (fontName != null)
        {                
            RichTextControl.Selection.ApplyPropertyValue(System.Windows.Controls.RichTextBox.FontFamilyProperty, fontName);
            RichTextControl.Focus();
        }
    }

The main reason that people struggle with the FontFace selection is that after the font selection has been made, you must return focus to the RichTextBox. If the user must manually press tab or click into the RichTextBox, a new text selection gets created and you lose the formatting options you've chosen.

One of the answers to this StackOverflow question discusses that problem. http://stackoverflow.com/questions/674384/wpf-richtextbox-fontface-fontsize

dthrasher
That is the easy part. When there exist a selected text to apply a font or other format property.The complex part is when no selection is present.
Néstor Sánchez A.