How to find the line number using Word API? - c#

I want to know the line number in the Word document from Word.Paragraph or Word.Range, but there are no suitable fields and methods.I'm using C#.

Getting the line number involves using an old part of the Word object model that comes from the Word Basic days: the Information property. Since C# doesn't "like" properties with arguments, it's the get_Information method for C#
int lineNumberSelection = WordApp.Selection.get_Information(Word.WdInformation.wdFirstCharacterLineNumber);
int lineNumberRange = myParagraph.Range.get_Information(Word.WdInformation.wdFirstCharacterLineNumber);
This returns the numbering as set in the document - it's "What you see is what you get". So if the numbering is set to restart on each page or for each section or each page - that's the result returned.
If a different result is required, the numbering rule for the document must be changed (and can be changed back again, after). For example:
wdDocument.PageSetup.LineNumbering.RestartMode = Word.Wdnumberingrule.wdRestartContinuous;

Related

Mail merging to multiple text boxes Word

I have a template Word document which contains multiple text boxes on top of shapes (to give it a better border outline than what can be achieved my the outline of a text box). These text boxes contain mail merge fields that I wish to merge to. I have the following code in an attempt to do this
foreach (Microsoft.Office.Interop.Word.Range range in document.StoryRanges)
{
foreach (Microsoft.Office.Interop.Word.Field field in range.Fields)
{
if (field.Code.Text.Contains("Test Field"))
{
field.Select();
application.Selection.TypeText("test");
}
}
The problem is this only changes the fields within the first text box, I have searched both on here and MSDN for a solution, however I am still having trouble actually finding a solution. I have also added the following lines in an attempt to figure out something
Console.WriteLine(document.StoryRanges.Count);
And within the foreach loop I also have
Console.WriteLine(range.Fields.Count);
The first call to WriteLine indicates there are two StoryRanges, one being the main document, the other being the range that all the text boxes are on, I presume. However, the second WriteLine indicates the first range has 0 fields, whereas the second range only has 1 field, even though the template document I am using contains over 10 fields.
Are the StoryRanges nested ? Are they of different type?
If so, you should consider using StoryRange.NextStoryRange as proposed here:
http://word.mvps.org/faqs/customization/ReplaceAnywhere.htm
'Iterate through all story types in the current document
For Each rngStory In ActiveDocument.StoryRanges
'Iterate through all linked stories
Do
With rngStory.Find
.Text = "find text"
.Replacement.Text = "I'm found"
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'Get next linked story (if any)
Set rngStory = rngStory.NextStoryRange
Loop Until rngStory Is Nothing
Next

Are there any issues using a lambda expression in a foreach loop?

I wrote a quick-n-dirty C# console application to search log files for string in another file. In one scenario, I passed in three files to search and one input file. The program loads all the values from the input file into memory as a list of objects. One of the properties of the objects is a boolean to mark the item as found.
Because I am expecting each value in the input file to be found only once, I update the object's property to true and then break out of the loop that is walking through each line of the log files. When the break is in place, the application says that 18 items were not matched. When the break is commented out, all input values are found. There are no duplicate values in the input file. Is it possible that my lambda expression not behaving as I thought?
while ((line = sr.ReadLine()) != null)
{
foreach (var needle in needles.Where(d => d.Found == false))
{
if (line.Contains(needle.SearchValue))
{
needle.Found = true;
//break; //works without break, misses some with break
}
}
}
Sample of supposedly missing rows:
Completed Document 48166115
Completed Document 48166120
Completed Document 48371705
Completed Document 48371710
Completed Document 48371720
Sample of log that contains some of these values:
06/20/13 20:53:22 - Completed Document 48132000, 2 pages
06/20/13 20:53:23 - Completed Document 48166115, 2 pages
06/20/13 20:53:23 - Completed Document 48166116, 2 pages
06/20/13 20:53:23 - Completed Document 48166117, 2 pages
06/20/13 20:53:23 - Completed Document 48166118, 2 pages
06/20/13 20:53:24 - Completed Document 48166119, 2 pages
06/20/13 20:53:24 - Completed Document 48166120, 2 pages
06/20/13 20:53:24 - Completed Document 48166121, 2 pages
You might have already done this, but just to rule out something happening you aren't aware of, you could put a control variable increment
inside of your flag. Check at the end your numbers match what you are expecting. How many flags should be changed to true? How many were actually hit?
if (line.Contains(needle.SearchValue))
{
needle.Found = true;
controlVariable++;
}
Your break is breaking out of the whole foreach loop, so the first of the needles which were previously !Found would then stop the others being checked for.
This means that if multiple needles succeeded on one line, then only the first would be marked, the rest would be missed. Given that you're only expecting one success this would mean that they'd never succeed.
the break is exiting the foreach loop so once you find the first match all subsequent needles will remain unsearched.
You must have duplicates if taking the break out results in all items being matched.
In an attempt to fix the issue, I added another parameter to the program to allow a suffix to be added to the search values in the input file. So, the line.Contains method argument basically included prefix + + suffix.
In the logs there was a comma after the document number, so I passed that as the suffix. The program indicated that 22 items were not found. I was really confused then until I started checking the missing values. They really were missing!
So, what happened was this:
Without the break, the shorter number was matching a larger one incorrectly and the larger one was matching correctly. (No missing values)
With the break, the shorter number was matching a larger one in error and the larger one was getting skipped. The skipped values were actually present, which confused me.
With the break and the suffix, the smaller missing values were correctly being logged as missing and the larger values were being found. So, the scanner is now faster and more accurate then before.
Thanks for all the help!

How to get words from current visible page of ms word document?

I'm developing C# addin for MS Word. I can grab all words of current document - it's something like that:
app = (Word._Application )Application; // Application object comes on addin's connection
foreach(Word.Word word in app.Application.Words)
{
doSmth(word);
}
My question, is how to grab all words not from entire document but from current active(visible for user) page?
In other words, I need to define active page/paragraph of app.Application.ActiveDocument and do something with "active" words.
Interesting question. [See update at end]
Word's object model doesn't really have a "page" object, because the pagination of the document is constantly changing as you add and remove content (or change the font size, the paper size, etc.). So, there is no "ActiveDocument.Pages(1)" sort of thing.
What's more, there's no easy way to tell what page is currently displayed. In part, that's because the user doesn't necessarily see only one page at a time. He may be viewing the end of one page and the start of the next, or several pages may be displayed - depending on his view settings.
If I can make the question slightly easier, then perhaps I can answer it in a way that helps you. Let me re-define "current active (visible for user) page" as the page where the selection is. (Actually, since the selection can span several pages, let's define it as "the page where the active end of the selection is").
I'll also answer using VBA because it's easier to play around with it in the VBA immediate window, and it's trivial to convert to C# when you need to (it's the same object model, after all).
Word's Selection object has the properties of a Range, and if you simply wanted all the selected words, then this would be trivial (Selection.Words!). However, if we want all the words on that page, then we need to work a little harder.
First, let's find out what page the (start of the) selection is on. For this, we can use the Information method:
pageNumber = Selection.Information(wdActiveEndPageNumber)
So now we know what page we're interested in. We now need to get a Range object that includes all the text on that page. We need to do this in two steps - by finding first the start and then the end of that range.
To find the start of the range, we can use the Goto function, which returns a Range object representing the start of a specified item:
startOfRange = ActiveDocument.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, pageNumber).Start
The end of the range is either the start of the next page (minus one character, but let's not quibble), or the end of the document (if we're on the last page):
If pageNumber = ActiveDocument.Content.Information(wdNumberOfPagesInDocument) Then
endOfRange = ActiveDocument.Content.End
Else
endOfRange = ActiveDocument.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, pageNumber + 1).Start
End If
Now we can construct a Range object encompassing all the text on the page:
Set pageRange = ActiveDocument.Range(startOfRange, endOfRange)
... and from there we can get the words:
Set words = pageRange.Words
Here is a short VBA macro that uses the above technique to report the number of words on the active page:
Sub Test()
Dim pageNumber As Integer
Dim startOfRange As Integer
Dim endOfRange As Integer
Dim pageRange As Range
pageNumber = Selection.Information(wdActiveEndPageNumber)
startOfRange = ActiveDocument.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, pageNumber).Start
If pageNumber = ActiveDocument.Content.Information(wdNumberOfPagesInDocument) Then
endOfRange = ActiveDocument.Content.End
Else
endOfRange = ActiveDocument.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, pageNumber + 1).Start
End If
Set pageRange = ActiveDocument.Range(startOfRange, endOfRange)
MsgBox pageRange.Words.Count
End Sub
UPDATE
OK, it turns out that there's a much easier way to do this. Word has a "special bookmark" that points to the text on the current page, so this will do the same as all that code above:
words = ActiveDocument.Bookmarks("\page").Range.Words

c# excel 2007 automation - how to follow no two dot rule while looping?

I'm using code lines similar to the one below several times throughout the loop. It works all fine EXCEPT it doesn't follow the "no two dot rule", right?
wksheet.Cells(cell.Row, "J").Value
I can store cell.Row as an int, but where to go from there? Is there a function that lets me pass row number and column letter and get the value of that particular cell while still following the rule?
It would be a pain to declare and then set the range variable every time I want to get a particular cell's value inside the loop.
How do I properly clean up Excel interop objects?
^this link explains no two dot rule.
I guess you could break it down
var row = cell.Row;
var cell = wksheet.Cells(row, "J");
var value = cell.Value;

How do I text wrap to next acrofield in iTextSharp?

how do I get text to wrap from one acrofield to the next? I have an adobe pdf doc our client gave us. It has acro fields one atop another (all with the same name). They want the text to wrap from one to another when it reaches the end of the line. All the other examples I see out there do not deal with filling in acro fields that wrap. Please help!
// loop through disabilities and display them
foreach (var disability in formNature.Disabilities)
{
fields.SetField("EVALUATION", disability.PrimaryDisabilityName + "; ");
}
in theory this should loop through all the disabilities they had entered on the web form and display them one after another while text-wrapping when it reaches the end of each line. But instead it only displays one item one the field.
This isn't a complete answer unfortunately.
First, when you call SetField() you are erasing the current contents of the field and replacing it with your new value. When done in a loop only the last value will ever be stored then. What you need to do is loop through each value and concatenate them into one big string.
string buf = '';
foreach (var disability in formNature.Disabilities)
{
buf += disability.PrimaryDisabilityName + "; ";
}
buf = buf.Trim();
Second, the PDF standard to the best of my knowledge does not support chaining of fields for overflow which is what you are looking for. The only way that I know of to accomplish what you are trying is to actually measure the strings and compare them to the widths of the fields and truncate them as needed. To do this you will need to find the font used for the given field, create a BaseFont from it and use that to Measure the string. Then compare that with the field's rectangle and use only the characters that "fit" into that field. Repeat as needed.
That all said, I would really really recommend that you just edit the PDF and replace the multiple fields with one large field that supports multiple lines. Your life will be much, much easier.

Categories

Resources