Silverlight Text Editor Sample

Microsoft Silverlight will reach end of support after October 2021. Learn more.

This topic provides an overview of a Silverlight Text Editor sample. Using this sample, you can type and edit rich text, and perform several operations. The operations include formatting text, inserting images, inserting calendars, inserting data grids, displaying text right-to-left, printing, and accessing the clipboard. This sample is based on the RichTextBox control in Silverlight 4 or later. The following illustration shows how this sample looks:

Silverlight Text Editor

To try this sample, click the following link.

Run this sample

Download this sample

Prerequisites

You need the following components to build the Silverlight Text Editor:

  • Silverlight 4 or later.

  • Silverlight Tools for Visual Studio 2010.

  • Visual Studio 2010.

All of the Silverlight software is available from the Silverlight download site.

Silverlight Text Editor UI

The Silverlight Text Editor user interface broadly consists of the following three sections:

  • A Grid named LayoutRoot that occupies the entire browser. It has the Silverlight Logo and the sample area. The sample area consists of the following sections:

  • A Grid named ToolBarGrid that hosts the horizontal toolbar. The toolbar has a set of buttons that lets you perform various operations on the RichTextBox.

  • A Grid named RTBGrid that hosts the rich text editor. The editor uses a RichTextBox control where you can input and edit rich content.

    Silverlight Text Editor User Interface

Toolbar

The toolbar is hosted within a Grid named ToolBarGrid. The ToolBarGrid has two rows and seven columns. The first row does not have any children, and is used to add padding to the second row. The second row hosts the toolbar buttons. Each column is used to host a toolbar section, for example, Clipboard, Font and so on.

Consider one toolbar section as an example. The second column on the ToolBarGrid (Grid.Column = 1) hosts the Font section of the toolbar. This section consists of seven controls. The following table lists the parts of the Font section and the control that is used to create each part.

Part

Control Used

Font name

ComboBox

Font size

ComboBox

Bold formatting

Button

Italic formatting

Button

Underline formatting

Button

Font color

ComboBox

Section title

TextBlock

The font name and font size ComboBox controls are positioned by setting an appropriate margin value. The Button controls and the font color ComboBox are positioned by using a horizontally oriented StackPanel layout. The section tile TextBlock is positioned with an appropriate margin value. The following XAML shows the Font section of the toolbar.

<!--Fonts Toolbar Section-->
<ComboBox x:Name="cmbFonts" ...
    <ComboBoxItem Content="Arial" ...
    <ComboBoxItem Content="Arial Black" ...
    ...
</ComboBox>

<ComboBox x:Name="cmbFontSizes" …
    <ComboBoxItem Content="8" Tag="8"/>
    <ComboBoxItem Content="9" Tag="9"/>
    ...
</ComboBox>

<StackPanel Grid.Column="1" Grid.Row="1" ...
    <!--Buttons-->
    <Button x:Name="btnBold" ...
    <Button x:Name="btnItalic" ...
    <Button x:Name="btnUnderline" ...
    <ComboBox x:Name="cmbFontColors" ...
        <ComboBoxItem Tag="FFFF0000">
        <ComboBoxItem Tag="FF008000">
        ...
    </ComboBox>
</StackPanel>

Editor

The editor is hosted within a Grid named RTBGrid. The following table lists the child elements of RTBGrid.

Child Element

Name

Description

Rectangle

Used as a border around the RichTextBox.

RichTextBox

rtb

Used to input and edit rich content.

Canvas

highlightCanvas

Used when you click the Highlight button in the Toolbar.

TextBox

xamlTB

Used to display the XAML when you click the XAML button in the Toolbar. This TextBox is hidden by default and becomes visible only when you click the XAML button in the toolbar.

The following XAML shows RTBGrid.

<Grid Name="RTBGrid" Grid.Row="1">
    <Rectangle Fill="White" Margin="0,1,1,0">
        <Rectangle.Effect>
            <DropShadowEffect ShadowDepth="1" Direction="371" BlurRadius="7" Opacity="0.345"/>
        </Rectangle.Effect>
    </Rectangle>
    <RichTextBox x:Name="rtb" AllowDrop="True" BorderBrush="{x:Null}" Margin="8,10,0,8" MouseRightButtonDown="rtb_MouseRightButtonDown" MouseRightButtonUp="rtb_MouseRightButtonUp" Drop="rtb_Drop" MouseMove="rtb_MouseMove" DragEnter="rtb_DragEnter" DragLeave="rtb_DragLeave" TextWrapping="Wrap" Style="{StaticResource RichTextBoxStyle1}" VerticalScrollBarVisibility="Auto" FontSize="20" />
    <Canvas x:Name="highlightCanvas" IsHitTestVisible="False" Margin="8,10,0,8"/>
    <TextBox x:Name="xamlTb" IsTabStop="False" FontSize="20" FontFamily="Lucida Console" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Visibility="Collapsed"/>
</Grid>

Silverlight Text Editor Features

The Silverlight Text Editor sample allows you to perform various rich text editing operations that are implemented by using some of the new features in Silverlight 4. The following sections explain these features.

Initialization

When you run the Silverlight Text Editor sample, it is loaded with an initial content. This content is stored in the XAML format in the sample.sav file. The sample.sav file is added as a resource to the project and is loaded using the RichTextBox.Xaml property. The following code shows how the .sav file is loaded.

//Initialize the RichTextBox. The intial text is saved as XAML inthe Hamlet.docx file.
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    rtb.Xaml = XElement.Load("/RichNotepad;component/sample.sav").ToString();
}

Clipboard

You can cut, copy, or paste the selected text in the editor by using the Cut, Copy, or Paste buttons in the Clipboard section of the toolbar. This is implemented by using the new Clipboard object in Silverlight 4, as shown in the following code.

//Cut the selected text
private void btnCut_Click(object sender, RoutedEventArgs e)
{
    Clipboard.SetText(rtb.Selection.Text);
    rtb.Selection.Text = "";
    ReturnFocus();
}

//Copy the selected text
private void btnCopy_Click(object sender, RoutedEventArgs e)
{
    Clipboard.SetText(rtb.Selection.Text);
    ReturnFocus();
}

//paste the text
private void btnPaste_Click(object sender, RoutedEventArgs e)
{
    rtb.Selection.Text = Clipboard.GetText();
    ReturnFocus();
}

Context Menu

You can also cut, copy, and paste the selected text by using a context menu. The context menu appears when you right-click in the editor. Right-click mouse events are a new feature in Silverlight 4. The following illustration shows the context menu.

Context Menu

The context menu is implemented by using the Popup control. The logic for the context menu is primarily in the ContextMenu.cs and RTBContextMenu.cs files of the project.

ContextMenu is an abstract class that provides the basic functionality to create, display and close a context menu. You can derive from this base class to create the desired context menu. The ContextMenu class provides the following functionalities:

  • Defines an abstract method named GetContent.

    The derived class must implement the GetContent method to create the desired content of the context menu. The GetContent method returns a FrameworkElement object that has the content of the context menu.

    //Abstract function that the child class needs to implement to return the framework element that needs to be displayed in the popup window.
    protected abstract FrameworkElement GetContent();
    
  • Constructs and displays the context menu.

    The logic for constructing and displaying the context menu is performed in the Show method of the ContextMenu class. In the Show method, a Popup control is instantiated and its size is set to the width and height of the sample. The FrameworkElement that is returned by the GetContent method is added as a child of the Popup control. The Show method takes the location at which the context menu must be displayed as a parameter. This location is set as the margin of the FrameworkElement in the pop up control. The following code shows how the Show method is implemented.

    //Intiialize and show the popup window. This is the public method to call, to display the ContextMenu at the desired location. 
    public void Show(Point location)
    {
        if (_isShowing)
            throw new InvalidOperationException();
    
        _isShowing = true;
        _location = location;
        ConstructPopup();
        _popup.IsOpen = true;
    }
    
  • Closes the context menu.

  • Provides an event handler for closing the context menu when you click outside the context menu.

The RTBContextMenu class derives from ContextMenu class and does the following:

  • Implements the GetContent method that was defined in the ContextMenu class. The GetContent method constructs and returns a FrameworkElement. This is then used in the Show method of the ContextMenu class to construct the context menu. The following code displays the GetContent method implemented by the RTBContextMenu class.

    //Construct the context menu and return the parent FrameworkElement. 
    protected override FrameworkElement GetContent()
    {
        Border border = new Border() { BorderBrush = new SolidColorBrush(Color.FromArgb(255, 167,171,176)), BorderThickness = new Thickness(1), Background = new SolidColorBrush(Colors.White) };
        border.Effect = new DropShadowEffect() { BlurRadius = 3, Color = Color.FromArgb(255, 230, 227, 236) };
    
        Grid grid = new Grid() { Margin = new Thickness(1) };
        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(25) });
        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(105) });
    
        grid.Children.Add(new Rectangle() { Fill = new SolidColorBrush(Color.FromArgb(255, 233, 238, 238)) });
        grid.Children.Add(new Rectangle() { Fill = new SolidColorBrush(Color.FromArgb(255, 226, 228, 231)), HorizontalAlignment = HorizontalAlignment.Right, Width = 1 });
    
        //cut
        Button cutButton = new Button() { Height = 22, Margin = new Thickness(0, 0, 0, 0), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Top, HorizontalContentAlignment = HorizontalAlignment.Left };
        cutButton.Style = Application.Current.Resources["ContextMenuButton"] as Style;
        cutButton.Click += cut_MouseLeftButtonUp;
        Grid.SetColumnSpan(cutButton, 2);
    
        StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal };
    
        Image cutImage = new Image() { HorizontalAlignment = HorizontalAlignment.Left, Width = 16, Height = 16, Margin = new Thickness(1, 0, 0, 0) };
        cutImage.Source = new BitmapImage(new Uri("/RichNotepad;component/images/cut.png", UriKind.RelativeOrAbsolute));
        sp.Children.Add(cutImage);
    
        TextBlock cutText = new TextBlock() { Text = "Cut", HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(16, 0, 0, 0) };
        sp.Children.Add(cutText);
    
        cutButton.Content = sp;
    
        grid.Children.Add(cutButton);
    
        //copy
        Button copyButton = new Button() { Height = 22, Margin = new Thickness(0, 24, 0, 0), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Top, HorizontalContentAlignment = HorizontalAlignment.Left };
        copyButton.Style = Application.Current.Resources["ContextMenuButton"] as Style;
        copyButton.Click += copy_MouseLeftButtonUp;
        Grid.SetColumnSpan(copyButton, 2);
    
        sp = new StackPanel() { Orientation = Orientation.Horizontal };
    
        Image copyImage = new Image() { HorizontalAlignment = HorizontalAlignment.Left, Width = 16, Height = 16, Margin = new Thickness(1, 0, 0, 0) };
        copyImage.Source = new BitmapImage(new Uri("/RichNotepad;component/images/copy.png", UriKind.RelativeOrAbsolute));
        sp.Children.Add(copyImage);
    
        TextBlock copyText = new TextBlock() { Text = "Copy", HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(16, 0, 0, 0) };
        sp.Children.Add(copyText);
    
        copyButton.Content = sp;
    
        grid.Children.Add(copyButton);          
    
        //paste
        Button pasteButton = new Button() { Height = 22, Margin = new Thickness(0, 48, 0, 0), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Top, HorizontalContentAlignment = HorizontalAlignment.Left };
        pasteButton.Style = Application.Current.Resources["ContextMenuButton"] as Style;
        pasteButton.Click += paste_MouseLeftButtonUp;
        Grid.SetColumnSpan(pasteButton, 2);
    
        sp = new StackPanel() { Orientation = Orientation.Horizontal };
    
        Image pasteImage = new Image() { HorizontalAlignment = HorizontalAlignment.Left, Width = 16, Height = 16, Margin = new Thickness(1, 0, 0, 0) };
        pasteImage.Source = new BitmapImage(new Uri("/RichNotepad;component/images/paste.png", UriKind.RelativeOrAbsolute));
        sp.Children.Add(pasteImage);
    
        TextBlock pasteText = new TextBlock() { Text = "Paste", HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(16, 0, 0, 0) };
        sp.Children.Add(pasteText);
    
        pasteButton.Content = sp;
    
        grid.Children.Add(pasteButton);
    
        border.Child = grid;
    
        return border;
    }
    
  • Handles the MouseLeftButtonUp event for the cut, copy, and paste operations when the corresponding buttons are clicked in the context menu. The following code shows the event handlers for the cut, copy, and paste operations.

    //handle the cut, copy and paste actions when the appropriate button on the context menu is clicked. 
    void cut_MouseLeftButtonUp(object sender, RoutedEventArgs e)
    {
        Clipboard.SetText(rtb.Selection.Text);
        rtb.Selection.Text = "";
        Close();
    }
    
    void copy_MouseLeftButtonUp(object sender, RoutedEventArgs e)
    {
        Clipboard.SetText(rtb.Selection.Text);
        Close();
    }
    
    void paste_MouseLeftButtonUp(object sender, RoutedEventArgs e)
    {
        rtb.Selection.Text = Clipboard.GetText();
        Close();
    }
    

Font

The Silverlight Text Editor sample allows you to apply bold, italic, underline, font type, font size, and font color formatting to selected text. To apply font formatting, you use the TextSelection.ApplyPropertyValue method on the selected text in the RichTextBox with the appropriate dependency property and property value. The following table summarizes the dependency property and the property value that you can use to apply the font formatting.

Font Formatting

Dependency Property

Property Value

Bold

FontWeightProperty

Use the FontWeights.Bold property to apply bold formatting to selected text. Use FontWeights.Normal to remove bold formatting from selected text.

Italic

FontStyleProperty

Use the FontStyles.Italic property to apply italic formatting to selected text.

Use FontStyles.Normal to remove italic formatting from selected text.

Underline

TextDecorationsProperty

Use the TextDecorations.Underline property to apply underline formatting to selected text.

Set TextDecorationsProperty to null to remove underline formatting from selected text.

Font type

FontFamilyProperty

Use a FontFamily object constructed with the desired font name to specify the font.

Font size

FontSizeProperty

Use a double value to specify the font size.

Font color

ForegroundProperty

Use a SolidColorBrush object to specify the desired color.

The following code shows how the font formatting is applied in the Silverlight Text Editor sample.

//Set Bold formatting to the selected text 
private void btnBold_Click(object sender, RoutedEventArgs e)
{
    if (rtb != null)
    {
        if (rtb.Selection.GetPropertyValue(Run.FontWeightProperty) is FontWeight && ((FontWeight)rtb.Selection.GetPropertyValue(Run.FontWeightProperty)) == FontWeights.Normal)
            rtb.Selection.ApplyPropertyValue(Run.FontWeightProperty, FontWeights.Bold);
        else
            rtb.Selection.ApplyPropertyValue(Run.FontWeightProperty, FontWeights.Normal);
    }
    ReturnFocus();
}

//Set Italic formatting to the selected text 
private void btnItalic_Click(object sender, RoutedEventArgs e)
{
    if (rtb != null)
    {
        if (rtb.Selection.GetPropertyValue(Run.FontStyleProperty) is FontStyle && ((FontStyle)rtb.Selection.GetPropertyValue(Run.FontStyleProperty)) == FontStyles.Normal)
            rtb.Selection.ApplyPropertyValue(Run.FontStyleProperty, FontStyles.Italic);
        else
            rtb.Selection.ApplyPropertyValue(Run.FontStyleProperty, FontStyles.Normal);
    }
    ReturnFocus();
}

//Set Underline formatting to the selected text 
private void btnUnderline_Click(object sender, RoutedEventArgs e)
{
    if (rtb != null)
    {
        if (rtb.Selection.GetPropertyValue(Run.TextDecorationsProperty) == null)
            rtb.Selection.ApplyPropertyValue(Run.TextDecorationsProperty, TextDecorations.Underline);
        else
            rtb.Selection.ApplyPropertyValue(Run.TextDecorationsProperty, null);
    }
    ReturnFocus();

}


...


//Set font type to selected text
private void cmbFonts_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (rtb != null)
    {
        rtb.Selection.ApplyPropertyValue(Run.FontFamilyProperty, new FontFamily((cmbFonts.SelectedItem as ComboBoxItem).Tag.ToString()));
    }
    ReturnFocus();
}

//Set font size to selected text
private void cmbFontSizes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (rtb != null)
    {
        rtb.Selection.ApplyPropertyValue(Run.FontSizeProperty, double.Parse((cmbFontSizes.SelectedItem as ComboBoxItem).Tag.ToString()));
    }
    ReturnFocus();
}

//Set font color to selected text
private void cmbFontColors_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (rtb != null)
    {
        string color = (cmbFontColors.SelectedItem as ComboBoxItem).Tag.ToString();

        SolidColorBrush brush = new SolidColorBrush(Color.FromArgb(
            byte.Parse(color.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
            byte.Parse(color.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
            byte.Parse(color.Substring(4, 2), System.Globalization.NumberStyles.HexNumber),
            byte.Parse(color.Substring(6, 2), System.Globalization.NumberStyles.HexNumber)));

        rtb.Selection.ApplyPropertyValue(Run.ForegroundProperty, brush);
    }
    ReturnFocus();
}

Insert

You can insert elements, such as images, hyperlinks, data grids, and calendars, into the Silverlight Text Editor sample.

The Insert Image button inserts an image at the current location in the editor. The image is first created by using an Image object that points to a URI specifying the location of the image. It is then inserted into the RichTextBox by using a InlineUIContainer. This sample inserts an image named desert.jpg that is stored as a project resource. However, you can insert any other image by passing in the appropriate URI.

The following code shows how an image is inserted.

'Insert an image into the RichTextBox
Private Sub btnImage_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim container As InlineUIContainer = New InlineUIContainer
    container.Child = MainPage.createImageFromUri(New Uri("/RichNotepad;component/images/Desert.jpg", UriKind.RelativeOrAbsolute), 200, 150)
    rtb.Selection.Insert(container)
    'ReturnFocus()
End Sub

Private Shared Function createImageFromUri(ByVal URI As Uri, ByVal width As Double, ByVal height As Double) As Image
    Dim img As Image = New Image
    img.Stretch = Stretch.Uniform
    img.Width = width
    img.Height = height
    Dim bi As BitmapImage = New BitmapImage(URI)
    img.Source = bi
    img.Tag = bi.UriSource.ToString
    Return img
End Function
//Insert an image into the RichTextBox
private void btnImage_Click(object sender, RoutedEventArgs e)
{
    InlineUIContainer container = new InlineUIContainer();

    container.Child = MainPage.createImageFromUri(new Uri("/RichNotepad;component/images/Desert.jpg", UriKind.RelativeOrAbsolute), 200, 150);

    rtb.Selection.Insert(container);
    ReturnFocus();
}

private static Image createImageFromUri(Uri URI, double width, double height)
{
    Image img = new Image();
    img.Stretch = Stretch.Uniform;
    img.Width = width;
    img.Height = height;
    BitmapImage bi = new BitmapImage(URI);
    img.Source = bi;
    img.Tag = bi.UriSource.ToString();
    return img;
}

The process for inserting any other UIElement in to a RichTextBox is the same as the process described for inserting an Image. This sample uses the same process for inserting DataGrid and Calendar objects.

The Insert Hyperlink button allows you to insert hyperlinks into the editor. A hyperlink is inserted by using the Hyperlink element. The NavigateUri property is used to specify the URI. When you click the Insert Hyperlink button, the Insert URL dialog box is displayed as shown in the following illustration.

Insert Hyperlink

The Insert URL dialog box is a ChildWindow control that allows you to enter the Target URL and URL Description for the hyperlink. The following code shows how the hyperlink insertion is implemented.

//Insert a hyperlink
private void btnHyperlink_Click(object sender, RoutedEventArgs e)
{
    InsertURL cw = new InsertURL(rtb.Selection.Text);
    cw.HasCloseButton = false;

    //Hook up an event handler to the Closed event on the ChildWindows cw. 
    cw.Closed += (s, args) =>
    {
        if (cw.DialogResult.Value)
        {
            Hyperlink hyperlink = new Hyperlink();
            hyperlink.TargetName = "_blank";
            hyperlink.NavigateUri = new Uri(cw.txtURL.Text);

            if (cw.txtURLDesc.Text.Length > 0)
                hyperlink.Inlines.Add(cw.txtURLDesc.Text);
            else
                hyperlink.Inlines.Add(cw.txtURL.Text);

            rtb.Selection.Insert(hyperlink);
            ReturnFocus();
        }
    };
    cw.Show();
}

When you click the OK or Cancel button in the Insert URL dialog box, the Target URL value is validated. This validation is performed in the Closing event handler. The validation is performed in the Closing event instead of the Closed event because this allows you to keep the dialog box open if the validation fails. If the URI is valid, a new Hyperlink object is created and inserted at the current selection in the RichTextBox.

Print

You can print the contents of the editor by using the Print button. Printing is a new feature in Silverlight 4. When you click the Print button, a Print Preview dialog box is displayed. This Print Preview dialog box is a ChildWindow object that has an image of the content to be printed. The following illustration shows the Print Preview dialog box.

Print Preview

This image is created with an WriteableBitmap object that takes the RichTextBox object as a parameter. The following shows the XAML for creating the Print Preview dialog box and the code for creating the WriteableBitmap image.

<!-- NOTE: 
  By convention, the sdk prefix indicates a URI-based XAML namespace declaration 
  for Silverlight SDK client libraries. This namespace declaration is valid for 
  Silverlight 4 only. In Silverlight 3, you must use individual XAML namespace 
  declarations for each CLR assembly and namespace combination outside the scope 
  of the default Silverlight XAML namespace. For more information, see the help 
  topic "Prefixes and Mappings for Silverlight Libraries". 
-->
    <sdk:ChildWindow x:Class="RichNotepad.PrintPreview"
           xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
           Width="400" Height="300"
           Title="Print Preview" FontFamily="Calibri" FontSize="16">

  <Grid x:Name="LayoutRoot" Margin="2">
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
        <Border BorderThickness="0.5" BorderBrush="#666" Background="White">
            <ScrollViewer>
                <Image x:Name="previewImage" Stretch="Uniform" />
            </ScrollViewer>
            </Border>
        <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
    <Button x:Name="OKButton" Content="Print" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />     
    </Grid>
</sdk:ChildWindow>
public void ShowPreview(RichTextBox rtb)
{            
    WriteableBitmap image = new WriteableBitmap(rtb, null);
    previewImage.Source = image;            
}

When you click the OK button in the Print Preview dialog box, its contents are printed by using the PrintDocument object, as shown in the following code.

//Print the document
private void btnPrint_Click(object sender, RoutedEventArgs e)
{
    PrintPreview cw = new PrintPreview();
    cw.ShowPreview(rtb);
    cw.HasCloseButton = false;

    //Hook up a handler to the Closed event before displaying the PrintPreview window by calling the Show() method.
    cw.Closed += (t, a) =>
    {
        if (cw.DialogResult.Value)
        {
            PrintDocument theDoc = new PrintDocument();
            theDoc.PrintPage += (s, args) =>
            {
                args.PageVisual = rtb;
                args.HasMorePages = false;
            };

            theDoc.EndPrint += (s, args) =>
            {
                MessageBox.Show("The document printed successfully", "Text Editor", MessageBoxButton.OK);
            };

            theDoc.Print("Silverlight 4 Text Editor");
            ReturnFocus();
        }
    };
    cw.Show();
}

For more information about printing in Silverlight, see Printing.

Display

The Silverlight Text Editor sample supports right-to-left layout, which is a new feature in Silverlight 4. This is to support languages like Arabic and Hebrew. The following illustration shows the Silverlight Text Editor sample in the right-to-left layout.

Right to left layout

You can use the Text Direction toggle button to switch between the left-to-right and right-to-left layouts. The following code shows how the FlowDirection object is used to achieve this.

//Set the flow direction
public void btnRTL_Checked(object sender, RoutedEventArgs e)
{
    //Set the button image based on the state of the toggle button. 
    if(btnRTL.IsChecked.Value)
        btnRTL.Content = MainPage.createImageFromUri(new Uri("/RichNotepad;component/images/rtl.png", UriKind.RelativeOrAbsolute), 30, 32);
    else
        btnRTL.Content = MainPage.createImageFromUri(new Uri("/RichNotepad;component/images/ltr.png", UriKind.RelativeOrAbsolute), 30, 32);

    ApplicationBorder.FlowDirection = (ApplicationBorder.FlowDirection == System.Windows.FlowDirection.LeftToRight) ? System.Windows.FlowDirection.RightToLeft : System.Windows.FlowDirection.LeftToRight;
    ReturnFocus();

}

You can use the Edit/View Mode toggle button to switch between the edit and read-only states of the editor. By default, the editor is in the edit mode. The following code shows how the edit/read-only mode is implemented.

//Make the RichTextBox read-only
public void btnRO_Checked(object sender, RoutedEventArgs e)
{
    rtb.IsReadOnly = !rtb.IsReadOnly;

    //Set the button image based on the state of the toggle button.
    if (rtb.IsReadOnly)
        btnRO.Content = MainPage.createImageFromUri(new Uri("/RichNotepad;component/images/view.png", UriKind.RelativeOrAbsolute), 29, 32);
    else
        btnRO.Content = MainPage.createImageFromUri(new Uri("/RichNotepad;component/images/edit.png", UriKind.RelativeOrAbsolute), 29, 32);
    ReturnFocus();
}

You specify read-only mode by setting the IsReadOnly property of RichTextBox to true. UI elements and hyperlinks in a RichTextBox are active only in read-only mode. For example, they can respond to input and receive focus only when they are in read-only mode. For more information about the read-only mode for RichTextBox, see RichTextBox Overview.

Highlight

When the Highlight toggle button is in the checked state, the entire line that the mouse pointer is positioned over is highlighted. The following illustration shows an example of the highlight.

Highlight

The following code shows how the highlight is implemented by using the TextPointer class.

public void btnHighlight_Checked(object sender, RoutedEventArgs e)
{
    if (btnHighlight.IsChecked.Value)
    {
        TextPointer tp = rtb.ContentStart;
        TextPointer nextTp = null;
        Rect nextRect = Rect.Empty;
        Rect tpRect = tp.GetCharacterRect(LogicalDirection.Forward);
        Rect lineRect = Rect.Empty;

        int lineCount = 1;

        while (tp != null)
        {
            nextTp = tp.GetNextInsertionPosition(LogicalDirection.Forward);
            if (nextTp != null && nextTp.IsAtInsertionPosition)
            {
                nextRect = nextTp.GetCharacterRect(LogicalDirection.Forward);
                // this occurs for more than one line
                if (nextRect.Top > tpRect.Top)
                {
                    if (m_selectionRect.Count < lineCount)
                        m_selectionRect.Add(lineRect);
                    else
                        m_selectionRect[lineCount - 1] = lineRect;

                    lineCount++;

                    if (m_selectionRect.Count < lineCount)
                        m_selectionRect.Add(nextRect);

                    lineRect = nextRect;
                }
                else if (nextRect != Rect.Empty)
                {
                    if (tpRect != Rect.Empty)
                        lineRect.Union(nextRect);
                    else
                        lineRect = nextRect;
                }
            }
            tp = nextTp;
            tpRect = nextRect;
        }
        if (lineRect != Rect.Empty)
        {
            if (m_selectionRect.Count < lineCount)
                m_selectionRect.Add(lineRect);
            else
                m_selectionRect[lineCount - 1] = lineRect;
        }
        while (m_selectionRect.Count > lineCount)
        {
            m_selectionRect.RemoveAt(m_selectionRect.Count - 1);
        }
    }
    else
    {
        if (highlightRect != null)
        {
            highlightRect.Visibility = System.Windows.Visibility.Collapsed;
        }
    }

}

When the highlight toggle button is checked, the event handler enumerates through the contents of the RichTextBox and constructs a rectangle that bounds every line. The rectangles are added to a list. The following steps show how the list of all rectangles is created.

  1. Obtain a TextPointer object that points to the start of the RichTextBox content by using the ContentStart property.

  2. Get the bounding rectangle for the first character by using the GetCharacterRect method.

  3. Get the bounding rectangle for the first line. This is done by enumerating through the content by using the GetNextInsertionPosition method. Get the TextPointer object in each position. Check to see whether you have gone to the next line by comparing the Top point coordinates of the rectangles obtained by using the start of the line TextPointer and the current TextPointer. In the case where you cross over to the next line, construct the rectangle for the current line and add it to the list before starting the same enumeration for the next line.

  4. Repeat the previous step until all lines are processed.

You now have a list of rectangles that corresponds to each line of the content.

To determine which line to highlight, the code performs a hit test for the current mouse position against the list of rectangles in the MouseMove event handler for the RichTextBox. The code highlights a line by displaying a rectangle on the Canvas named highlightCanvas.

XAML

You can use the XAML button to display the XAML markup of the content in the editor. The RichTextBox.Xaml property is used for this. The following code shows how the XAML is displayed by using the Xaml property.

'Set the xamlTb TextBox with the current XAML of the RichTextBox and make it visible. Any changes to the XAML made 
'in xamlTb is also reflected back on the RichTextBox. Note that the Xaml string returned by RichTextBox.Xaml will 
'not include any UIElement contained in the current RichTextBox. Hence the UIElements will be lost when you 
'set the Xaml back again from the xamlTb to the RichTextBox.

Public Sub btnMarkUp_Checked(ByVal sender As Object, ByVal e As RoutedEventArgs)
    If btnMarkUp.IsChecked.Value Then
        xamlTb.Visibility = System.Windows.Visibility.Visible
        xamlTb.IsTabStop = True
        xamlTb.Text = rtb.Xaml
    Else
        rtb.Xaml = xamlTb.Text
        xamlTb.Visibility = System.Windows.Visibility.Collapsed
        xamlTb.IsTabStop = False

    End If
End Sub
//Set the xamlTb TextBox with the current XAML of the RichTextBox and make it visible. Any changes to the XAML made 
//in xamlTb is also reflected back on the RichTextBox. Note that the Xaml string returned by RichTextBox.Xaml will 
//not include any UIElement contained in the current RichTextBox. Hence the UIElements will be lost when we 
//set the Xaml back again from the xamlTb to the RichTextBox.
public void btnMarkUp_Checked(object sender, RoutedEventArgs e)
{
    if (btnMarkUp.IsChecked.Value)
    {
        xamlTb.Visibility = System.Windows.Visibility.Visible;
        xamlTb.IsTabStop = true;
        xamlTb.Text = rtb.Xaml;
    }
    else
    {
        rtb.Xaml = xamlTb.Text;
        xamlTb.Visibility = System.Windows.Visibility.Collapsed;
        xamlTb.IsTabStop = false;
    }

}

When you click the XAML button, the XAML markup is displayed in a TextBox named xamlTb. The Visibility property of xamlTb is Collapsed by default, and is changed to Visible when you click the XAML button. If you make any changes to the XAML in xamlTb, it is reflected on the content in the RichTextBox. Note that the XAML string returned by the Xaml property will not include any UIElement objects that are contained in the RichTextBox. So, the UIElement objects will be lost when you switch back from the XAML view to the RichTextBox.

File

When you click the New File button, the existing content in the RichTextBox is cleared. The following code shows the process for creating a new file.

//Clears the contents of the existing file.
private void btnNew_Click(object sender, RoutedEventArgs e)
{
    rtb.Blocks.Clear();
}

You can open an existing file by using the Open File button. The following code shows the process of opening an existing file.

//Opens an existing file
private void btnOpen_Click(object sender, RoutedEventArgs e)
{
    OpenFileDialog ofd = new OpenFileDialog();
    ofd.Multiselect = false;
    ofd.Filter = "Saved Files|*.sav|All Files|*.*";

    if (ofd.ShowDialog().Value)
    {
        FileInfo fi = ofd.File;
        StreamReader r = fi.OpenText();
        rtb.Xaml = r.ReadToEnd();
        r.Close();
    }
}

When you click the Open File button, an OpenFileDialog object is created and is displayed by using the ShowDialog method. This is the standard Windows open file dialog box that you can use to select the file you want to open. The selected file is opened as a StreamReader and its contents are assigned to the Xaml property of the RichTextBox.

You can save the existing file by using the File Save button. If the RichTextBox contains any UIElement objects, the save operation does save these. You cannot save UIElement objects because the Xaml property of the RichTextBox includes only text content and associated markup. The following code shows how the current contents are saved.

//Saves the existing file
private void btnSave_Click(object sender, RoutedEventArgs e)
{
    string ContentToSave = rtb.Xaml;

    //Check if the file contains any UIElements
    var res = from block in rtb.Blocks
              from inline in (block as Paragraph).Inlines
              where inline.GetType() == typeof(InlineUIContainer)
              select inline;

    //If the file contains any UIElements, it will not be saved
    if (res.Count() != 0)
    {
        MessageBox.Show("Saving documents with UIElements is not supported");
        return;
    }

    SaveFileDialog sfd = new SaveFileDialog();
    sfd.DefaultExt = ".sav";
    sfd.Filter = "Saved Files|*.sav|All Files|*.*";

    if (sfd.ShowDialog().Value)
    {
        using (FileStream fs = (FileStream)sfd.OpenFile())
        {
            System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
            byte[] buffer = enc.GetBytes(ContentToSave);
            fs.Write(buffer, 0, buffer.Length);
            fs.Close();
        }
    }
}

The code first enumerates through the contents of the RichTextBox and checks for any UIElement objects. If the RichTextBox contents contains any UIElement objects, the save operation is terminated and a message box is displayed. If the RichTextBox has only text elements, a SaveFileDialog is created and is displayed by using the ShowDialog method. This is the standard Windows save file dialog box that you can use to save a file. The content in the RichTextBox is retrieved as a string by using the Xaml property and then written to a FileStream object.

Drag-and-Drop

You can open text and Office Word files in the Silverlight Text Editor sample by dragging them into the content area. This feature is implemented by using the new drag-and-drop feature in Silverlight 4. The following code shows the event handler method for the drop event.

private void rtb_Drop(object sender, System.Windows.DragEventArgs e)
{
    VisualStateManager.GoToState(this, "Normal", true);

    //the Drop event passes in an array of FileInfo objects for the list of files that were selected and drag-dropped onto the RichTextBox.
    if (e.Data == null)
    {
        ReturnFocus();
        return;
    }

    //This checks if the dropped objects are files and if not, return. 
    IDataObject f = e.Data as IDataObject;

    if (f == null)
    {
        ReturnFocus();
        return;
    }

    object data = f.GetData(DataFormats.FileDrop);
    FileInfo[] files = data as FileInfo[];

    if (files == null)
    {
        ReturnFocus();
        return;
    }

    //Walk through the list of FileInfo objects of the selected and drag-dropped files and parse the .txt and .docx files 
    //and insert their content in the RichTextBox.
    foreach (FileInfo file in files)
    {
        if (file == null)
        {
            continue;
        }

        if (file.Extension.Equals(".txt"))
        {
            ParseTextFile(file);
        }
        else if (file.Extension.Equals(".docx"))
        {
            ParseDocxFile(file);
        }
    }
    ReturnFocus();
}

In the Drop event handler (rtb_Drop), the list of FileInfo objects for the files that were dropped on the RichTextBox are passed in the event argument. The rtb_Drop event handler enumerates the FileInfo objects and parses the .txt and .docx files. Files with other extensions are ignored. The ParseTxtFile and ParseDocxFile functions (not displayed in this topic) take the FileInfo object, parse it and add its contents to the RichTextBox.