Bullet points in Word with c# Interop - c#

I have the following code which is supposed to add a bulleted list to a word document that I'm generating automatically. From other answers I believe the code is correct, but the result doesn't produce any bullet points at all, it doesn't seem to apply the indent either.
Any Ideas?
Microsoft.Office.Interop.Word.Paragraph assets;
assets = doc.Content.Paragraphs.Add(Type.Missing);
// Some code to generate the text
foreach (String asset in assetsList)
{
assetText = assetText + asset + "\n";
}
assets.Range.ListFormat.ApplyBulletDefault(Type.Missing);
// Add it to the document
assets.Range.ParagraphFormat.LeftIndent = -1;
assets.Range.Text = assetText;
assets.Range.InsertParagraphAfter();

This happens because you're adding multiple paragraphs to the range after the range (it seems that setting the Text property is equivalent to InsertAfter). You want to InsertBefore the range so that the formatting you set gets applied.
Paragraph assets = doc.Content.Paragraphs.Add();
assets.Range.ListFormat.ApplyBulletDefault();
string[] bulletItems = new string[] { "One", "Two", "Three" };
for (int i = 0; i < bulletItems.Length; i++)
{
string bulletItem = bulletItems[i];
if (i < bulletItems.Length - 1)
bulletItem = bulletItem + "\n";
assets.Range.InsertBefore(bulletItem);
}
Notice that we add an End of Paragraph mark to all items except the last one. You will get an empty bullet if you add one to the last.

This is based on Tergiver's answer. The difference is it inserts the list items in the correct order after the initially created paragraph. For your own use make the starting range equal to the item you want to insert the list after.
Paragraph assets = doc.Content.Paragraphs.Add();
rng = assets.Range;
rng.InsertAfter("\n");
start = rng.End;
end = rng.End;
rng = _oDoc.Range(ref start, ref end);
object listType = 0;
rng.ListFormat.ApplyBulletDefault(ref listType);
string[] bulletItems = new string[] { "One", "Two", "Three" };
for (int i = 0; i < bulletItems.Length; i++)
{
string bulletItem = bulletItems[i];
if (i < RowCount - 1)
bulletItem = bulletItem + "\n";
rng.InsertAfter(bulletItem);
}
Please note I don't really understand what I'm doing with the range here. This solution was arrived at after considerable trial and error. I suspect it may have to do with the fact that I'm reusing the same range and Tergiver's solution is grabbing a new range each time the range is accessed. I particularly don't understand the following lines:
rng.InsertAfter("\n");
start = rng.End;
end = rng.End;
rng = _oDoc.Range(ref start, ref end);
Generally any alterations to the above code and the list gets intermingled with the previous element. If somebody could explain why this works, I'd be grateful.

You can try below code block if you want list-sublist relations:
static void Main(string[] args)
{
try
{
Application app = new Application();
Document doc = app.Documents.Add();
Range range = doc.Range(0, 0);
range.ListFormat.ApplyNumberDefault();
range.Text = "Birinci";
range.InsertParagraphAfter();
ListTemplate listTemplate = range.ListFormat.ListTemplate;
//range.InsertAfter("Birinci");
//range.InsertParagraphAfter();
//range.InsertAfter("İkinci");
//range.InsertParagraphAfter();
//range.InsertAfter("Üçüncü");
//range.InsertParagraphAfter();
Range subRange = doc.Range(range.StoryLength - 1);
subRange.ListFormat.ApplyBulletDefault();
subRange.ListFormat.ListIndent();
subRange.Text = "Alt Birinci";
subRange.InsertParagraphAfter();
ListTemplate sublistTemplate = subRange.ListFormat.ListTemplate;
Range subRange2 = doc.Range(subRange.StoryLength - 1);
subRange2.ListFormat.ApplyListTemplate(sublistTemplate);
subRange2.ListFormat.ListIndent();
subRange2.Text = "Alt İkinci";
subRange2.InsertParagraphAfter();
Range range2 = doc.Range(range.StoryLength - 1);
range2.ListFormat.ApplyListTemplateWithLevel(listTemplate,true);
WdContinue isContinue = range2.ListFormat.CanContinuePreviousList(listTemplate);
range2.Text = "İkinci";
range2.InsertParagraphAfter();
Range range3 = doc.Range(range2.StoryLength - 1);
range3.ListFormat.ApplyListTemplate(listTemplate);
range3.Text = "Üçüncü";
range3.InsertParagraphAfter();
string path = Environment.CurrentDirectory;
int totalExistDocx = Directory.GetFiles(path, "test*.docx").Count();
path = Path.Combine(path, string.Format("test{0}.docx", totalExistDocx + 1));
app.ActiveDocument.SaveAs2(path, WdSaveFormat.wdFormatXMLDocument);
doc.Close();
Process.Start(path);
}
catch (Exception exception)
{
throw;
}
}
Attention this point: If you don't know input length, you must not define the end of range value like this:
static void Main(string[] args)
{
try
{
Application app = new Application();
Document doc = app.Documents.Add();
Range range = doc.Range(0, 0);
range.ListFormat.ApplyNumberDefault();
range.Text = "Birinci";
range.InsertParagraphAfter();
ListTemplate listTemplate = range.ListFormat.ListTemplate;
//range.InsertAfter("Birinci");
//range.InsertParagraphAfter();
//range.InsertAfter("İkinci");
//range.InsertParagraphAfter();
//range.InsertAfter("Üçüncü");
//range.InsertParagraphAfter();
Range subRange = doc.Range(range.StoryLength - 1, range.StoryLength - 1);
subRange.ListFormat.ApplyBulletDefault();
subRange.ListFormat.ListIndent();
subRange.Text = "Alt Birinci";
subRange.InsertParagraphAfter();
ListTemplate sublistTemplate = subRange.ListFormat.ListTemplate;
Range subRange2 = doc.Range(subRange.StoryLength - 1, range.StoryLength - 1);
subRange2.ListFormat.ApplyListTemplate(sublistTemplate);
subRange2.ListFormat.ListIndent();
subRange2.Text = "Alt İkinci";
subRange2.InsertParagraphAfter();
Range range2 = doc.Range(range.StoryLength - 1, range.StoryLength - 1);
range2.ListFormat.ApplyListTemplateWithLevel(listTemplate,true);
WdContinue isContinue = range2.ListFormat.CanContinuePreviousList(listTemplate);
range2.Text = "İkinci";
range2.InsertParagraphAfter();
Range range3 = doc.Range(range2.StoryLength - 1, range.StoryLength - 1);
range3.ListFormat.ApplyListTemplate(listTemplate);
range3.Text = "Üçüncü";
range3.InsertParagraphAfter();
string path = Environment.CurrentDirectory;
int totalExistDocx = Directory.GetFiles(path, "test*.docx").Count();
path = Path.Combine(path, string.Format("test{0}.docx", totalExistDocx + 1));
app.ActiveDocument.SaveAs2(path, WdSaveFormat.wdFormatXMLDocument);
doc.Close();
Process.Start(path);
}
catch (Exception exception)
{
throw;
}
}

You just need to keep track of the start and end positions of the list and then apply the list format.
Application wordApp = new Application() {
Visible = true
};
Document doc = wordApp.Documents.Add();
Range range = doc.Content;
range.Text = "Hello world!";
range.InsertParagraphAfter();
range = doc.Paragraphs.Last.Range;
// start of list
int startOfList = range.Start;
// each \n character adds a new paragraph...
range.Text = "Item 1\nItem 2\nItem 3";
// ...or insert a new paragraph...
range.InsertParagraphAfter();
range = doc.Paragraphs.Last.Range;
range.Text = "Item 4\nItem 5";
// end of list
int endOfList = range.End;
// insert the next paragraph before applying the format, otherwise
// the format will be copied to the suceeding paragraphs.
range.InsertParagraphAfter();
// apply list format
Range listRange = doc.Range(startOfList, endOfList);
listRange.ListFormat.ApplyBulletDefault();
range = doc.Paragraphs.Last.Range;
range.Text = "Bye for now!";
range.InsertParagraphAfter();

Related

Italic inline word with Interop.Word

I've starting learning C#, mostly for the purpose of MS Word automation. Using Interop.Word, how can I add a line with words "one two three" with two being italic? The closest I can get is something like this:
//text with some italic words.
para.Range.Text = "one ";
Console.WriteLine(para.Range.Start);
Console.WriteLine(para.Range.End);
// <some magic methods that end the last range and start a new one in place>
para.Range.Text = "two";
para.Range.Font.Italic = 1;
Console.WriteLine(para.Range.Start);
Console.WriteLine(para.Range.End);
// <some magic methods that end the last range and start a new one in place>
para.Range.Text = " three";
Console.WriteLine(para.Range.Start);
Console.WriteLine(para.Range.End);
para.Range.InsertParagraphAfter();
As for the method I need, I tried many things, but none of them worked. The MSDN documentation is very hard to read and omit many important details.
EDIT: I finally made it worked, by creating a new range object for every word. This is about as ugly as I could imagine but as least it works:
Word.Range rng = word_doc.Range(para.Range.End - 1, para.Range.End);
rng.Text = "one ";
Console.WriteLine(rng.Start);
Console.WriteLine(rng.End);
rng = word_doc.Range(rng.End - 1, rng.End);
rng.Text = "two";
rng.Font.Italic = 1;
Console.WriteLine(rng.Start);
Console.WriteLine(rng.End);
rng = word_doc.Range(rng.End - 1, rng.End);
rng.Text = " three";
rng.Font.Italic = 0;
Console.WriteLine(rng.Start);
Console.WriteLine(rng.End);
para.Range.InsertParagraphAfter();
This is the closest I could get, the main issue with this code I made for you is that it doesn't add the text back inline, but instead adds a new line for each word it finds. Hopefully this code gives you some ideas on how to best programmatically create word documents!
Document extendedDocument = Globals.Factory.GetVstoObject(Globals.ThisAddIn.Application.ActiveDocument);
Word.Selection currentSelection = Globals.ThisAddIn.Application.Selection;
Word.Paragraph para;
para = extendedDocument.Content.Paragraphs.Add(ref oMissing);
para.Range.SetRange(currentSelection.Range.Start, currentSelection.Range.End);
string string1 = "one two three";
string split1 = " ";
string match1 = "two";
string[] elements = Regex.Split(string1, split1);
foreach (var m in elements)
{
if (m.Equals(match1))
{
para.Range.Text = m + " ";
para.Range.Font.Italic = 1;
}
else
{
para.Range.Text = m + " ";
para.Range.Font.Italic = 0;
}
para.Range.InsertParagraphAfter();
}
Edit: Have a good weekend! I will try to check my SO inbox over the weekend, but I may not reply to any questions till Monday.

using Aspose Words to replace page numbers with barcodes

this may be a silly question but I cannot work out an answer to it and after a day I am turning to the community at large for help...
I am using Aspose for Word (C# or .Net) and I am trying to replace the generated page numbering for barcode images of my own creation. I can use fonts to do it currently but I have found they are less reliable with my barcode reader and thus need to be able to read the value from the page numbering and replace it with an image of my own creation.
So really I need to find the numbering container, read the value in it and replace it. Once I have that creating the barcode and inserting it is easy.
Can anyone help?
The current method (sorry its messy but i keep trying new things):
internal static void SetFooters(ref Document doc)
{
doc.FirstSection.HeadersFooters.LinkToPrevious(false);
var builder = new DocumentBuilder(doc);
builder.MoveToDocumentStart();
Section currentSection = builder.CurrentSection;
PageSetup pageSetup = currentSection.PageSetup;
int totalPages = doc.PageCount;
int j = 1;
foreach (Section sect in doc.Sections)
{
//Loop through all headers/footers
foreach (HeaderFooter hf in sect.HeadersFooters)
{
if (
hf.HeaderFooterType == HeaderFooterType.FooterPrimary || hf.HeaderFooterType == HeaderFooterType.FooterEven || hf.HeaderFooterType == HeaderFooterType.FooterFirst)
{
builder.MoveToHeaderFooter(hf.HeaderFooterType);
Field page = builder.InsertField("PAGE");
builder.Document.UpdatePageLayout();
try
{
page.Update();
}
catch { }
int pageNumber = j;
if (int.TryParse(page.Result, out pageNumber))
{ j++; }
// Remove PAGE field.
page.Remove();
builder.Write(string.Format("{0}/{1}", pageNumber, totalPages));
}
}
}
}
HeaderFooter is a section-level node and can only be a child of Section. The page field inside the header/footer returns the latest updated value and it will be same value for all pages of a section.
In your case, I suggest you to insert text-box at the top/bottom of each page and inset the desired contents in it. Following code example inserts the text-box on each page of document and insert page field and some text in it. Hope this helps you.
public static void InsertTextBoxAtEachPage()
{
string filePathIn = MyDir + #"input.docx";
string filePathOut = MyDir + #"output.docx";
Document doc = new Document(filePathIn);
DocumentBuilder builder = new DocumentBuilder(doc);
LayoutCollector collector = new LayoutCollector(doc);
int pageIndex = 1;
foreach (Section section in doc.Sections)
{
NodeCollection paragraphs = section.Body.GetChildNodes(NodeType.Paragraph, true);
foreach (Paragraph para in paragraphs)
{
if (collector.GetStartPageIndex(para) == pageIndex)
{
builder.MoveToParagraph(paragraphs.IndexOf(para), 0);
builder.StartBookmark("BM_Page" + pageIndex);
builder.EndBookmark("BM_Page" + pageIndex);
pageIndex++;
}
}
}
collector = new LayoutCollector(doc);
LayoutEnumerator layoutEnumerator = new LayoutEnumerator(doc);
const int PageRelativeY = 0;
const int PageRelativeX = 0;
foreach (Bookmark bookmark in doc.Range.Bookmarks)
{
if (bookmark.Name.StartsWith("BM_"))
{
Paragraph para = (Paragraph)bookmark.BookmarkStart.ParentNode;
Shape textbox = new Shape(doc, Aspose.Words.Drawing.ShapeType.TextBox);
textbox.Top = PageRelativeY;
textbox.Left = PageRelativeX;
int currentPageNumber = collector.GetStartPageIndex(para);
string barcodeString = string.Format("page {0} of {1}", currentPageNumber, doc.PageCount);
string barcodeEncodedString = "some barcode string";
Paragraph paragraph = new Paragraph(doc);
ParagraphFormat paragraphFormat = paragraph.ParagraphFormat;
paragraphFormat.Alignment = ParagraphAlignment.Center;
Aspose.Words.Style paragraphStyle = paragraphFormat.Style;
Aspose.Words.Font font = paragraphStyle.Font;
font.Name = "Tahoma";
font.Size = 12;
paragraph.AppendChild(new Run(doc, barcodeEncodedString));
textbox.AppendChild(paragraph);
paragraph = new Paragraph(doc);
paragraphFormat = paragraph.ParagraphFormat;
paragraphFormat.Alignment = ParagraphAlignment.Center;
paragraphStyle = paragraphFormat.Style;
font = paragraphStyle.Font;
font.Name = "Arial";
font.Size = 10;
paragraph.AppendChild(new Run(doc, barcodeString));
textbox.AppendChild(paragraph);
//Set the width height according to your requirements
textbox.Width = doc.FirstSection.PageSetup.PageWidth;
textbox.Height = 50;
textbox.BehindText = false;
para.AppendChild(textbox);
textbox.RelativeHorizontalPosition = Aspose.Words.Drawing.RelativeHorizontalPosition.Page;
textbox.RelativeVerticalPosition = Aspose.Words.Drawing.RelativeVerticalPosition.Page;
bool isInCell = bookmark.BookmarkStart.GetAncestor(NodeType.Cell) != null;
if (isInCell)
{
var renderObject = collector.GetEntity(bookmark.BookmarkStart);
layoutEnumerator.Current = renderObject;
layoutEnumerator.MoveParent(LayoutEntityType.Cell);
RectangleF location = layoutEnumerator.Rectangle;
textbox.Top = PageRelativeY - location.Y;
textbox.Left = PageRelativeX - location.X;
}
}
}
doc.Save(filePathOut, SaveFormat.Docx);
}
I work with Aspose as Developer evangelist.

Systematically remove first line

I am trying to remove the first line of a paragraph when the total lines exceed a predetermined number of entries. This is for a kind of chat window and I do not want too many lines displayed at one time.
private Paragraph paragraph = new Paragraph();
public void WriteMessage(string output)
{
string outputFormat = string.Format("{0}", output);
string[] parts = output.Split(new char[]{':'}, 2);
string user = parts[0];
string[] username = parts[0].Split('!');
paragraph.Inlines.Add(new Run(username[0].Trim() + ": "){Foreground = UserColor});
paragraph.Inlines.Add(new Run(parts[1]) { Foreground = MessageColor});
paragraph.Inlines.Add(new LineBreak());
if (paragraph.Inlines.Count >= 50) {
//???
//The count does not actually count lines the way I would expect.
}
}
Not sure of the easiest way to do this, everything I have tried thus far has not worked.
Suggest you use List verus array. It gives you some functionality you need.
public List<string> TrimParagraph(List<string> paragraph)
{
int count = paragraph.Count;
if (count > 50)
paragraph = paragraph.Skip(count - 50).ToList();
return paragraph;
}
Edit... Use something like this when constructing your paragraph object.
Solved it by creating a FlowDocument and adding the paragraph to the block. Then each entry is it's own block and it retains the original formatting.
private Paragraph paragraph = new Paragraph();
_rtbDocument = new FlowDocument(paragraph);
public void WriteMessage(string output)
{
string outputFormat = string.Format("{0}", output);
string[] parts = output.Split(new char[]{':'}, 2);
string user = parts[0];
string[] username = parts[0].Split('!');
Paragraph newline = new Paragraph();
newline.LineHeight = 2;
newline.Inlines.Add(new Run(username[0].Trim() + ": ") { Foreground = UserColor });
newline.Inlines.Add(new Run(parts[1]) { Foreground = MessageColor });
_rtbDocument.Blocks.Add(newline);
if (_rtbDocument.Blocks.Count > 10)
{
_rtbDocument.Blocks.Remove(_rtbDocument.Blocks.FirstBlock);
}
}

How do i highlight a background text in a text file?

I have a dictionary collection that stores the starting position and the charecter values of a text file.
For example: a sample text file (a.txt) may contain text like "how are you? how do you do?"
I have indexed the above text as follows
Dictionary<long,string> charLocation = new Dictionary<long,string>();
charLocation[0] = "how"
charLocation[1] = "ow"
charLocation[2] = "w"
charLocation[4] = "are"
charLocation[6] = "e"
charLocation[5] = "re"
charLocation[11] = "?"
charLocation[9] = "ou?"
charLocation[10] = "u?"
charLocation[8] = "you?"
charLocation[13] = "how"
charLocation[14] = "ow"
charLocation[15] = "w"
charLocation[17] = "do"
charLocation[18] = "o"
charLocation[21] = "ou"
charLocation[22] = "u"
charLocation[20] = "you"
charLocation[26] = "?"
charLocation[24] = "do?"
charLocation[25] = "o?"
Now, I want to highlight each occurrence of "how" or "do" in the text file.
For this I want to first do a lookup in the dictionary collection and find each occurrence of the string, then open the text file and highlight the text for each occurrence.
How can i do this?
Not tested, but this should works.
public string HighLight (int startPoint, string text, string word)
{
if (startPoint > = 0)
{
int startIndex = text.indexOf (word, startPoint);
if (startIndex >= 0)
{
StringBuilder builder = new StringBuilder ();
builder.Append (text.Substring ( 0, startIndex));
builder.Append ("<strong>");
builder.Append (text.Substring (startIndex + 1, word.Length));
builder.Append ("</strong>");
builder.Append (text.Substring (startIndex + word.Length + 1));
return HighLight ((startIndex + "<strong>".Length + "</strong>".Length + word.Length, builder.ToString (), word);
}
}
//Word not found.
return text;
}
So you could do :
string myText = "how are you? how do you do?";
string hightLightedText = HighLight (0, myText, "how");
And if my code has no errors, that would returns "<strong>how</strong> are you? <strong>how</strong> do you do?";
Then you can repalce <strong> and </strong> with wathever you want to "highlight" your text.

Shuffle string array without duplicates

I am using the Knuth-Fisher-Yates algorithm to display a shuffled array of string items on a windows form. I do not get any duplicates, which is what I was trying to achieve, however, it only spits out 12 of the 13 elements of the array. How can I get it to display all 13 elements of the array? Here is my code:
private void FormBlue1_Load(object sender, EventArgs e)
{
// set the forms borderstyle
this.FormBorderStyle = FormBorderStyle.Fixed3D;
// create array of stationOneParts to display on form
string[] stationOneParts = new string[13];
stationOneParts[0] = "20-packing";
stationOneParts[1] = "5269-stempad";
stationOneParts[2] = "5112-freeze plug";
stationOneParts[3] = "2644-o'ring";
stationOneParts[4] = "5347-stem";
stationOneParts[5] = "4350-top packing";
stationOneParts[6] = "5084-3n1 body";
stationOneParts[7] = "4472-packing washer";
stationOneParts[8] = "3744-vr valve o'ring";
stationOneParts[9] = "2061-packing spring";
stationOneParts[10] = "2037-packing nut";
stationOneParts[11] = "2015-latch ring";
stationOneParts[12] = "stem assembly";
Random parts = new Random();
// loop through stationOneParts using a Swap method to shuffle
labelBlueOne.Text = "\n";
for (int i = stationOneParts.Length - 1; i > 0; i--)
{
int j = parts.Next(i + 1);
Swap(ref stationOneParts[i], ref stationOneParts[j]);
// display in a random order
labelBlueOne.Text += stationOneParts[i] + "\n";
}
}
private void Swap(ref string firstElement, ref string secondElement)
{
string temp = firstElement;
firstElement = secondElement;
secondElement = temp;
}
You don't access the first element.
for (int i = stationOneParts.Length - 1; i >= 0; i--).
As you are showing the texts using the loop that swaps the items, you will not show the last item, because it's never swapped by itself.
Just show the last item after the loop:
labelBlueOne.Text += stationOneParts[0] + "\n";
Alternatively, you can display all the items outside the loop that shuffles them:
for (int i = stationOneParts.Length - 1; i > 0; i--) {
Swap(ref stationOneParts[i], ref stationOneParts[parts.Next(i + 1)]);
}
labelBlueOne.Text = "\n" + String.Join("\n", stationOneParts);
Change your loop condition to i >= 0.
Simpliest approach :
Random rnd = new Random();
var stationOneParts = new List<string>{
"20-packing",
"5269-stempad",
"5112-freeze plug",
"2644-o'ring",
"5347-stem",
"4350-top packing",
"5084-3n1 body",
"4472-packing washer",
"3744-vr valve o'ring",
"2061-packing spring",
"2037-packing nut",
"2015-latch ring",
"stem assembly"}.OrderBy(s => rnd.Next());
labelBlueOne.Text = string.Join(Environment.NewLine, stationOneParts);
Since you mention C# 4.0, why not write is C#-ish?
using System.Linq;
// ...
var stationOneParts = new [] { "20-packing",
"5269-stempad",
"5112-freeze plug",
"2644-o'ring",
"5347-stem",
"4350-top packing",
"5084-3n1 body",
"4472-packing washer",
"3744-vr valve o'ring",
"2061-packing spring",
"2037-packing nut",
"2015-latch ring",
"stem assembly" };
Random rand = new Random();
stationOneParts = stationOneParts
.Distinct() // see subject: '... without duplicates'
.Select(i => new { i, key=rand.Next() })
.OrderBy(p => p.key)
.Select(p => p.i)
.ToArray();

Categories