XamRichTextEditor: Build your own Office Inspired Contextual Rich Text Format Toolbar

Brian Lagunas / Tuesday, April 29, 2014

The xamRichTextEditor was release just over 5 months ago, and it has quickly becoming one of Infragistics’ most popular controls.  For those of you that don’t know about the xamRichTextEditor, it’s a highly customizable rich text editing control that provides functionality modeled after the features and behavior of Microsoft Word.  So imagine taking Microsoft Word, removing all the crap you don’t care about or need, and then making a control out of it.  Well, that’s exactly what we did.

Now that the xamRichTextEditor has been release, the number of feature requests are just pouring in.  Honestly, a lot of the features you are asking for can be built by you, the customer.  I get it; you want these feature built into the control so you don’t have to write them yourself.  The reality is, if you need a feature sooner rather than waiting 6 months for the next release, which there is no guarantee that the feature will even get implemented, then you are better off writing it yourself.  One such feature is a contextual mini toolbar similar to Microsoft Word’s toolbar.

Microsoft Word mini toolbar

This is actually one of the more popular features I have been hearing about.  I know that there is no way I can get this in the next 14.2 release, so I decided I will help you guys get started writing your own Office inspired contextual rich text format toolbar for the xamRichTextEditor.  This will be a very simple example, but will contain all the code required to make it work, and you will easily be able to add more options to your toolbar.

The Solution

Since we are all WPF developers here, we know that the best way to show a control on top of another control is to utilize the Adorner layer.  Now, the AdornerLayer is most possibly the most under utilized feature in WPF.  That’s too bad, because it is extremely powerful and flexible.  In order to show a custom control in the AdornerLayer we need to create a custom Adorner to act as our control host.  For this, I will use a very simple, yet functional, UIElementAdorner class.

///
/// An adorner that can display one and only one UIElement.  
/// That element can be a panel, which contains multiple other elements.
/// The element is added to the adorner's visual and logical trees, enabling it to
/// particpate in dependency property value inheritance, amongst other things.
///
public class UIElementAdorner : Adorner where T : UIElement
{
    #region Fields

    VisualCollection _visuals;
    T _child = null;
    double _offsetLeft = 0;
    double _offsetTop = 0;

    #endregion // Fields

    #region Constructor

    ///
    /// Constructor.
    ///
    /// The element to which the adorner will be bound.
    public UIElementAdorner(UIElement adornedElement)
        : base(adornedElement)
    {
        _visuals = new VisualCollection(this);
    }

    #endregion // Constructor

    #region Properties

    #region Child

    ///
    /// Gets/sets the child element hosted in the adorner.
    ///
    public T Child
    {
        get { return _child; }
        set
        {
            if (value == _child)
                return;

            if (_child != null)
            {
                if (_visuals.Contains(_child))
                    _visuals.Remove(_child);
            }

            _child = value;

            if (_child != null)
            {
                var visualParentAdorner = VisualTreeHelper.GetParent(_child) as UIElementAdorner;
                if (visualParentAdorner != null)
                    visualParentAdorner._visuals.Remove(_child);

                if (!_visuals.Contains(_child))
                    _visuals.Add(_child); ;
            }
        }
    }

    #endregion // Properties

    #region GetDesiredTransform

    public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
    {
        GeneralTransformGroup result = new GeneralTransformGroup();
        result.Children.Add(base.GetDesiredTransform(transform));
        result.Children.Add(new TranslateTransform(_offsetLeft, _offsetTop));
        return result;
    }

    #endregion // GetDesiredTransform

    #region OffsetLeft

    ///
    /// Gets/sets the horizontal offset of the adorner.
    ///
    public double OffsetLeft
    {
        get { return _offsetLeft; }
        set
        {
            _offsetLeft = value;
            UpdateLocation();
        }
    }

    #endregion // OffsetLeft

    #region SetOffsets

    ///
    /// Updates the location of the adorner in one atomic operation.
    ///
    public void SetOffsets(double left, double top)
    {
        _offsetLeft = left;
        _offsetTop = top;
        this.UpdateLocation();
    }

    #endregion // SetOffsets

    #region OffsetTop

    ///
    /// Gets/sets the vertical offset of the adorner.
    ///
    public double OffsetTop
    {
        get { return _offsetTop; }
        set
        {
            _offsetTop = value;
            UpdateLocation();
        }
    }

    #endregion // OffsetTop

    #endregion // Public Interface

    #region Protected Overrides

    protected override Size MeasureOverride(Size constraint)
    {
        if (_child == null)
            return base.MeasureOverride(constraint);

        _child.Measure(constraint);
        return _child.DesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        if (_child == null)
            return base.ArrangeOverride(finalSize);

        _child.Arrange(new Rect(finalSize));
        return finalSize;
    }

    protected override Visual GetVisualChild(int index)
    {
        return _visuals[index];
    }

    protected override int VisualChildrenCount
    {
        get { return _visuals.Count; }
    }

    #endregion // Protected Overrides

    #region Private Helpers

    void UpdateLocation()
    {
        AdornerLayer adornerLayer = base.Parent as AdornerLayer;
        if (adornerLayer != null)
            adornerLayer.Update(base.AdornedElement);
    }

    #endregion // Private Helpers
}

Now that we have our Adorner that will host our Office inspired contextual rich text format mini toolbar control, we need to find a way to control when it shows, where it shows, and when it hides.  To achieve this, we need to create some type of manager that we can attach to any xamRichTextEditor instance.  This is why we will create a class called the RichTextFormatBarManager.  This class will be responsible for showing, hiding, and positioning the mini toolbar in the xamRichTextEditor.  This manager will have to have some knowledge of the mini toolbar we will be using.  It will need to be able to tell the mini toolbar which xamRichTextEditor instance to manipulate, and it will need to tell the mini toolbar when to update the button state for newly selected text.  To achive this, and remain decoupled from other implementations, we will need to use an interface named IRichTextFormatBar.

public interface IRichTextFormatBar
{
    ///
    /// Represents the RichTextBox that will be the target for all text manipulations in the format bar.
    ///
    XamRichTextEditor Target { get; set; }

    ///
    /// Represents the method that will be used to update the format bar values based on the current selection.
    ///
    void UpdateVisualState();
}

Now that we have defined our IRichTextFormatBar, we have what we need to create the RichTextFormatBarManager.

public class RichTextBoxFormatBarManager : DependencyObject
{
    #region Members

    private XamRichTextEditor _richTextBox;
    private UIElementAdorner<Control> _adorner;
    private IRichTextFormatBar _toolbar;

    #endregion //Members

    #region Properties

    public static readonly DependencyProperty FormatBarProperty = DependencyProperty.RegisterAttached("FormatBar", typeof(IRichTextFormatBar), typeof(RichTextBoxFormatBarManager), new PropertyMetadata(null, OnFormatBarPropertyChanged));
    public static void SetFormatBar(UIElement element, IRichTextFormatBar value)
    {
        element.SetValue(FormatBarProperty, value);
    }
    public static IRichTextFormatBar GetFormatBar(UIElement element)
    {
        return (IRichTextFormatBar)element.GetValue(FormatBarProperty);
    }

    private static void OnFormatBarPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var rtb = d as XamRichTextEditor;
        if (rtb == null)
            throw new Exception("A FormatBar can only be applied to a xamRichTextEditor.");

        RichTextBoxFormatBarManager manager = new RichTextBoxFormatBarManager();
        manager.AttachFormatBarToRichtextBox(rtb, e.NewValue as IRichTextFormatBar);
    }

    public bool IsAdornerVisible
    {
        get { return _adorner.Visibility == Visibility.Visible; }
    }

    #endregion //Properties

    #region Event Handlers

    void RichTextBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Left && e.LeftButton == MouseButtonState.Released)
        {
            if (_richTextBox.Selection.Text.Length > 0 && !String.IsNullOrWhiteSpace(_richTextBox.Selection.Text))
                ShowAdorner();
            else
                HideAdorner();

            e.Handled = true;
        }
        else
            HideAdorner();
    }

    void RichTextBox_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        //if the mouse moves outside the richtextbox bounds hide the adorner
        //though this deosn't always work, especially if the user moves the mouse very quickly.
        //need to find a better solution, but this will work for now.
        Point p = e.GetPosition(_richTextBox);
        if (p.X <= 5="" 0="" p="" x="">= _richTextBox.ActualWidth - 5 || p.Y <= 3="" 0="" p="" y="">= _richTextBox.ActualHeight - 3)
            HideAdorner();
    }

    #endregion //Event Handlers

    #region Methods

    ///
    /// Attaches a FormatBar to a RichtextBox
    ///
    /// The RichtextBox to attach to.
    /// The Formatbar to attach.
    private void AttachFormatBarToRichtextBox(XamRichTextEditor richTextBox, IRichTextFormatBar formatBar)
    {
        _richTextBox = richTextBox;
        _richTextBox.MouseLeftButtonUp += RichTextBox_MouseLeftButtonUp;
        _richTextBox.PreviewMouseMove += RichTextBox_PreviewMouseMove;

        _adorner = new UIElementAdorner<Control>(_richTextBox);
        formatBar.Target = _richTextBox;
        _toolbar = formatBar;
    }

    ///
    /// Shows the FormatBar
    ///
    void ShowAdorner()
    {
        if (_adorner.Visibility == Visibility.Visible)
            return;

        VerifyAdornerLayer();

        Control adorningEditor = _toolbar as Control;

        if (_adorner.Child == null)
            _adorner.Child = adorningEditor;

        _toolbar.UpdateVisualState(); //let's tell the toolbar to update it's visual state

        _adorner.Visibility = Visibility.Visible;

        PositionFormatBar(adorningEditor);
    }

    ///
    /// Positions the FormatBar so that is does not go outside the bounds of the RichTextBox or covers the selected text
    ///
    ///
    private void PositionFormatBar(Control adorningEditor)
    {
        Point mousePosition = Mouse.GetPosition(_richTextBox);

        double left = mousePosition.X;
        double top = (mousePosition.Y - 15) - adorningEditor.ActualHeight;

        //top
        if (top < 0)
        {
            top = mousePosition.Y + 10;
        }

        //right boundary
        if (left + adorningEditor.ActualWidth > _richTextBox.ActualWidth - 20)
        {
            left = left - (adorningEditor.ActualWidth - (_richTextBox.ActualWidth - left));
        }

        _adorner.SetOffsets(left, top);
    }

    ///
    /// Ensures that the IRichTextFormatBar is in the adorner layer.
    ///
    /// True if the IRichTextFormatBar is in the adorner layer, else false.
    bool VerifyAdornerLayer()
    {
        if (_adorner.Parent != null)
            return true;

        AdornerLayer layer = AdornerLayer.GetAdornerLayer(_richTextBox);
        if (layer == null)
            return false;

        layer.Add(_adorner);
        return true;
    }

    ///
    /// Hides the IRichTextFormatBar that is in the adornor layer.
    ///
    void HideAdorner()
    {
        if (IsAdornerVisible)
        {
            _adorner.Visibility = Visibility.Collapsed;
        }
    }

    #endregion //Methods
}

Now that we have a manager, we need to create a mini toolbar that will contain all the functions such as Bold, Italic, Underline, and other font options.  You could create an actual custom control, but for our purposes, I will use a simple UserControl.  I’ll name it RichTextFormatBar.xaml.  Let’s start with the View.  We won’t do anything complicated.  Just add a few combo boxes and buttons.  Of course to make applying the styles to the selected text of the xamRichTextEditor easy, we will use simple commands.  For the font family, and font size, we will have to handle some events and manually set the values.  No big deal though.  It’s super easy to do.

<UserControl x:Class="xamRichTextEditorOfficeFormatToolbar.RichTextFormatBar"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Background="Transparent"
             IsTabStop="False"
             x:Name="_window">
    <UserControl.Effect>
        <DropShadowEffect BlurRadius="5" Opacity=".25" />
    UserControl.Effect>

    <UserControl.Resources>
        
        <Style TargetType="{x:Type Separator}" BasedOn="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}"/>

        <ControlTemplate x:Key="ThumbControlTemplate" TargetType="{x:Type Thumb}">
            <Border Background="Transparent" Cursor="Hand" ToolTip="Click to Drag">
                <StackPanel VerticalAlignment="Center" Width="75">
                    <Line SnapsToDevicePixels="True" Stretch="Fill" StrokeDashArray="1,2" StrokeThickness="1" X1="0" X2="1" Margin=".5" >
                        <Line.Stroke>
                            <SolidColorBrush Color="Gray" />
                        Line.Stroke>
                    Line>
                    <Line SnapsToDevicePixels="True" Stretch="Fill" StrokeDashArray="1,2" StrokeThickness="1" X1="0" X2="1" Margin=".5">
                        <Line.Stroke>
                            <SolidColorBrush Color="Gray" />
                        Line.Stroke>
                    Line>
                    <Line SnapsToDevicePixels="True" Stretch="Fill" StrokeDashArray="1,2" StrokeThickness="1" X1="0" X2="1" Margin=".5">
                        <Line.Stroke>
                            <SolidColorBrush Color="Gray" />
                        Line.Stroke>
                    Line>
                StackPanel>
            Border>
        ControlTemplate>

        <SolidColorBrush x:Key="MouseOverBorderBrush" Color="#FFFFB700" />
        <LinearGradientBrush x:Key="MouseOverBackgroundBrush" StartPoint="0,0" EndPoint="0,1" >
            <GradientStop Offset="0" Color="#FFFEFBF4" />
            <GradientStop Offset="0.19" Color="#FFFDE7CE" />
            <GradientStop Offset="0.39" Color="#FFFDDEB8" />
            <GradientStop Offset="0.39" Color="#FFFFCE6B" />
            <GradientStop Offset="0.79" Color="#FFFFDE9A" />
            <GradientStop Offset="1" Color="#FFFFEBAA" />
        LinearGradientBrush>

        <SolidColorBrush x:Key="CheckedBorderBrush" Color="#FFC29B29" />
        <LinearGradientBrush x:Key="CheckedBackgroundBrush" StartPoint="0,0" EndPoint="0,1" >
            <GradientStop Offset="0" Color="#FFFFDCA0" />
            <GradientStop Offset="0.18" Color="#FFFFD692" />
            <GradientStop Offset="0.39" Color="#FFFFC45D" />
            <GradientStop Offset="1" Color="#FFFFD178" />
        LinearGradientBrush>

        <SolidColorBrush x:Key="PressedBorderBrush" Color="#FFC29B29" />
        <LinearGradientBrush x:Key="PressedBackgroundBrush" StartPoint="0,0" EndPoint="0,1" >
            <GradientStop Offset="0" Color="#FFE3C085" />
            <GradientStop Offset="0.19" Color="#FFF4CC89" />
            <GradientStop Offset="0.36" Color="#FFF5C777" />
            <GradientStop Offset="0.36" Color="#FFF5BB56" />
            <GradientStop Offset="0.79" Color="#FFF4CE9A" />
            <GradientStop Offset="1" Color="#FFF3E28D" />
        LinearGradientBrush>

        <Style x:Key="FormatBarToggleButtonStyle" TargetType="{x:Type ToggleButton}">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="Height" Value="22" />
            <Setter Property="HorizontalContentAlignment" Value="Center"/>
            <Setter Property="ToolTipService.InitialShowDelay" Value="900"/>
            <Setter Property="ToolTipService.ShowDuration" Value="20000"/>
            <Setter Property="ToolTipService.BetweenShowDelay" Value="0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ToggleButton}">
                        <Grid SnapsToDevicePixels="True">
                            <Border x:Name="OuterBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="2"/>
                            <Border x:Name="MiddleBorder" BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" Background="Transparent" CornerRadius="2">
                                <Border x:Name="InnerBorder" BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" Background="Transparent" CornerRadius="2" Padding="{TemplateBinding Padding}">
                                    <StackPanel x:Name="StackPanel" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}">
                                        <ContentPresenter x:Name="Content" Content="{TemplateBinding Content}" Margin="1"
                                           RenderOptions.BitmapScalingMode="NearestNeighbor"
                                           VerticalAlignment="Center"
                                           HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
                                    StackPanel>
                                Border>
                            Border>
                        Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" TargetName="OuterBorder" Value="{StaticResource MouseOverBackgroundBrush}"/>
                                <Setter Property="BorderBrush" TargetName="OuterBorder" Value="{StaticResource MouseOverBorderBrush}"/>
                                <Setter Property="BorderBrush" TargetName="InnerBorder" Value="#80FFFFFF"/>
                            Trigger>

                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Opacity" TargetName="Content" Value="0.5"/>
                                <Setter Property="TextElement.Foreground" TargetName="OuterBorder" Value="#FF9E9E9E"/>
                            Trigger>

                            <Trigger Property="IsChecked" Value="True">
                                <Setter Property="Background" TargetName="OuterBorder" Value="{StaticResource CheckedBackgroundBrush}"/>
                                <Setter Property="BorderBrush" TargetName="OuterBorder" Value="{StaticResource CheckedBorderBrush}"/>
                                <Setter Property="BorderBrush" TargetName="InnerBorder">
                                    <Setter.Value>
                                        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                                            <GradientStop Color="#FFE7CBAD" Offset="0"/>
                                            <GradientStop Color="#FFF7D7B5" Offset="0.1"/>
                                            <GradientStop Color="#FFFFD38C" Offset="0.36"/>
                                            <GradientStop Color="#FFFFC75A" Offset="0.36"/>
                                            <GradientStop Color="#FFFFEFA5" Offset="1"/>
                                        LinearGradientBrush>
                                    Setter.Value>
                                Setter>
                            Trigger>

                            <Trigger Property="IsPressed" Value="True">
                                <Setter Property="Background" TargetName="OuterBorder" Value="{StaticResource PressedBackgroundBrush}"/>
                                <Setter Property="BorderBrush" TargetName="OuterBorder" Value="{StaticResource PressedBorderBrush}"/>
                                <Setter Property="BorderBrush" TargetName="InnerBorder" Value="Transparent"/>
                            Trigger>

                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="True"/>
                                    <Condition Property="IsMouseOver" Value="True"/>
                                MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="MiddleBorder">
                                    <Setter.Value>
                                        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                                            <GradientStop Color="#40FFFEFE" Offset="0"/>
                                            <GradientStop Color="#40FFFEFE" Offset="0.39"/>
                                            <GradientStop Color="#20FFCE68" Offset="0.39"/>
                                            <GradientStop Color="#20FFCE68" Offset="0.69"/>
                                            <GradientStop Color="#10FFFFFF" Offset="1"/>
                                        LinearGradientBrush>
                                    Setter.Value>
                                Setter>
                            MultiTrigger>
                        ControlTemplate.Triggers>
                    ControlTemplate>
                Setter.Value>
            Setter>
        Style>

    UserControl.Resources>

    <Border CornerRadius="3" BorderThickness="1" BorderBrush="Gray" Background="WhiteSmoke">
        <Grid Margin="5,0,5,5">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            Grid.RowDefinitions>

            <Thumb x:Name="_dragWidget" Height="10"
                   Template="{StaticResource ThumbControlTemplate}"
                   DragDelta="DragWidget_DragDelta"/>

            <StackPanel Grid.Row="1">
                <StackPanel Orientation="Horizontal">
                    <ComboBox x:Name="_cmbFontFamilies" IsEditable="True" Width="100"
                              SelectionChanged="FontFamily_SelectionChanged"
                              ToolTip="Font Family"/>

                    <ComboBox x:Name="_cmbFontSizes" IsEditable="True" Width="43"
                              SelectionChanged="FontSize_SelectionChanged"
                              ToolTip="Font Size"/>

                StackPanel>

                <StackPanel Orientation="Horizontal" Margin="0,3,0,0">
                    <ToggleButton x:Name="_btnBold" Style="{StaticResource FormatBarToggleButtonStyle}"
                                Command="EditingCommands.ToggleBold" CommandTarget="{Binding ElementName=_window, Path=Target}"
                                  ToolTip="Bold">
                        <Image Source="/xamRichTextEditorOfficeFormatToolbar;component/Images/Bold16.png" />
                    ToggleButton>
                    <ToggleButton x:Name="_btnItalic" Style="{StaticResource FormatBarToggleButtonStyle}"
                                  Command="{x:Static EditingCommands.ToggleItalic}" CommandTarget="{Binding ElementName=_window, Path=Target}"
                                  ToolTip="Italic">
                        <Image Source="/xamRichTextEditorOfficeFormatToolbar;component/Images/Italic16.png" />
                    ToggleButton>
                    <ToggleButton x:Name="_btnUnderline" Style="{StaticResource FormatBarToggleButtonStyle}"
                                  Command="{x:Static EditingCommands.ToggleUnderline}" CommandTarget="{Binding ElementName=_window, Path=Target}"
                                  ToolTip="Underline">
                        <Image Source="/xamRichTextEditorOfficeFormatToolbar;component/Images/Underline16.png" />
                    ToggleButton>

                    <Separator />

                    <RadioButton x:Name="_btnAlignLeft" Style="{StaticResource FormatBarToggleButtonStyle}"
                                 Command="{x:Static EditingCommands.AlignLeft}" CommandTarget="{Binding ElementName=_window, Path=Target}"
                                 ToolTip="Align Left">
                        <Image Source="/xamRichTextEditorOfficeFormatToolbar;component/Images/LeftAlign16.png" />
                    RadioButton>
                    <RadioButton x:Name="_btnAlignCenter" Style="{StaticResource FormatBarToggleButtonStyle}"
                                 Command="{x:Static EditingCommands.AlignCenter}" CommandTarget="{Binding ElementName=_window, Path=Target}"
                                 ToolTip="Align Center">
                        <Image Source="/xamRichTextEditorOfficeFormatToolbar;component/Images/CenterAlign16.png" />
                    RadioButton>
                    <RadioButton x:Name="_btnAlignRight" Style="{StaticResource FormatBarToggleButtonStyle}"
                                 Command="{x:Static EditingCommands.AlignRight}" CommandTarget="{Binding ElementName=_window, Path=Target}"
                                 ToolTip="Align Right">
                        <Image Source="/xamRichTextEditorOfficeFormatToolbar;component/Images/RightAlign16.png" />
                    RadioButton>

                StackPanel>
            StackPanel>
        Grid>
    Border>

UserControl>

Now, we need to make sure we are populating the font names and sizes, as well as updating the state of the buttons whenever the IRichTextFormatBar.UpdateVisualState method is called.

///
/// Interaction logic for RichTextFormatBar.xaml
///
public partial class RichTextFormatBar : UserControl, IRichTextFormatBar
{
    bool _updatingVisualState;

    #region Properties

    public static double[] FontSizes
    {
        get
        {
            return new double[] {
                3.0, 4.0, 5.0, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5,
                10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 15.0,
                16.0, 17.0, 18.0, 19.0, 20.0, 22.0, 24.0, 26.0, 28.0, 30.0,
                32.0, 34.0, 36.0, 38.0, 40.0, 44.0, 48.0, 52.0, 56.0, 60.0, 64.0, 68.0, 72.0, 76.0,
                80.0, 88.0, 96.0, 104.0, 112.0, 120.0, 128.0, 136.0, 144.0
                };
        }
    }

    public static readonly DependencyProperty TargetProperty = DependencyProperty.Register("Target", typeof(XamRichTextEditor), typeof(RichTextFormatBar));
    public XamRichTextEditor Target
    {
        get { return (XamRichTextEditor)GetValue(TargetProperty); }
        set { SetValue(TargetProperty, value); }
    }

    #endregion //Properties

    #region Ctor

    public RichTextFormatBar()
    {
        InitializeComponent();

        _cmbFontFamilies.ItemsSource = Fonts.SystemFontFamilies.ToList().Select(x => x.Source);
        _cmbFontSizes.ItemsSource = FontSizes;
    }

    #endregion //Ctor

    #region Events

    private void DragWidget_DragDelta(object sender, DragDeltaEventArgs e)
    {
        ProcessMove(e);
    }

    private void FontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count == 0 || _updatingVisualState)
            return;

        var font = e.AddedItems[0];
        Target.Selection.ApplyFont(new Infragistics.Documents.RichText.RichTextFont(font.ToString()));
    }

    private void FontSize_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count == 0 || _updatingVisualState)
            return;

        var size = (double)e.AddedItems[0];
        Target.Selection.ApplyFontSize(size);
    }

    #endregion //Events

    #region Methods

    ///
    /// Processes the dragging of the RichTextFormatBar
    ///
    private void ProcessMove(DragDeltaEventArgs e)
    {
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(Target);
        UIElementAdorner<Control> adorner = layer.GetAdorners(Target)[0] as UIElementAdorner<Control>;
        adorner.SetOffsets(adorner.OffsetLeft + e.HorizontalChange, adorner.OffsetTop + e.VerticalChange);
    }

    ///
    /// Updates the visual state of the buttons and other tools of the RichTextFormatBar
    ///
    public void UpdateVisualState()
    {
        _updatingVisualState = true;

        DocumentSpan documentSpan = Target.Selection == null ? new DocumentSpan(0, 0) : Target.Selection.DocumentSpan;
        var settings = Target.Document.GetCommonCharacterSettings(documentSpan);
        UpdateSelectedFontFamily(settings);
        UpdateSelectedFontSize(settings);

        UpdateBoldState(settings);
        UpdateItalicState(settings);
        UpdateUnderlineState(settings);

        UpdateParagraphAlignmentState(documentSpan);

        _updatingVisualState = false;
    }

    ///
    /// Updates the selected item in the font family list.
    ///
    void UpdateSelectedFontFamily(CharacterSettings settings)
    {
        if (settings.FontSettings != null)
        {
            _cmbFontFamilies.SelectedValue = (settings.FontSettings.Ascii.HasValue && !string.IsNullOrWhiteSpace(settings.FontSettings.Ascii.Value.Name)) ? settings.FontSettings.Ascii.Value.Name : "Arial";
        }
    }

    ///
    /// Updates the selected item in the font list.
    ///
    void UpdateSelectedFontSize(CharacterSettings settings)
    {
        _cmbFontSizes.SelectedValue = settings.FontSize.HasValue ? settings.FontSize.Value.Points : 12.0;
    }

    ///
    /// Updates the state of the bold button
    ///
    void UpdateBoldState(CharacterSettings settings)
    {
        UpdateToggleButtonCheckedState(_btnBold, settings.Bold);
    }

    ///
    /// Updates the state of the italic button
    ///
    void UpdateItalicState(CharacterSettings settings)
    {
        UpdateToggleButtonCheckedState(_btnItalic, settings.Italics);
    }

    ///
    /// Updates the state of the underline button
    ///
    void UpdateUnderlineState(CharacterSettings settings)
    {
        if (settings.UnderlineType.HasValue)
            UpdateToggleButtonCheckedState(_btnUnderline, settings.UnderlineType.Value != UnderlineType.None);
    }

    ///
    /// Updates the state of the paragraph alignment buttons
    ///
    void UpdateParagraphAlignmentState(DocumentSpan documentSpan)
    {
        ParagraphSettings paragraphSettings = Target.Document.GetCommonParagraphSettings(documentSpan);
        if (paragraphSettings.ParagraphAlignment.HasValue)
        {
            var value = paragraphSettings.ParagraphAlignment.Value;
            switch (value)
            {
                case ParagraphAlignment.Start:
                    {
                        UpdateToggleButtonCheckedState(_btnAlignLeft, true);
                        break;
                    }
                case ParagraphAlignment.Center:
                    {
                        UpdateToggleButtonCheckedState(_btnAlignCenter, true);
                        break;
                    }
                case ParagraphAlignment.End:
                    {
                        UpdateToggleButtonCheckedState(_btnAlignRight, true);
                        break;
                    }
            }
        }
    }

    ///
    /// Updates the visual state of a toggle button.
    ///
    /// The button.
    /// The formatting property.
    /// The expected value.
    void UpdateToggleButtonCheckedState(ToggleButton button, bool? value)
    {
        button.IsChecked = value.HasValue ? value.Value : false;
    }

    #endregion //Methods
}

Now that we have all that finished, we need to find a way to attach this cool new mini toolbar to any xamRichTextEditor instance.  Luckily, we are experienced WPF developers and now the best mechanism to attach objects to a control is to use either a Behavior, or an AttachedProperty.  Well, for this example, we will use an Attached property to attach our RichTextFormatBarManager object to a xamRichTExtEditor instance.  You might have already noticed this property defined in the RichTextFormatBarManager class.  The AttachedProperty is called FormatBar.  So how do we use all this code we wrote?  Simple!  Create a View, don’t forget to add a namespace for our toolbar stuff, and then attached a new toolbar instance to a xamRichTExtEditor using the FormatBar AttachedProperty.  Like this:

<Window x:Class="xamRichTextEditorOfficeFormatToolbar.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ig="http://schemas.infragistics.com/xaml"
        xmlns:local="clr-namespace:xamRichTextEditorOfficeFormatToolbar"
        Title="MainWindow" Height="350" Width="525">
    <Grid>

        <ig:XamRichTextEditor>
            <local:RichTextBoxFormatBarManager.FormatBar>
                <local:RichTextFormatBar />
            local:RichTextBoxFormatBarManager.FormatBar>
        ig:XamRichTextEditor>

    Grid>
Window>

Now run the application, and start typing some text.  Highlight some text and BAM; the rich text mini toolbar appears.  Now you can start applying various rich text formatting options to the selected text.

xamRichTextEditor Office inspired rich text format mini toolbar

That’s all there is to it.  Download the source code, and start playing around with it to make it fit your application needs.  Be sure to let me know if this was useful to you, and I always love to hear about how my samples are used in the real world.  As always, feel free contact me on my blog, connect with me on Twitter (@brianlagunas), or leave a comment below for any questions or comments you may have.