PDA

Click to See Complete Forum and Search --> : WPF RichTextBox Question


Tewl
Feb 14th, 2009, 12:26 AM
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. www.somesite.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 (http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5a16a263-12b9-4a59-b93e-bbcfb6d6ac96/)

DeanMc
Feb 14th, 2009, 09:14 AM
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
Feb 14th, 2009, 02:30 PM
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
Feb 14th, 2009, 06:48 PM
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.

Tewl : Hello! :P

DeanMc
Feb 15th, 2009, 06:09 AM
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
Feb 15th, 2009, 12:51 PM
Thanks

DeanMc
Feb 15th, 2009, 01:45 PM
Does it work for you?

Tewl
Feb 15th, 2009, 05:44 PM
I'm kinda of lost as to how to insert images and clickable urls with this method

DeanMc
Feb 15th, 2009, 06:25 PM
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
Feb 16th, 2009, 04:00 PM
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
Feb 18th, 2009, 08:49 PM
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
Feb 18th, 2009, 08:57 PM
Screenshot: Removed bandwidth issue

DeanMc
Feb 19th, 2009, 06:00 AM
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
Feb 19th, 2009, 07:29 AM
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
Feb 19th, 2009, 08:26 AM
I see the issue, your link doesnt have a navigateuri in it it only has text. see this http://msdn.microsoft.com/en-us/library/system.windows.documents.hyperlink.aspx for usage.

Tewl
Feb 19th, 2009, 08:56 AM
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
Feb 19th, 2009, 04:43 PM
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
Feb 20th, 2009, 03:57 AM
Doing that removes all of the formatting

DeanMc
Feb 20th, 2009, 07:46 AM
I know but for the moment we just need to find out if it is affecting the click ability of the link.

Tewl
Feb 20th, 2009, 02:37 PM
Same result. Link is now grey. Updated screenshot above.

DeanMc
Feb 20th, 2009, 03:17 PM
and still not clickable. This may sound stupid but try to control click the link. Otherwise im out of ideas for the moment.

Tewl
Feb 20th, 2009, 04:02 PM
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
Feb 22nd, 2009, 05:58 PM
One of your objects is not initialised. IE

MyVar VAR = new VAR;

Tewl
Feb 23rd, 2009, 06:21 AM
It's the TextPointer that is null; it becomes null during the loop.

Tewl
Feb 23rd, 2009, 07:37 AM
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);
}
}
}