I'm trying to turn some text to Hyperlink while a user is typing in WPF RichTextBox.
My first attempt at it involves running this code at each KeyUp:
Regex r = new Regex("[A-Z]{3}");
FlowDocument doc = this.inputBox.Document;
String text = new TextRange(doc.ContentStart, doc.ContentEnd).Text;
foreach (Match m in r.Matches(text))
{
TextPointer start = doc.ContentStart.GetPositionAtOffset(m.Index + 2);
TextPointer end = doc.ContentStart.GetPositionAtOffset(m.Index + m.Length + 2);
Hyperlink sp = new Hyperlink(start, end);
}
This runs correctly the first time a user enters a sequence of 3 capital letters, ABC, but hyperlink creation fails with an exception when a second sequence is entered. Looking at the variable while debugging, it appears that the two TextPointers keep pointing at the first sequence.
I think your problem might be due to a common misunderstanding of what the GetPositionAtOffset() method does.
GetPositionAtOffset returns the offset within the FlowDocument markup, not the visible text character offset.
After you insert your first hyperlink, the FlowDocument contains opening and closing tags:
<Hyperlink>the_regex_match</Hyperlink>
This means that the FlowDocument offsets no longer align with the plain text character indexes returned by the RegEx match.
Related
I've been working on a tool to modify a text file to change graphics settings for a game. A few examples of the settings are as follows:
sg.ShadowQuality=0
ResolutionSizeX=1440
ResolutionSizeY=1080
bUseVSync=False
I want to be able to find sg.ShadowQuality=(rest of line, regardless of what is after this text), and replace it. This is so that a user can set this to say, 10 then 1 without having to check for 10 and 1 etc.
Basically, I'm try to find out what I need to use to find/replace a string in a text file without knowing the end of the string.
My current code looks like:
FileInfo GameUserSettings = new FileInfo(#SD + GUSDirectory);
GameUserSettings.IsReadOnly = false;
string text = File.ReadAllText(SD + GUSDirectory);
text = text.Replace("sg.ShadowQuality=0", "sg.ShadowQuality=" + Shadows.Value.ToString());
File.WriteAllText(SD + GUSDirectory, text);
text = text.Replace("sg.ShadowQuality=1", "sg.ShadowQuality=" + Shadows.Value.ToString());
File.WriteAllText(SD + GUSDirectory, text);
SD + GUSDirectory is the location of the text file.
The file must have readonly Off to be edited, otherwise the game can revert the settings back, hence the need for this.(It is turned back to readonly On after any change, its just not included in this code provided)
You can do it like you do, if you use a regular expression to match all the line
FileInfo gameUserSettings = new FileInfo(Path.Combine(#SD, GUSDirectory)); //name local varaible in camelCase, use Path.Combine to combine paths
gameUserSettings.IsReadOnly = false;
string text = File.ReadAllText(gameUserSettings.FullName); //use the fileinfo you just made rather than make the path again
text = Regex.Replace(text, "^sg[.]ShadowQuality=.*$", $"sg.ShadowQuality={Shadows.Value}", RegexOptions.Multiline); //note switch to interpolated strings
File.WriteAllText(gameUserSettings.FullName, text);
That regex is a Multiline one (so ^ and $ have altered meanings):
^sg[.]ShadowQuality=.*$
start of line ^ (not start of input)
followed by sg
followed by period . (in a character class it loses its "any character" meaning)
followed by ShadowQuality=
followed by any number of any character(.*)
followed by end of line $ (not end of input)
The vital bit is "any number of any character" that can cope with the vlaue in the file being 1, 2, 7, hello and so on..
The replacement is:
$"sg.ShadowQuality={Shadows.Value}"
This is an interpolated string; a neater way of representing strings that mix constant content (hardcoded chars) and variable content. When a $tring contains a { that "breaks out" of the string and back into normal c# code so you can write code that resolves to values that will be included in the string -> if Shadows.Value is for example a decimal? of 1.23 it will become 1.23
You can format data too; calling for $"to one dp is {Shadows.Value:F1}" would produce "to one dp is 1.2" - the 1.23 is formatted to 1 decimal place by the F1, just like calling Shadows.Value.ToString("F1") would
Cannot keep the highlighted effect I set in my RichTextBox on my text after removing content of a line in front of him.
No matter how much text I remove from the control it always removes the custom SelectionColor and SelectionBackColor I set to a text already contained in it.
Code of my Removal method:
private void btnRemove_Click(object sender, EventArgs e)
{
//Remove selected line from RichTextBox
richTextBox1.Text = richTextBox1.Text.Remove(richTextBox1.Text.Length - 1, 1);
//Remove all blank lines remaining after deletion
richTextBox1.Text = Regex.Replace(richTextBox1.Text, #"^\s*$(\n|\r|\r\n)", "", RegexOptions.Multiline);
}
The number of letters I want to remove here is 1 as the word "AND" is a simple image inserted by means of Clipboard Paste method.
You must never (read my lips: Never, never, never) change to Text or the Lines property of a RichtTextBox or else you will lose/mess up all previous formatting.
So you need to change this:
richTextBox1.Text = richTextBox1.Text.Remove(richTextBox1.Text.Length - 1, 1);
To this sequence:
First Select the part of the Text you want to change in some way:
richTextBox1.SelectionStart = richTextBox1.Text.Length - 1;
richTextBox1.SelectionLength = 1;
Now you can change it. To delete either use:
richTextBox1.SelectedText = "";
or
richTextBox1.Cut();
The latter version also will place the text in the clipboard; doing it it will keep the formatting of that portion and you could Paste it to some other place..
The same rules apply when you want to add or change any type of formatting:
First Select Then Modify
And, yes, this means that the second command will grow quite a bit, i.e. you will have to replace the RegEx.Replace by a loop :-(
Cannot keep the highlighted effect I set in my RichTextBox on my text after removing content of a line in front of him.
No matter how much text I remove from the control it always removes the custom SelectionColor and SelectionBackColor I set to a text already contained in it.
Code of my Removal method:
private void btnRemove_Click(object sender, EventArgs e)
{
//Remove selected line from RichTextBox
richTextBox1.Text = richTextBox1.Text.Remove(richTextBox1.Text.Length - 1, 1);
//Remove all blank lines remaining after deletion
richTextBox1.Text = Regex.Replace(richTextBox1.Text, #"^\s*$(\n|\r|\r\n)", "", RegexOptions.Multiline);
}
The number of letters I want to remove here is 1 as the word "AND" is a simple image inserted by means of Clipboard Paste method.
You must never (read my lips: Never, never, never) change to Text or the Lines property of a RichtTextBox or else you will lose/mess up all previous formatting.
So you need to change this:
richTextBox1.Text = richTextBox1.Text.Remove(richTextBox1.Text.Length - 1, 1);
To this sequence:
First Select the part of the Text you want to change in some way:
richTextBox1.SelectionStart = richTextBox1.Text.Length - 1;
richTextBox1.SelectionLength = 1;
Now you can change it. To delete either use:
richTextBox1.SelectedText = "";
or
richTextBox1.Cut();
The latter version also will place the text in the clipboard; doing it it will keep the formatting of that portion and you could Paste it to some other place..
The same rules apply when you want to add or change any type of formatting:
First Select Then Modify
And, yes, this means that the second command will grow quite a bit, i.e. you will have to replace the RegEx.Replace by a loop :-(
I have a document with a format similar to
Section Heading 1
Paragraph 1
...
Paragraph N
Sub Heading 1
Paragraph 1
...
Paragraph N
What I am trying to do is add a hyperlink from a heading to a reference document. I can add the hyperlink and apply a style to the link but the style gets applied to the section's Paragraph 1 as well as the hyperlink.
Note: WordApp is a singleton wrapper around Microsoft.Office.Interop.Word.Application. The HyperlinkDestionation class just holds the bookmark name and the path for the file that contains the bookmark.
private void LinkHeadings(string file)
{
Document doc = WordApp.Open(file);
for (int i = 1; i <= proposal.Paragraphs.Count; i++)
{
HyperlinkDestination dest = null;
Paragraph paragraph = proposal.Paragraphs[i];
paragraph.Range.Select();
Style style = (Style)paragraph.get_Style();
string styleString = ((Style)paragraph.get_Style()).NameLocal;
string headingText = paragraph.Range.Text.Split(' ')[0];
if (styleString.Contains("Heading"))
{
dest = _hyperlinkDestinations.Find(x => x.HyperlinkText == headingText);
}
if (dest != null)
{
Hyperlink link = WordApp.ActiveWindow.Document.Hyperlinks.Add(WordApp.Selection.Range, Address: dest.FilePath, SubAddress: dest.bookmarkName, TextToDisplay: WordApp.Selection.Text);
link.Range.set_Style(style);
}
}
WordApp.Close(true);
}
My guess is that it has something to do with with the hyperlink anchor. I've also tried deleting the heading first then inserting the hyperlink but it also has the same result.
The basic problem is that you are including the paragraph mark in the Hyperlink field that Word inserts. That pargraph mark will then be hidden when the hyperlink field result is displayed, i.e. the Section Heading 1 para. will actually become part of Paragraph 1. When you apply the style to the selection, the entire paragraph will be affected.
I'm not going to attempt to provide C# here, but here are some suggestions
a. as a rule it is better to work with Range objects in Word than the Selection where possible, and you should be able to do so here.
b. If you apply the Hyperlink to the paragraph without the paragraph marker, the paragraph style will be unchanged, so you should not need to re-apply it
c. So instead of the code starting with "paragraph.Range.Select();" you should be able to use something like this (I leave you to get the C# syntax right - perhaps you can edit this message)
Range r = Paragraph.Range();
string headingText = r.Text.Split(' ')[0];
if (styleString.Contains("Heading"))
// you shoul probably also tst for an empty paragraph here before inserting anything (I leave it to you)
{
dest = _hyperlinkDestinations.Find(x => x.HyperlinkText == headingText);
}
if (dest != null)
{
// Move the end of the range one character towards the beginning
r.MoveEnd(Word.WdUnits.WdCharacter,-1)
Hyperlink link = WordApp.ActiveWindow.Document.Hyperlinks.Add(r, Address: dest.FilePath, SubAddress: dest.bookmarkName, TextToDisplay: r.Text);
}
If your code needs to run internationally and you only need to check paragraphs with the built-in style types Heading 1..Heading 9, then it would also be better to compare the Style.Type to see if it is one of those 9 style types. If you have other style types called "Heading something" that need to be included, then you probably need to check both the Style.Type and the name.
This question started out with someone wanting to import every apparent 'line of text' in a Word document into a row in an Excel sheet, but I'm sure this is not possible without some development.
I would like to insert a newline (whatever that may be configured as) at every point that a string is wrapped to a new line when displayed in a window. How can I do that? I figure I would need to query the window and get an array of its text lines, then count characters in each line, and in the original string, insert a newline after the number of characters that are displayed for that line.
I do of course realise that changing the window size will invalidate all the work of this code I'm asking how to write, but that is not a relevant issue here.
USEFUL INFO:
I have found a solution to the original, Word to Excel export, problem. When you save a Word document as a text file, after you click Save on the Save dialogue, you are given another dialogue with an option for inserting line breaks.
What you can do is something like this:
int width = txtBox.width;
Graphics g = CreateGraphics();
SizeF size = g.MeasureString(myText, txtBox.Font);
if (size.Width > width)
{
int i = 0;
List<string> lines = new List<String>();
string str = "";
while (i<myText.Length)
{
str = str + myText.SubString(i,1);
int w = (int)(g.MeasureString(str, txtBox.Font).Width);
while (w<width && i<myText.Length)
{
i++;
str = str + myText.SubString(i,1);
}
str = str + '\n';
}
// str now contains your newline formatted string
return str;
}
return myText;
What you can do is get the width of the display control and the font it is using, then use GetTextMetrics to get a TEXTMETRIC struct that can be used to find out the width of each character. This is very involved as it takes into account all the different character profiles.
An easier way is to use GetTextExtentPoint32 which, given a string and the font properties, will return its width in pixels. So you can make a rough estimate as to where the line break would be, call this function on that substring, and insert a line break when the result of GetTextExtentPoint32 is just less than the width of the text control.
Cheers.