I'm trying to apply hyperlink to given range of text when user enters a web url in richtextbox in wpf. So far I've found how to apply hyperlink to a given text dynamically in runtime if correct textpointer object is given.
Basicly my problem is I cannot correctly locate range of text in richtextbox. I've been working on this for whole two days. I tried lots of different approaches. The final stage I come is below code.
I'm traversing paragraph's in reverse order so that the changes wont affect next one.
After getting paragraph as string I'm applying regex and getting index of match text if any match hit.
Then I'm verifying text range I've got from match by appling new TextRange(sp, ep).Text. (see below; mat.index, mat.length)
But the point is that index positions I get from regex sometimes doesn't match with I get from TextRange mentioned above. Because of this reason I tried to get around from this by a ugly(heuristic approach) way.
After some debugging and deep digging I realized white-spaces end of or before the those matches are causing crash to program. The weird thing is that If I remove the whitespace it works fine otherwise throws exception System.ExecutionEngineException: 'Exception of type 'System.ExecutionEngineException' was thrown.' in the line new Hyperlink(sp, ep).Click += Match_link_Click;
So as a result how can i locate the match text and get as textpointer.
private void DetectAndMarkHyperlinks()
{
foreach (Paragraph item in rtbxTextArea.Document.Paragraphs().Reverse())
{
string paragraph = new TextRange(item.ContentStart, item.ContentEnd).Text;
//find all web adresses/urls by given regex pattern.
MatchCollection matches = _regex.Matches(paragraph);
if(matches.Count > 0)
{
foreach (Match mat in matches.Reverse())
{
// sp is start pointer of match
// ep is end of the pointer of the match
TextPointer sp, ep;
int len = 0, start = 0, prev_diff;
string last_text = "";
do
{
// get start position of match in the paragraph in document scale.
sp = item.ContentStart.GetPositionAtOffset(mat.Index + start, LogicalDirection.Forward
// get end of the position of match in the paragraph in document scale.
ep = sp.GetPositionAtOffset(mat.Length + len, LogicalDirection.Forward);
prev_diff = len; // backup
len = mat.Value.Length - (last_text = new TextRange(sp, ep).Text).Length;
// if the text we got from given range is same as match found by regex.
// if not same then next iteration comes in;
// which "len" is used to determine the difference between range text and match text
// to locate textpointer correctly.
if(last_text.Length == mat.Value.Length)
{
// if the text from given range and match are same
if (last_text != mat.Value)
{
// even if length is same there might be glide
// so we are sliding by increasing start and end of indexes same amount.
for (int ch = 0; ch < last_text.Length; ch++)
if (last_text[ch] == mat.Value[0])
// make sure first two char are same.
if (last_text[ch + 1] == mat.Value[1])
{
len = (prev_diff += (start = ch));
break;
}
}
else
{
//navigateUri is not set since user might not enter full uri.
new Hyperlink(sp, ep).Click += Match_link_Click;
break;
}
}
}
}
}
}
}
private void Match_link_Click(object sender, RoutedEventArgs e)
{
var hyperlink = (Hyperlink)sender;
Process.Start(
new ProcessStartInfo
{
FileName = hyperlink.GetText(),
UseShellExecute = true
});
}
These are the extensions for getting paragraph by given richtextbox document.
public static IEnumerable<Paragraph> Paragraphs(this FlowDocument doc)
{
return doc.Descendants().OfType<Paragraph>();
}
public static IEnumerable<DependencyObject> Descendants(this DependencyObject root)
{
if (root == null)
yield break;
yield return root;
foreach (var child in LogicalTreeHelper.GetChildren(root).OfType<DependencyObject>())
foreach (var descendent in child.Descendants())
yield return descendent;
}