Click here to Skip to main content
15,390,470 members
Articles / Desktop Programming / WPF
Tip/Trick
Posted 27 May 2013

Tagged as

Stats

33.3K views
1.8K downloads
15 bookmarked

WPF RichTextBox supporting smileys

Rate me:
Please Sign up or sign in to vote.
4.67/5 (8 votes)
28 May 2013CPOL2 min read
The article describes the way to create an extended RichTextBox which supports smileys.

Introduction 

An editor supporting smileys is a very important feature nowadays. Any chat editor should render smileys from a piece of text. For example, Smile | <img src= denotes a smiling face. This article helps you to create an extended rich text editor which supports smileys.

Image 2

Background 

Before going to create an extended editor, it is a must to understand the structure of the WPF RichTextBox. The WPF editor usually works on flow document objects. In order to replace a text in RTB with a UI container say image in our case, we should iterate all words first. 

Basically we need a helper method which takes the TextPointer position as an argument and returns the word start and end positions. The following scenarios need to be considered on word iteration:

  1. If the TextPointer is within a word or at start of a word boundary, the containing word will be returned.
  2. If the TextPointer is between two words, the following (next) word will be returned.
  3. If the TextPointer is at trailing word boundary, the following (next) word will be returned.  

It is convenient to return the word start and end positions as a TextRange object. Also, our implementation of GetWordRange will return the range covering just the word, excluding any extra trailing whitespaces.

C#
public static TextRange GetWordRange(TextPointer position)
{
    TextRange wordRange = null;
    TextPointer wordStartPosition = null;
    TextPointer wordEndPosition = null;
    // Go forward first, to find word end position.
    wordEndPosition = GetPositionAtWordBoundary(
      position, /*wordBreakDirection*/LogicalDirection.Forward);
    if (wordEndPosition != null)
    {
        // Then travel backwards, to find word start position.
        wordStartPosition = GetPositionAtWordBoundary(
          wordEndPosition, /*wordBreakDirection*/LogicalDirection.Backward);
    }
    if (wordStartPosition != null)
    {
        wordRange = new TextRange(wordStartPosition, wordEndPosition);
    }
    return wordRange;
} 

More details: http://blogs.msdn.com/b/prajakta/archive/2006/11/01/navigate-words-in-richtextbox.aspx 

Using the code 

Our extended editor will contain a collection property of type EmoticonMapper. It has a Text property and a ImageSource property. The text denotes the piece of text from which the smiley should render. ImageSource denotes the corresponding icon for the text. 

C#
public class EmoticonMapper
{
    public ImageSource Icon { get; set; }
    public string Text { get; set; }
}

The usage of this collection property in XAML will look like below:

XML
<local:RichTextBoxExt Margin="5">
    <local:RichTextBoxExt.Emoticons>
        <local:EmoticonMapper Text=":)" Icon="Smileys/1.png"/>
        <local:EmoticonMapper Text=":-c" Icon="Smileys/101.png"/>
        <local:EmoticonMapper Text="B-)" Icon="Smileys/16.png"/>
        <local:EmoticonMapper Text=":D" Icon="Smileys/4.png"/>
        <local:EmoticonMapper Text=":(" Icon="Smileys/2.png"/>
    </local:RichTextBoxExt.Emoticons>
</local:RichTextBoxExt>  

Our control will look for a word matching with any of the given text and render a corresponding smiley at the cursor position. The look up will trigger on the OnTextChanged override. So for every text change the following method will be invoked:

C#
private void UpdateSmileys()
{
    var tp = Document.ContentStart;
    var word = WordBreaker.GetWordRange(tp);
    while (word.End.GetNextInsertionPosition(LogicalDirection.Forward) != null)
    {
        word = WordBreaker.GetWordRange(
          word.End.GetNextInsertionPosition(LogicalDirection.Forward));
        var smileys = from smiley in Emoticons
                      where smiley.Text == word.Text
                      select smiley;
        var emoticonMappers = smileys as IList<EmoticonMapper> ?? smileys.ToList();
        if (emoticonMappers.Any())
        {
            var emoticon = emoticonMappers.FirstOrDefault();
            var img = new Image(){Stretch = Stretch.None};
            if (emoticon != null) img.Source = emoticon.Icon;
            ReplaceTextRangeWithImage(word, img);
        }
    }
} 

We iterate through all the words in the editor and look for a text exactly matching any of the emoticon mapper text. Once we find such a word, an image will be created and inserted at the position of the text. The ReplacingTextRangeWithImage method is used to insert the smiley image at the particular text range.

C#
public void ReplaceTextRangeWithImage(TextRange textRange, Image image)
{
    if (textRange.Start.Parent is Run)
    {
        var run = textRange.Start.Parent as Run;
        var runBefore =
            new Run(new TextRange(run.ContentStart, textRange.Start).Text);
        var runAfter =
            new Run(new TextRange(textRange.End, run.ContentEnd).Text);

        if (textRange.Start.Paragraph != null)
        {
            textRange.Start.Paragraph.Inlines.Add(runBefore);
            textRange.Start.Paragraph.Inlines.Add(image);
            textRange.Start.Paragraph.Inlines.Add(runAfter);
            textRange.Start.Paragraph.Inlines.Remove(run);
        }
        CaretPosition = runAfter.ContentEnd;
    }
} 

Performance 

Triggering look up on text changed will kill the typing speed. So I suggest to start look up on idle time. A dispatcher timer is used, which will re-start at text changed with a 0.5 seconds interval. It allows the user to type faster and never start the look up. Once the user is idle for 0.5 seconds, it will start look up in the entire editor. The interval can be adjusted if more performance is needed. 

C#
protected override void OnTextChanged(TextChangedEventArgs e)
{
    //Looking for an idle time to start look up..
    if (_timer == null)
    {
        _timer = new DispatcherTimer(DispatcherPriority.Background);
        _timer.Interval = TimeSpan.FromSeconds(0.5);
        _timer.Tick += LookUp;
    }
    //Restart timer here...
    _timer.Stop();
    _timer.Start();
    base.OnTextChanged(e);
}  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jawahar Suresh Babu
Software Developer (Senior)
India India
Jawahar working as a Senior Development Engineer in Aditi Technologies,Bangalore, India. Specialist in all XAML frameworks. Very passionate on UX Design and Development. Skilled in Expression Blend, Design, WPF, Silverlight, Windows Phone 7/8, Windows 8. Good knowledge in Entity Framework, SQLite and SQL Server also. Also had good experience with PRISM, MVVM, Caliiburn Micro and other design patterns.

He developed few products for Syncfusion Inc. Also working on some freelancing projects. Worked as a lead developer of Metro Studio from Syncfusion Inc.

An active freelancer. http://xamlfactory.elance.com

http://about.me/jawahars

http://wpfplayground.com/

Comments and Discussions

 
QuestionSingle smiley with no text is not being converted to image Pin
verbov6-Oct-15 12:45
Memberverbov6-Oct-15 12:45 
QuestionThank you very much Pin
Anas Tamemy7-Apr-14 23:38
MemberAnas Tamemy7-Apr-14 23:38 
QuestionInserted at the cursor position,but smiley updated at the end of content Pin
idave@ys23-Aug-13 17:54
Memberidave@ys23-Aug-13 17:54 
BugRe: Inserted at the cursor position,but smiley updated at the end of content Pin
User 232205812-Sep-13 13:02
MemberUser 232205812-Sep-13 13:02 
GeneralMy vote of 5 Pin
Henrique Clausing12-Jun-13 3:03
MemberHenrique Clausing12-Jun-13 3:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.