Writing into word document with different font sizes, etc - c#

I am trying to pick string values from different Excel worksheets and write it into Word. However, each text line will have different font size, bold and underline.
I managed to pull all values into Word with StringBuilder, however I am not able to format each line way I wanted, therefore trying to make other solution without StringBuilder.
Please see code below. Unfortunately, the end result has only the last row of Excel sheet value.
Anyone has idea, what I can do here:
for (int c = 0; c < aList.Count; c++)
{
Worksheet excelWorksheet = excelWorkbook.Worksheets[aList[c]];
Microsoft.Office.Interop.Excel.Range lastRow = excelWorksheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell);
int lastUsedRow = lastRow.Row;
currentItemNumber = "Item " + (c + 1).ToString();
docRange.Text = currentItemNumber;
docRange.Font.Bold = 1;
docRange.Font.Size = 12;
docRange.Font.Underline = (WdUnderline)1;
docWholeRange.Text = docRange.Text;
for (int d = 0; d < lastUsedRow; d++)
{
if (d == 0)
{
currentRowValue = excelWorksheet.Range["A" + (d + 1)].Value2 + "\t" + excelWorksheet.Range["D"+(d+1)].Value2;
docRange.Text = currentRowValue;
docRange.Font.Bold = 0;
docRange.Font.Size = 12;
docRange.Font.Underline = (WdUnderline)0;
docWholeRange.Text = docWholeRange.Text + docRange.Text;
}
else
{
currentRowValue = excelWorksheet.Range["A" + (d + 1)].Value2+ excelWorksheet.Range["B" + (d + 1)].Value2+ excelWorksheet.Range["C" + (d + 1)].Value2;
docRange.Text = currentRowValue;
docRange.Font.Bold = 0;
docRange.Font.Size = 10;
docRange.Font.Underline = (WdUnderline)0;
docWholeRange.Text = docWholeRange.Text + docRange.Text;
}
}
wordDocument.Bookmarks.Add(bookmarkPlace, docWholeRange);
}
I tried using StringBuilder, however it is not possible to format each line separately and keep it formatted.
Then tried using Selection object in similar manner as shown in code and then tried to use Selection.TypeText. Instead of being entered at Bookmark place, it was actually starting from the beginning of word file. The size of the letter was way too small.
Managed to get all lines correctly to be written in Word without using StringBuilder.
However, trying to get all text lines between two bookmarks and all samples advising to use Bookmarks.get_Item and this does not exists in Visual Studio 2019 and C#.
Anyone has idea, how to store it into Range?
Tried following:
object start = bm.Range.End;
object end = ebm.Range.Start;
Microsoft.Office.Interop.Word.Range textRange = wordDocument.Range(ref start, ref end);
bm and ebm are two bookmarks.
I am trying to store all text line into range, then iterate line by line and change font size/bold in each text line.
Anyone has better idea how it can be done.
Regards,

Related

I need to get a string Array with single lines, which should be extraced from a big text with paragraphs

I got an idea that i wanted to make today but ran into the problem that i have a string variable with paragraph, but i need an Array of single lines. So I tried to do this with the String.Substring and the String.IndexOf functions but this only worked kinda because i dont exactly know how VisualStudio handels the Index of Paragraphs and how strings work with paragraphs because i just learned C# this year.
I tried it in Windows-Forms btw.
Can anyone tell me how index's with paragraphs work or especialy how to use them correctly.
This is the code i tried which only works for the 1st line and works kinda with the 2nd but not with any further
string input_raw;
string[] input = new string[100];
int index_zeile = 0;
int x = 0, y = 0;
input_raw = input_text_box.Text;
for (int i = 0; i < 100; i++)
{
if (i == 0)
{
y = input_raw.IndexOf(";");
input[i] = input_raw.Substring(index_zeile, y);
x = y + 1;
}
else
{
index_zeile = input_raw.IndexOf(";", x);
input[i] = input_raw.Substring(x, index_zeile-3);
x = x + index_zeile;
}
}
I wish you entered your text input, but you can split the string into an array. The following code assigns a multi-line string to an array.
string[] lines = input_text_box.Text.Split('\n');

How can I add underlined words in a line of text in a word header object?

I need to add underlined column headers to the header objects in a document. I am using C# and Microsoft.Office.Interop.Word
The pertinent parts of the code look like this...
foreach (Word.HeaderFooter header in wordSection.Headers)
{
int[] fiscalYears = RetrieveFiscalYears(docProfile);
string paddingFY = new String(' ', 8);
Word.Paragraph colParagraph = wordRng.Paragraphs.Add();
int year;
for (int i = fiscalYears.Length - 1; i >= 0; i--)
{
year = fiscalYears[i];
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
//begin underline
colParagraph.Range.InsertAfter(paddingFY + year.ToString() + paddingFY);
//end underline
}
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
colParagraph = wordRng.Paragraphs.Add();
colParagraph.set_Style(wordDoc.Styles["ColumnHeadings"]);
}
Basically it needs to look similar to ...
Expended Estimated Budgeted
2015 2016 2017
--------- ---------- --------
In the body of the document, my for loop looks like
foreach (int year in fiscalYears)
{
wordApp.Selection.TypeText(MARKUP_WORD_TAB);
wordApp.Selection.Font.Underline = Word.WdUnderline.wdUnderlineSingle;
wordApp.Selection.TypeText(paddingFY + year.ToString() + paddingFY);
wordApp.Selection.Font.Underline = Word.WdUnderline.wdUnderlineNone;
}
But the when I use the selection object, it writes to the body of the document, not to the header/footer objects. I might be able to get around this by using the SeekHeader and making it the focus, but that offers its own challenges...
I've tried using the colParagraph.Range.Font.Underline object, but that underlines the entire line, not just the words that make up the column headings.
I've tried using a find object, but the execute doesn't find the text for some reason.
Appreciate any guidance you can provide.
I had to move the set style above the for loop and set a new range based on the paragraph range and move its start and end positions. Then apply the underlining to the new range.
So now it looks similar to ....
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
colParagraph = wordRng.Paragraphs.Last; //reset the range to include the tab so the style can be applied.
colParagraph.set_Style(wordDoc.Styles["ColumnHeadings"]);
int year;
int start = colParagraph.Range.Text.Length - 1;
string yrHeading = string.Empty;
Word.Range underlineRange = null;
for (int i = 0 ; i < fiscalYears.Length; i++)
{
year = fiscalYears[i];
colParagraph = wordRng.Paragraphs.Last; //reset the range to include the last fiscal year that was entered.
start = colParagraph.Range.Text.Length - 1;
colParagraph.Range.InsertAfter(yrHeading);
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
underlineRange = colParagraph.Range.Duplicate;
underlineRange.MoveStart(Word.WdUnits.wdCharacter, start);
underlineRange.MoveEnd(Word.WdUnits.wdCharacter, -2); //-2 = /t/r for tab & paragraph characters
underlineRange.Font.Underline = Word.WdUnderline.wdUnderlineSingle;
}
colParagraph = wordRng.Paragraphs.Add();

C# losing Font Style in RichTextBox after deleting lines

I have a RichTextBox for a simple chat where I add lines programmatically.
I make the usernames bold and the messages in regular style.
After some lines I want to delete the first lines to keep the chat in a acceptably length. But when I do so I lose the text format and everything appears bold. What am I doing wrong and how can I fix this?
EDIT
I could solve the problem where I wasn't able to delete the first line.
I had to set the the ReadOnly property to false. Even though I was able to add new lines it prevented deleting lines. So the following code works to delete lines. Thanks to #TaW!
if (ChatText.Lines.Length >= 10)
{
int p = 0; int count = 0;
do
{
p = ChatText.Text.IndexOf("\n\r");
if (p >= 0)
{
ChatText.SelectionStart = p;
ChatText.SelectionLength = 2; // length of "\n\r"
ChatText.SelectedText = "\n";
count++;
}
}
while(p >= 0);
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = ChatText.Lines.Take(0).Select(x => x.Length + nll).Sum() - nll;
int pL = ChatText.Lines.Take(1).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
ChatText.SelectionStart = pS;
ChatText.SelectionLength = pL - pS;
ChatText.Cut();
}
//////////////////////////////////
// now add new lines
//////////////////////////////////
string[] chatstr;
// string text is given as method parameter
chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// go to the end of the text
ChatText.SelectionStart = ChatText.Text.Length;
ChatText.SelectionLength = 0;
// make text bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
// add username (chatstr[0]) and colon
ChatText.AppendText(chatstr[0] + ": ");
// make text regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
// add message (chatstr[1])
ChatText.AppendText(chatstr[1] + "\n");
// and finaly scroll down
ChatText.ScrollToCaret();
So deleting lines works and new lines are added as intended. Finaly!
solved :)
Never change the Text of a RichtTextBox if it contains any formatting.
Changing the Lines property (by Skip) is just another way to change the Text.
Instead only use the functions the RTB provides: Always start by selecting the portion you want to format, then apply one or more of the functions and/or set one or more of the properties..:
To delete portions use Cut.
Here is a function that will delete a number of entire lines:
void DeleteLines(RichTextBox rtb, int fromLine, int count)
{
int p1 = rtb.GetFirstCharIndexFromLine(fromLine);
int p2 = rtb.GetFirstCharIndexFromLine(fromLine + count);
rtb.SelectionStart = p1;
rtb.SelectionLength = p2 - p1;
bool readOnly = rtb.ReadOnly; // allow change even when the RTB is readonly
rtb.ReadOnly = false; ;
rtb.Cut();
rtb.ReadOnly = readOnly;
}
Trying to keept the formatting alive yourself is a tedious and error-prone waste of your time.
In addition to font properties you would also have to resore all other things you can set with the SelectedXXX properties, like colors, alignment, spacing etc etc..
To delete the first 3 lines use:
DeleteLines(yourRTB, 0, 3);
To restrict the text to 10 lines use:
DeleteLines(yourRTB, 0, yourRTB.Lines.Length - 10);
Note that the function above should have a few checks for valid input; I left them out as the checks somehow need a decision what to do, if count or fromLine if greater than Lines.Length or if fromLine is negative..
While we are at it, here is how to append a bold line:
yourRTB.SelectionStart = yourRTB.Text.Length;
yourRTB.SelectionLength = 0;
using (Font font = new Font(yourRTB.SelectionFont, FontStyle.Bold))
yourRTB.SelectionFont = font;
yourRTB.AppendText(yourNewLine + textOfNewLine);
Of course it really shold go into a reuseable function that the the bolding as a parameter..
Update:
since you are using WordWrap you may prefer this function. It deletes the actual lines, not the visible ones:
void DeleteLinesWW(RichTextBox rtb, int fromLine, int count)
{
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = rtb.Lines.Take(fromLine).Select(x => x.Length + nll).Sum() - nll;
int pL = rtb.Lines.Take(fromLine + count).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
rtb.SelectionStart = pS;
rtb.SelectionLength = pL - pS ;
bool readOnly = rtb.ReadOnly;
rtb.ReadOnly = false; // allow change even when the RTB is readonly
rtb.Cut();
rtb.ReadOnly = readOnly;
}
A word on NewLine: Do note that I have not used the Environment.NewLine constant as it not really a good idea. If you add multiline text to the RichTextBox in the designer and then look at it you will see that it uses simple '\n' new lines, no returns, no NL-CR, just '\n'. So this seems to be the generic way in a winforms RTB and I recommend using it..
The new function relies on all lines having a newline of the same length!
To make sure you can use this replacement function:
int RTBReplace(RichTextBox rtb, string oldText, string newText)
{
int p = 0; int count = 0;
do
{
p = richTextBox1.Text.IndexOf(oldText);
if (p >= 0)
{
richTextBox1.SelectionStart = p;
richTextBox1.SelectionLength = oldText.Length;
richTextBox1.SelectedText = newText;
count ++;
}
}
while (p >= 0);
return count;
}
Calling it like this:
RTBReplace(yourrichTextBox, "\r\n", "\n");
Update 2:
Here is an example how to add your chat lines:
private void button1_Click(object sender, EventArgs e)
{
string cLine = "Taw: Hello World"; // use your own lines!
var chatstr = cLine.Split(new string[] { ": " }, 2, StringSplitOptions.None);
AppendLineBold(yourrichTextBox, "\n" + chatstr[0], true);
AppendLineBold(yourrichTextBox, chatstr[1], false);
yourrichTextBox.ScrollToCaret();
}
void AppendLineBold(RichTextBox rtb, string text, bool bold)
{
rtb.SelectionStart = richTextBox1.Text.Length;
rtb.SelectionLength = 0;
using (Font font = new Font(rtb.SelectionFont,
bold ? FontStyle.Bold : FontStyle.Regular))
rtb.SelectionFont = font;
rtb.AppendText(text);
}
Update 3:
Looks like the ReadOnly property disallows the use of Cut. So we need to temporatily allow changes.
Funny: SelectedText can't be set either, but AppendText works fine..
To keep text formatting, you can also try the following (it's a little shorter and should also do the trick)
string text = "Username: hello this is a chat message";
// delete the first line when after 10 lines
if (ChatText.Lines.Length >= 10)
{
ChatText.SelectionStart = 0; // set SelectionStart to the beginning of chat text (RichTextBox)
ChatText.SelectionLength = ChatText.Text.IndexOf("\n", 0) + 1; // select the first line
ChatText.SelectedText = ""; // replace by an empty string
ChatText.SelectionStart = ChatText.Text.Length; // set SelectionStart to text end to make SelectionFont work for appended text
}
// split the string in chatstr[0] = username, chatstr[1] = message
string[] chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// make the username bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
ChatText.AppendText(chatstr[0] + ": ");
// make the message regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
ChatText.AppendText(chatstr[1] + Environment.NewLine);
ChatText.ScrollToCaret();

C# Algorithm for converting Visual LTR to Logic RTL

Iam working on a project in Unity3d, using C# scripting, with strings that are written in Hebrew and English.
The problem with Unity3d is that RTL languages (like Hebrew) are not supported when writing UI texts.
The problem looks like this:
original text:
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
(Ground Penetrating Radar) - ראדאר חודר קרקע.
in unity:
GPR -ה תיגולונכט לע תססבתמ רשא ,תכרעמ הניה ףלטעה תכרעמ
.עקרק רדוח ראדאר - (Ground Penetrating Radar)
Iam trying to hack the order that the text is printed to the text boxes for a month now and I cant get it just right.
I made up the following function:
public string CorrectText (string text)
{
string result = "";
string temp = "";
string charListString = "";
List<Char> charList = new List<char> ();
char[] charArray = text.ToCharArray ();
Array.Reverse (charArray);
//go through each char in charArray
for (int x = 0; x <= charArray.Length -1; x++) {
//if the current char we're examing isn't a Space char -
if (!char.IsWhiteSpace (charArray [x])) {
//add it to charList
charList.Add (charArray [x]);
} else { //if the current char we're examing is a Space char -
charListString = new string (charList.ToArray ());
//if charListString doesn't contains English or Numeric chars -
if (!Regex.IsMatch (charListString, "^[0-9a-zA-Z.,()-]*$")) {
//go through each char in charList
for (int y = 0; y <= charList.Count - 1; y++) {
//add the current char to temp as is (not flipped)
temp += charList [y];
}
//add temp to result
if (x < charArray.Length - 1)
result += temp + " ";
if (x == charArray.Length - 1)
result += temp;
//clear charList and temp
charList.Clear ();
temp = "";
} else { //if temp contains English or Numeric chars -
//go through each char in charList - This flipps the order of letters
for (int y = charList.Count - 1; y >= 0; y--) {
//add the current char to temp
temp += charList [y];
}
//add temp to result
if (x < charArray.Length - 1)
result += temp + " ";
if (x == charArray.Length - 1)
result += temp;
//clear charList and temp
charList.Clear ();
temp = "";
}
}
}
return result;
}
this almost fixed the problem, but the text is written from bottom up for example:
input:
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
(Ground Penetrating Radar) - ראדאר חודר קרקע.
output:
(Ground Penetrating Radar) - ראדאר חודר קרקע.
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
and sometimes the parenthesis are getting mixed up in the sentence, showing like this:
)Ground Penetrating( Radar - ראדאר חודר קרקע.
I've found a tool online that turns Visual Hebrew to Logic Hebrew, and when I copied the flipped text to unity it worked like magic!
But I cant find any useful information online on how to make my own script with C# that does the same thing.
I managed to overcome this... sort of:
Assign the string into the text element.
Force the canvas to update using Canvas.ForceUpdateCanvases(), or another means.
Get the cached text generator (NOT for layout) of the text component. It will lie to you if you didn't force an update or yielded and waited for the next frame; so don't skip step 2.
Use getLines of the cached text generator to get the information of the text wrapping; it holds the index where every line starts.
Take the maximum difference in the values of those and you have the likely amount of characters per row before it wraps.
Insert '\n' (new line) into the original string manually using the row length you found in the previous step before flipping the characters!
Do the reversal process on the wrapped string. If you do these things in a different order you will end up with the first line being the shortest, instead of the last. So wrap first, reverse second.
Set the string to the text component and it should work. Usually.

C#/WPF: How to find the closest line breaks from the selection of a TextBox

I have a TextBox txtEditor. I want to find the nearest line breaks and select it
Example 1: With "no selection"
suppose the selection/cursor is *
this is the 1st line of text
this is *the 2nd line of text
this is the 3rd line of text
I want to extend the selection such that the selection is now
this is the 1st line of text
*this is the 2nd line of text*
this is the 3rd line of text
Example 2: With selection
this is the 1st line of text
this is *the 2nd line* of text
this is the 3rd line of text
I want to extend the selection such that the selection is now
this is the 1st line of text
*this is the 2nd line of text*
this is the 3rd line of text
Update: Possible solution
I found a possible solution, wonder if anyone got a better solution?
string tmp = txtEditor.Text;
int selStart = txtEditor.SelectionStart;
int selLength = txtEditor.SelectionLength;
int newSelStart;
int newSelLength;
string tmp1 = tmp.Substring(0, selStart);
if (tmp1.LastIndexOf(Environment.NewLine) < 0)
{
newSelStart = 0;
}
else
{
newSelStart = tmp1.LastIndexOf(Environment.NewLine) + Environment.NewLine.Length;
}
tmp1 = tmp.Substring(selStart);
if (tmp1.IndexOf(Environment.NewLine) < 0)
{
newSelLength = tmp.Length;
}
else
{
newSelLength = tmp1.IndexOf(Environment.NewLine) + selStart - newSelStart;
}
txtEditor.SelectionStart = newSelStart;
txtEditor.SelectionLength = newSelLength;
Well, mostly the problem is that your code is somwhat bloated (and thus harder to read) and a lot less efficient than it needs to be. Performance probably doesn't really matter in your case (those duplicate calls to IndexOf and LastIndexOf rub me the wrong way), but personally I'd rewrite your code like this:
string tmp = txtEditor.Text;
int selStart = txtEditor.SelectionStart;
int selLength = txtEditor.SelectionLength;
int newSelStart = tmp.LastIndexOf(Environment.NewLine, selStart);
if (newSelStart == -1)
newSelStart = 0;
int newSelEnd = tmp.IndexOf(Environment.NewLine, selStart);
if (newSelEnd == -1)
newSelEnd = tmp.Length;
txtEditor.SelectionStart = newSelStart;
txtEditor.SelectionLength = newSelEnd - newSelStart;

Categories

Resources