# .NET and More > WPF, WCF, WF >  WPF RichTextBox Question

## Tewl

Recently I have decided to switch from the Forms RichTextBox to the WPF RichTextBox for my chat client. I've been reading up on applying properties to the text. I've found that I can use 2 methods to doing this.

Method 1:


```
TextRange tr = new TextRange(rtb.Document.ContentEnd, rtb.Document.ContentEnd);
tr.Text = "Some Text Here";
tr.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Black);
```

Method 2:


```
Run r = new Run("Some Text Here");
r.FontFamily = new System.Windows.Media.FontFamily("Tahoma");
r.FontSize = 12;
r.FontStyle = System.Windows.FontStyles.Normal;
r.FontWeight = System.Windows.FontWeights.Regular;
r.Foreground = System.Windows.Media.Brushes.SteelBlue;
para.Inlines.Add(r);
```

At the moment I am using method 2. The server my client works uses a style-like tags around text to specify the color, font, weight, ect. So for each group of text in a style tag I create and add a new run object to the paragraph. My problem comes when I need to loop through the run to set hyperlinks and insert emoticons. Since all the text is not in a single run object I find that if a user uses a gradient (text fader) the style tags may open or close inside of a hyperlink or the emoticons text.

Eg. [style co:#D6D6D6]www.some[/style]site.com

"www.some" would be a run object and "site.com" would be another.

What I need to do is to figure how to either apply multiple properties to different segments of a run object or find a way to join multiple run objects. Unless there is some other way I have overlooked. I am still very new to WPF. 

Here is a post with the method I use to add emoticons that shows how I loop through the run object.

Help with embedding images into a RichTextbox

----------


## DeanMc

Your best bet is to pull all the content out into an array list or list of strings do the formatting their and then build a flow document (which is what the RTB uses) each time. That way your RUN's are re writen each time. Also rather than loop through the runs you could just apply some REGEX queries to replace each smilie code with the inline code for the given smilie. Im in work at the moment but I will post some code on how to do that later (around 7pm) for ya!

----------


## DeanMc

Ok so I need to find out what exactly which way you are doing what your doing. So in your application do I type into a text box and then you update the RTB? Give me a run through and Il give you a hand?

----------


## Tewl

On self input yes. Say I type in "Hello! :P" and my font I have selected is Tahoma and the color is black it will pass the folowing string to be formatted to the rtb.

[style co:#000000;b;]Tewl : [/style][style ff:Tahoma;co:#000000]Hello! :P[/style]

----------


## DeanMc

Ok sorry this took so long, I was playing with code. Now if you are using an RTB for your input box and another RTB for your "display" you do not need to use style tags or anything else for that matter. Consider that a flow document is simply a Xaml rendering control. This means that its content is all written in Xaml. Xaml is of course text. So if we use this nifty piece of code which is basically your method 1 extended:



```
        Dim tr As New TextRange(PasteBox.Document.ContentStart, PasteBox.Document.ContentEnd)
        Dim ms As New MemoryStream
        tr.Save(ms, DataFormats.Xaml)
        Dim TextArray As String = ASCIIEncoding.Default.GetString(ms.ToArray())
```

and text array now contains an array of your text with the Xaml mark-up included as a plain string. the content is already grouped with a section object from what I can gather from tests. So you would simply need to add this array as a section to your display window.

Now because you have the FULL power of flow documents you can now do something that most messengers cant do. you can make fantasticly rich content. so smilies embedded video, fancy lists! This all of course depends on your server construction but it is all technically possible. have fun with it!

----------


## Tewl

Thanks

----------


## DeanMc

Does it work for you?

----------


## Tewl

I'm kinda of lost as to how to insert images and clickable urls with this method

----------


## DeanMc

This is the flow document class. If you look towards the end you will see the different classes you can use with a flow document. The ones you need to look at is link and inline ui container.

----------


## Tewl

I was having an issue understanding the TextPointers but I think I have it now.



```
        private void button1_Click(object sender, EventArgs e)
        {
            TextRange tr = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
            TextPointer stp = rtb.Document.ContentStart.GetPositionAtOffset(tr.Text.Length - 2);

            tr = new TextRange(rtb.Document.ContentEnd, rtb.Document.ContentEnd);
            tr.Text = "\rSome :) text here";
            tr.ApplyPropertyValue(TextElement.ForegroundProperty, System.Windows.Media.Brushes.Black);
            tr = new TextRange(rtb.Document.ContentEnd, rtb.Document.ContentEnd);
            tr.Text = "\rSome more text :) here";
            tr.ApplyPropertyValue(TextElement.ForegroundProperty, System.Windows.Media.Brushes.Red);
            tr = new TextRange(rtb.Document.ContentEnd, rtb.Document.ContentEnd);
            tr.Text = "\rmore text here :)";
            tr.ApplyPropertyValue(TextElement.ForegroundProperty, System.Windows.Media.Brushes.SteelBlue);
           
            BitmapImage bi;

            tr = new TextRange(stp, rtb.Document.ContentEnd);

            string emoticonText = GetEmoticonText(tr.Text);

            TextPointer tp = stp;

            while (emoticonText != string.Empty)
            {
                while (!tp.GetTextInRun(LogicalDirection.Forward).StartsWith(emoticonText))
                    tp = tp.GetNextInsertionPosition(LogicalDirection.Forward);

                tr = new TextRange(tp, tp.GetPositionAtOffset(emoticonText.Length));

                tr.Text = string.Empty;

                bi = new BitmapImage();
                bi.BeginInit();
                bi.UriSource = new Uri(mappings[emoticonText]);
                bi.DecodePixelWidth = 26;
                bi.EndInit();

                System.Windows.Controls.Image image = new System.Windows.Controls.Image();
                image.Width = 26;
                image.Stretch = System.Windows.Media.Stretch.None;
                image.Source = bi;

                new InlineUIContainer(image, tp);

                tr = new TextRange(tp, rtb.Document.ContentEnd);

                if (tr.Text == string.Empty)
                    break;
                else
                    emoticonText = GetEmoticonText(tr.Text);
            }
        }

        private string GetEmoticonText(string text)
        {
            string match = string.Empty;
            int lowestPosition = text.Length;

            foreach (KeyValuePair<string, string> pair in mappings)
            {
                if (text.Contains(pair.Key))
                {
                    int newPosition = text.IndexOf(pair.Key);

                    if (newPosition < lowestPosition)
                    {
                        match = pair.Key;
                        lowestPosition = newPosition;
                    }
                }
            }
            return match;
        }
```

I am curious as to why this returns a length of 2 when the RTB is empty.

TextRange tr = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
MessageBox.Show(tr.Text.Lengh.ToString());

Other than that, now all I have to do is write a function to convert urls tp clickable hyperlinks.

----------


## Tewl

Ok my next issue with this is putting in the Hyperlinks



```
            string uriText = GetUriText(tr.Text);

            Hyperlink h;

            while (uriText != string.Empty)
            {
                while (!tp.GetTextInRun(LogicalDirection.Forward).StartsWith(uriText))
                    tp = tp.GetNextInsertionPosition(LogicalDirection.Forward);

                h = new Hyperlink(tp, tp.GetPositionAtOffset(uriText.Length));

                tp = tp.GetPositionAtOffset(uriText.Length);

                tr = new TextRange(tp, rtb.Document.ContentEnd);

                if (tr.Text != string.Empty)
                    uriText = GetUriText(tr.Text);
            }
```



```
        private string GetUriText(string text)
        {
            string match = string.Empty;
            Regex r = new Regex("(?:^|[\\s\\[\\]\\}\\{\\(\\)\\\'\\\"<>])((?:(?:https?|gopher|ftp|file|irc):\\/\\/|www\\.)[a-zA-Z0-9\\.\\-=;&%\\?]+(?:\\/?[a-zA-Z0-9\\.\\-=;&%\\?]*)*)");
            if (r.IsMatch(text))
            {
                Match m = r.Match(text);
                match = m.Groups[1].Value;
            }
            return match;
        }
```

After I run this I get a squiggly line under the url but it is not clickable nor does it fire any events when I add a click event to the hyperlink object.

----------


## Tewl

Screenshot: Removed bandwidth issue

----------


## DeanMc

Hmmmm, something is breaking your Links alright. You need to pull the xaml out and have a look to see if they are being created properly.

----------


## Tewl

hmmm. I'm kind of lost as to what to do at this point.



```
<Section xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve" TextAlignment="Left" LineHeight="Auto" IsHyphenationEnabled="False" xml:lang="en-us" FlowDirection="LeftToRight" NumberSubstitution.CultureSource="User" NumberSubstitution.Substitution="AsCulture" FontFamily="Microsoft Sans Serif" FontStyle="Normal" FontWeight="Normal" FontStretch="Normal" FontSize="11" Foreground="#FF000000" Typography.StandardLigatures="True" Typography.ContextualLigatures="True" Typography.DiscretionaryLigatures="False" Typography.HistoricalLigatures="False" Typography.AnnotationAlternates="0" Typography.ContextualAlternates="True" Typography.HistoricalForms="False" Typography.Kerning="True" Typography.CapitalSpacing="False" Typography.CaseSensitiveForms="False" Typography.StylisticSet1="False" Typography.StylisticSet2="False" Typography.StylisticSet3="False" Typography.StylisticSet4="False" Typography.StylisticSet5="False" Typography.StylisticSet6="False" Typography.StylisticSet7="False" Typography.StylisticSet8="False" Typography.StylisticSet9="False" Typography.StylisticSet10="False" Typography.StylisticSet11="False" Typography.StylisticSet12="False" Typography.StylisticSet13="False" Typography.StylisticSet14="False" Typography.StylisticSet15="False" Typography.StylisticSet16="False" Typography.StylisticSet17="False" Typography.StylisticSet18="False" Typography.StylisticSet19="False" Typography.StylisticSet20="False" Typography.Fraction="Normal" Typography.SlashedZero="False" Typography.MathematicalGreek="False" Typography.EastAsianExpertForms="False" Typography.Variants="Normal" Typography.Capitals="Normal" Typography.NumeralStyle="Normal" Typography.NumeralAlignment="Normal" Typography.EastAsianWidths="Normal" Typography.EastAsianLanguage="Normal" Typography.StandardSwashes="0" Typography.ContextualSwashes="0" Typography.StylisticAlternates="0"><Paragraph><Run>
Some </Run><Run> </Run><Run> text here</Run><Run Foreground="#FFFF0000">
Some </Run><Hyperlink Foreground="#FF808080" TextDecorations="Underline"><Run Foreground="#FFFF0000">http://www.microsoft.com</Run></Hyperlink><Run Foreground="#FFFF0000"> more text </Run><Run> </Run><Run Foreground="#FFFF0000"> here</Run><Run Foreground="#FF4682B4">
more text here </Run><Run> </Run></Paragraph></Section>
```

----------


## DeanMc

I see the issue, your link doesnt have a navigateuri in it it only has text. see this http://msdn.microsoft.com/en-us/libr...hyperlink.aspx for usage.

----------


## Tewl

I get the same result if I set the NavigateUri as well



```
h = new Hyperlink(tp, tp.GetPositionAtOffset(uriText.Length));
h.NavigateUri = new Uri(uriText);
```



```
<Section xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve" TextAlignment="Left" LineHeight="Auto" IsHyphenationEnabled="False" xml:lang="en-us" FlowDirection="LeftToRight" NumberSubstitution.CultureSource="User" NumberSubstitution.Substitution="AsCulture" FontFamily="Microsoft Sans Serif" FontStyle="Normal" FontWeight="Normal" FontStretch="Normal" FontSize="11" Foreground="#FF000000" Typography.StandardLigatures="True" Typography.ContextualLigatures="True" Typography.DiscretionaryLigatures="False" Typography.HistoricalLigatures="False" Typography.AnnotationAlternates="0" Typography.ContextualAlternates="True" Typography.HistoricalForms="False" Typography.Kerning="True" Typography.CapitalSpacing="False" Typography.CaseSensitiveForms="False" Typography.StylisticSet1="False" Typography.StylisticSet2="False" Typography.StylisticSet3="False" Typography.StylisticSet4="False" Typography.StylisticSet5="False" Typography.StylisticSet6="False" Typography.StylisticSet7="False" Typography.StylisticSet8="False" Typography.StylisticSet9="False" Typography.StylisticSet10="False" Typography.StylisticSet11="False" Typography.StylisticSet12="False" Typography.StylisticSet13="False" Typography.StylisticSet14="False" Typography.StylisticSet15="False" Typography.StylisticSet16="False" Typography.StylisticSet17="False" Typography.StylisticSet18="False" Typography.StylisticSet19="False" Typography.StylisticSet20="False" Typography.Fraction="Normal" Typography.SlashedZero="False" Typography.MathematicalGreek="False" Typography.EastAsianExpertForms="False" Typography.Variants="Normal" Typography.Capitals="Normal" Typography.NumeralStyle="Normal" Typography.NumeralAlignment="Normal" Typography.EastAsianWidths="Normal" Typography.EastAsianLanguage="Normal" Typography.StandardSwashes="0" Typography.ContextualSwashes="0" Typography.StylisticAlternates="0"><Paragraph><Run>
Some </Run><Run> </Run><Run> text here</Run><Run Foreground="#FFFF0000">
Some </Run><Hyperlink Foreground="#FF808080" NavigateUri="http://www.microsoft.com" TextDecorations="Underline"><Run Foreground="#FFFF0000">http://www.microsoft.com</Run></Hyperlink><Run Foreground="#FFFF0000"> more text </Run><Run> </Run><Run Foreground="#FFFF0000"> here</Run><Run Foreground="#FF4682B4">
more text here </Run><Run> </Run></Paragraph></Section>
```

----------


## DeanMc

Try remove the runs manually and then put the xaml code back into the RTB via a section and see does it click then I think they are causing the issue.

----------


## Tewl

Doing that removes all of the formatting

----------


## DeanMc

I know but for the moment we just need to find out if it is affecting the click ability of the link.

----------


## Tewl

Same result. Link is now grey. Updated screenshot above.

----------


## DeanMc

and still not clickable. This may sound stupid but try to control click the link. Otherwise im out of ideas for the moment.

----------


## Tewl

I apparently the click event wouldnt fire. MouseLeftButtonDown however does. This fixes my main issue, now I just have to figure out how to insert the hyperlink to to text that is split into 2 seperate runs

<Run>http://www.micro</Run><Run>soft.com</Run>



```
tr = new TextRange(rtb.Document.ContentEnd, rtb.Document.ContentEnd);
tr.Text = "\rSome http://www.micro";
tr.ApplyPropertyValue(TextElement.ForegroundProperty, System.Windows.Media.Brushes.Red);
tr = new TextRange(rtb.Document.ContentEnd, rtb.Document.ContentEnd);
tr.Text = "soft.com more text :) here";
tr.ApplyPropertyValue(TextElement.ForegroundProperty, System.Windows.Media.Brushes.SteelBlue);
```

----------


## DeanMc

One of your objects is not initialised. IE

MyVar VAR = new VAR;

----------


## Tewl

It's the TextPointer that is null; it becomes null during the loop.

----------


## Tewl

I was experimenting with a new method of inserting the text and managed to get this which works really well altho it doesn't fix my issue with inserting urls that are in 2 different Runs but that is a minor issue.



```
        private void InsertHyperlink(TextPointer position)
        {
            string match = string.Empty;
            Regex r = new Regex("(?:^|[\\s\\[\\]\\}\\{\\(\\)\\\'\\\"<>])((?:(?:https?|gopher|ftp|file|irc):\\/\\/|www\\.)[a-zA-Z0-9\\.\\-=;&%\\?]+(?:\\/?[a-zA-Z0-9\\.\\-=;&%\\?]*)*)");
            
            Hyperlink h;

            while (position != null)
            {
                if (position.CompareTo(this.Document.ContentEnd) == 0)
                {
                    break;
                }
                if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
                {
                    String text = position.GetTextInRun(LogicalDirection.Forward);
                    Int32 indexInRun = -1;
                    if (r.IsMatch(text))
                    {
                        Match m = r.Match(text);
                        match = m.Groups[1].Value;
                        indexInRun = m.Groups[1].Index;
                    }

                    if (indexInRun >= 0)
                    {
                        position = position.GetPositionAtOffset(indexInRun);
                        h = new Hyperlink(position, position.GetPositionAtOffset(match.Length));
                        h.Tag = match;
                        h.Foreground = Brushes.Blue;
                        h.TextDecorations = TextDecorations.Underline;
                        h.Cursor = System.Windows.Input.Cursors.Hand;
                        h.MouseLeftButtonDown += new MouseButtonEventHandler(h_MouseLeftButtonDown);
                        position = position.GetPositionAtOffset(match.Length);
                    }
                    else
                    {
                        position = position.GetPositionAtOffset(text.Length);
                    }
                }
                else
                {
                    position = position.GetNextContextPosition(LogicalDirection.Forward);
                }
            }
        }
```

----------

