Get Paragraph in Word document - c#

I create a table of figures programmatically in a Word document.
Well, the ToF style is centered and I would like it to be left indent.
To do so (set paragraph indention) I have to get the paragraph where the ToF is located.
This is the way rto access the ToF:
wordApp.ActiveDocument.TablesOfFigures[1]
Any ideas?

Try the code below. Assuming that TablesOfFigures[1] is exists (otherwise we will get buffer overflow).
// Check in which paragraph TablesOfFigures[1] is found
for (int i=1; i <= wordApp.ActiveDocument.Paragraphs.Count; i++)
{
if (IsInRange(wordApp.ActiveDocument.TablesOfFigures[1].Range, wordApp.ActiveDocument.Paragraphs[i].Range))
{
MessageBox.Show("ToF is in paragraph " + i);
}
}
// Returns true if 'target' is contained in 'source'
private bool IsInRange(Range target, Range source)
{
return target.Start >= source.Start && target.End <= source.End;
}

Provided that you only have one Table of Figures, you can try this:
With wordApp.ActiveDocument.TablesOfFigures(1).Range
'Setting the indent
.ParagraphFormat.LeftIndent = CentimetersToPoints(1)
End With
I have tested it just using Word and it Selects the Table of Figures and then Indents it by 1cm

Related

Get bounds of glyphs in PDF with GemBox

Goal: extract a value from a specific location inside a PDF page. In GemBox.Pdf, I can extract text elements including their bounds and content, but:
Problem: a text element can have a complex structure, with each glyph being positioned using individual settings.
Consider this common example of a page header:
Billing Info Date: 02/02/20222
Company Ltd. Order Number: 0123456789
123 Main Street Name: Smith, John
Let's say, I want to get the order number (0123456789) from the document, knowing its precise position on the page. But in practice, often enough the entire line would be one single text element, with the content SO CompanyOrder Number:0123456789, and all positioning and spacing done via offsets and indices only. I can get the bounds and text of the entire line, but I need the bounds (and value) of each character/glyph, so I can combine them into "words" (= character sequences, separated by whitespace or large offsets).
I know this is definitely possible in other libraries. But this question is specific to GemBox. It seems to me, all the necessary implementations should already there, just not much is exposed in the API.
In itextsharp I can get the bounds for each single glyph, like this:
// itextsharp 5.2.1.0
public GlyphExtractionStrategy : LocationTextExtractionStrategy
{
public override void RenderText(TextRenderInfo renderInfo)
{
var segment = renderInfo.GetBaseline();
var chunk = new TextChunk(
renderInfo.GetText(),
segment.GetStartPoint(),
segment.GetEndPoint(),
renderInfo.GetSingleSpaceWidth(),
renderInfo.GetAscentLine(),
renderInfo.GetDescentLine()
);
// glyph infos
var glyph = chunk.Text;
var left = chunk.StartLocation[0];
var top = chunk.StartLocation[1];
var right = chunk.EndLocation[0];
var bottom = chunk.EndLocation[1];
}
}
var reader = new PdfReader(bytes);
var strategy = new GlyphExtractionStrategy();
PdfTextExtractor.GetTextFromPage(reader, pageNumber: 1, strategy);
reader.Close();
Is this possible in GemBox? If so, that would be helpful, because we already have the code to combinine the glphs into "words".
Currently, I can somewhat work around this using regex, but this is not always possible and also way too technical for end users to configure.
Try using this latest NuGet package, we added PdfTextContent.GetGlyphOffsets method:
Install-Package GemBox.Pdf -Version 17.0.1128-hotfix
Here is how you can use it:
using (var document = PdfDocument.Load("input.pdf"))
{
var page = document.Pages[0];
var enumerator = page.Content.Elements.All(page.Transform).GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Current.ElementType != PdfContentElementType.Text)
continue;
var textElement = (PdfTextContent)enumerator.Current;
var text = textElement.ToString();
int index = text.IndexOf("Number:");
if (index < 0)
continue;
index += "Number:".Length;
for (int i = index; i < text.Length; i++)
{
if (text[i] == ' ')
index++;
else
break;
}
var bounds = textElement.Bounds;
enumerator.Transform.Transform(ref bounds);
string orderNumber = text.Substring(index);
double position = bounds.Left + textElement.GetGlyphOffsets().Skip(index - 1).First();
// TODO ...
}
}

Interop Word - Insert Mergefield to the End of the Range [duplicate]

I have created a 1x3 table as my header in word. This is how I want it to look like.
LeftText MiddleText PageNumber:
I want the PageNumber cell to look like this -
Page: X of Y
I have managed to do cell (1,1) and (1,2). I found this to help me with cell (1,3) but it is not working as I like. I know how to get the total count of the document. I'm not sure how to implement it properly.
Range rRange = restheaderTable.Cell(1, 3).Range;
rRange.End = rRange.End - 1;
oDoc.Fields.Add(rRange, Type: WdFieldType.wdFieldPage, Text: "Page Number: ");
I can't even get the Text "Page Number: " to display in the cell. All it has is a number right now.
The field enumeration you're looking for is WordWdFieldType.wdFieldNumPages.
The next hurdle is how to construct field + text + field as Word doesn't behave "logically" when things are added in this order. The target point remains before the field that's inserted. So it's either necessary to work backwards, or to move the target range after each bit of content.
Here's some code I have the demonstrates the latter approach. Inserting text and inserting fields are in two separate procedures that take the target Range and the text (whether literal or the field text) as parameters. This way the field code can be built up logically (Page x of n). The target Range is returned from both procedures, already collapsed to its end-point, ready for appending further content.
Note that I prefer to construct a field using the field's text (including any field switches) rather than specifying a field type (the WdFieldType enumeration). This provides greater flexibility. I also highly recommend setting the PreserveFormatting parameter to false as the true setting can result in very odd formatting when fields are updated. It should only be used in very specific instances (usually involving linked tables).
private void btnInsertPageNr_Click(object sender, EventArgs e)
{
getWordInstance();
Word.Document doc = null;
if (wdApp.Documents.Count > 0)
{
doc = wdApp.ActiveDocument;
Word.Range rngHeader = doc.Sections[1].Headers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
if (rngHeader.Tables.Count > 0)
{
Word.Table tbl = rngHeader.Tables[1];
Word.Range rngPageNr = tbl.Range.Cells[tbl.Range.Cells.Count].Range;
//Collapse the range so that it's within the cell and
//doesn't include the end-of-cell markers
object oCollapseStart = Word.WdCollapseDirection.wdCollapseStart;
rngPageNr.Collapse(ref oCollapseStart);
rngPageNr = InsertNewText(rngPageNr, "Page ");
rngPageNr = InsertAField(rngPageNr, "Page");
rngPageNr = InsertNewText(rngPageNr, " of ");
rngPageNr = InsertAField(rngPageNr, "NumPages");
}
}
}
private Word.Range InsertNewText(Word.Range rng, string newText)
{
object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
rng.Text = newText;
rng.Collapse(ref oCollapseEnd);
return rng;
}
private Word.Range InsertAField(Word.Range rng,
string fieldText)
{
object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
object unitCharacter = Word.WdUnits.wdCharacter;
object oOne = 1;
Word.Field fld = rng.Document.Fields.Add(rng, missing, fieldText, false);
Word.Range rngField = fld.Result;
rngField.Collapse(ref oCollapseEnd);
rngField.MoveStart(ref unitCharacter, ref oOne);
return rngField;
}

C# Call an object from concatenated text

I was trying to call multiple labels with multiple names from a for loop, but the thing is that i dont want to use the "foreach" to loop trough all the controls.
I want to make a direct reference to it, for example :
for(ai = 2; ai < 11 ; ai ++)
{
this.Controls("label" + ai).Text = "SomeRandomText";
}
How can i do this?
I already tried to find this question on the net, but all i find are answers with "foreach" loops.
Thanks!!
Assuming that your labels are named "lable2" through "label10", then you can do it like this:
for(int ai = 2; ai < 11 ; ai++)
{
this.Controls["label" + ai].Text = "SomeRandomText";
}
Here is a solution that is not dependent on the control's name so you are free to change the name of the label at any point in time without breaking your code.
foreach (var control in this.Controls)
{
if (control is Label)
{
int index;
if (control.Tag != null && int.TryParse(control.Tag.ToString(), out index) && index >= 2 && index < 11)
{
((Label)control).Text = "SomeRandomText";
}
}
}
Then, all you need to do is assign a value between 2 and 11 to each control's Tag property that you want updated. You can set this property through code or set the property in the designer.
You are also free to change the values of the Tag property as you see fit. Just make sure the index checks in the code line up with the tag values you choose!

Is there an equivalent to SetCursorPosition for a text stream?

when outputting to the console, you can set the specific location of the cursor and write to that (or use other nifty tricks like printing backspaces that will take you back.)
Is there a similar thing that can be done with a stream of text?
Scenario: I need to build a string with n pieces of, text where each might be on a different line and start position (or top and left padding).
Two strings might appear on the same line.
I could build a simple Dictionary<int, StringBuilder> and fidget with that, but I'm wondering if there's something like the console functionality for streams of text where you can write to a specific place (row and column).
Edit:
This is for a text only. No control.
The result might be a string with several new lines, and text appearing at different locations.
Example (where . will be white spaces):
..... txt3....... txt2
......................
................ txt1.
this will be the result of having txt1 at row 3 column (whatever), and txt2 and txt3 and row 1 with different colum values (where txt3 column < txt2 colmun)
While waiting for a better answer, here's my solution. Seems to work, been lightly tested, and can be simply pasted into linqpad and run.
void Main()
{
m_dict = new SortedDictionary<int, StringBuilder>();
AddTextAt(1,40, "first");
AddTextAt(2,40, "xx");
AddTextAt(0,10, "second");
AddTextAt(4,5, "third");
AddTextAt(1,15, "four");
GetStringFromDictionary().Dump();
}
// "global" variable
SortedDictionary<int, StringBuilder> m_dict;
/// <summary>
/// This will emulate writting to the console, where you can set the row/column and put your text there.
/// It's done by having Dictionary(int,StringBuilder) that will use to store our data, and eventually,
/// when we need the string iterate over it and build our final representation.
/// </summary>
private void AddTextAt(int row, int column, string text)
{
StringBuilder sb;
// NB: The following will initialize the string builder !!
// Dictionary doesn't have an entry for this row, add it and all the ones before it
if (!m_dict.TryGetValue(row, out sb))
{
int start = m_dict.Keys.Any() ? m_dict.Keys.Last() +1 : 0;
for (int i = start ; i <= row; i++)
{
m_dict.Add(i, null);
}
}
int leftPad = column + text.Length;
// If dictionary doesn't have a value for this row, just create a StringBuilder with as many
// columns as left padding, and then the text
if (sb == null)
{
sb = new StringBuilder(text.PadLeft(leftPad));
m_dict[row] = sb;
}
// If it does have a value:
else
{
// If the new string is to be to the "right" of the current text, append with proper padding
// (column - current string builder length) and the text
int currrentSbLength = sb.ToString().Length;
if (column >= currrentSbLength)
{
leftPad = column - currrentSbLength + text.Length;
sb.Append(text.PadLeft(leftPad));
}
// otherwise, text goes on the "left", create a new string builder with padding and text, and
// append the older one at the end (with proper padding?)
else
{
m_dict[row] = new StringBuilder( text.PadLeft(leftPad)
+ sb.ToString().Substring(leftPad) );
}
}
}
/// <summary>
/// Concatenates all the strings from the private dictionary, to get a representation of the final string.
/// </summary>
private string GetStringFromDictionary()
{
var sb = new StringBuilder();
foreach (var k in m_dict.Keys)
{
if (m_dict[k]!=null)
sb.AppendLine(m_dict[k].ToString());
else
sb.AppendLine();
}
return sb.ToString();
}
Output:
second
four first
xx
third
No. Text files don't really have concept of horizontal/vertical position, so you'd need to build some sort of positioning yourself.
For basic positioning tabs ("\t") may be enough, for anything more advanced you'd need to fill empty space with spaces.
It sounds like you have some sort of table layout - it may be easier to build data in cells first (List<List<string>> - list of rows consisting of columns of strings) and than format it with either String.Format("{0}\t{1}\t...", table[row][0],table[row][1],...) or manually adding necessary amount of spaces for each "cell"

How to add a space after the paragraph RTF c#?

Hi well my problem is this
I have a RichTextBox but i wanna add a "pretty" space after the paragraph, i found on the internet many examples but all examples change all the lines and not only the paragraph.
private void FormatRTB(byte rule, int space, int x)
{
PARAFORMAT fmt = new PARAFORMAT();
fmt.cbSize = Marshal.SizeOf(fmt);
fmt.dwMask = PFM_LINESPACING;
fmt.dyLineSpacing = space;
fmt.bLineSpacingRule = rule;
richTextBox1.Select(x, 2);
SendMessage(new HandleRef(richTextBox1, richTextBox1.Handle),
EM_SETPARAFORMAT,
SCF_SELECTION,
ref fmt
);
}
Well i add this code and select ony the \n because after of "\n" start the paragraph and dosent works i dont if my logic is bad or i need to add more code
while (richTextBox1.Text.IndexOf("\n", k) > 0)
{
k = richTextBox1.Text.IndexOf("\n", k);
setLineFormat(2, 0, k);
k++;
}
.
I know that there is already an accepted answer, but maybe this will help other people.
If you really want to add spacing before or after a paragraph in RichTextBox, there is a very simple and "native" (ie. no hacking) solution using PFM_SPACEBEFORE or PFM_SPACEAFTER. The code is quite similar to the first one you present.
The complete solution with a custom control is posted on http://dominicweb.eu/en/blog/various/winforms-richtextbox-with-paragraph-spacing-csharp/
If you are sure that all the occurrences of "\n" are indeed a different paragraph, you can simply add spaces after it. You could use a simple loop as:
for (int i = 0; i < richTextBox1.Text.Length; i++)
{
if (richTextBox1.Text[i] == '\n')
richTextBox1.Text.Insert(i + 1, " ");
}
Often though paragraphs are marked with both '\n' and '\r' so you may look for the \r instead

Categories