How do you have a bulletted list in migradoc / pdfsharp - c#

even after reading this forum post, its still quite confusing how to create a bulletted list using migradoc / pdfsharp. I basically want to display a list of items like this:
Dodge
Nissan
Ford
Chevy

Here's a sample (a few lines added to the HelloWorld sample):
// Add some text to the paragraph
paragraph.AddFormattedText("Hello, World!", TextFormat.Italic);
// Add Bulletlist begin
Style style = document.AddStyle("MyBulletList", "Normal");
style.ParagraphFormat.LeftIndent = "0.5cm";
string[] items = "Dodge|Nissan|Ford|Chevy".Split('|');
for (int idx = 0; idx < items.Length; ++idx)
{
ListInfo listinfo = new ListInfo();
listinfo.ContinuePreviousList = idx > 0;
listinfo.ListType = ListType.BulletList1;
paragraph = section.AddParagraph(items[idx]);
paragraph.Style = "MyBulletList";
paragraph.Format.ListInfo = listinfo;
}
// Add Bulletlist end
return document;
I didn't use the AddToList method to have it all in one place. In a real application I'd use that method (it's a user-defined method, code given in this thread).

A little bit more concise than the above answer:
var document = new Document();
var style = document.AddStyle("BulletList", "Normal");
style.ParagraphFormat.LeftIndent = "0.5cm";
style.ParagraphFormat.ListInfo = new ListInfo
{
ContinuePreviousList = true,
ListType = ListType.BulletList1
};
var section = document.AddSection();
section.AddParagraph("Bullet 1", "BulletList");
section.AddParagraph("Bullet 2", "BulletList");
Style is only created once, including listinfo, and can be re-used everywhere.

With PDFsharp you must draw the bullets yourself.
With MigraDoc you add a paragraph and set paragraph.Format.ListInfo for this paragraph to create a bullet list.
The linked thread shows two helper routines:
DefineList() only sets a member variable so next time a new list will be created.
AddToList() is called for each entry.
Simply call DefineList() to start a new bullet list, then call AddToList() for every entry.
DefineList() makes a big difference for numbered lists.
Adapt the helper routines for your needs.

Related

Not able to copy specific pages of word document

I am trying to cut specific pages of my word document(.docx), say 2, 4. I am using for loop to traverse as per the page gave splitting it based on ,.Below is the code for the same
if (startEnd.Contains(','))
{
arrSpecificPage = startEnd.Split(',');
for (int i = 0; i < arrSpecificPage.Length; i++)
{
range.Start = doc.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, arrSpecificPage[i]).Start;
range.End = doc.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, arrSpecificPage[i]).End;
range.Copy();
newDocument.Range().Paste();
}
newDocument.SaveAs(outputSplitDocpath);
}
but the issue with this code is that its just copying the last page only to the new document i.e 4 in this case. How to add 2 as well? What's wrong in the code?
Since you always specify the entire document "range" as the target, each time you paste the entire content of the document is replaced.
It's correct that you work with a Range object and not with a selection, but it helps if you think about a Range like a selection. If you select everything (Ctrl+A) then paste, what was selected is replaced by what is pasted. Whatever is assigned to a Range will replace the content of the Range.
The way to solve this is to "collapse" the Range - think of it like pressing the Right-arrow or left-arrow key to "collapse" a selection to its start or end point. In the object model, this is the Collapse method that takes a parameter indicating whether to collapse to the start or end point (see the code below).
Note that I've also changed the code to use document.Content instead of Document.Range. Content is a property that returns the entire body of the document; Rangeis a method that expects a start and end point defining a Range. Using the property is the preferred method for the entire document.
if (startEnd.Contains(','))
{
arrSpecificPage = startEnd.Split(',');
for (int i = 0; i < arrSpecificPage.Length; i++)
{
range.Start = doc.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, arrSpecificPage[i]).Start;
range.End = doc.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, arrSpecificPage[i]).End;
range.Copy();
Word.Range targetRange = newDocument.Content
targetRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
targetRange.Paste();
}
newDocument.SaveAs(outputSplitDocpath);
}

Randomizing images in picture boxes without repeating

I am pretty new in the programming scene and I want to be able to use a button to show random photos in multiple picture boxes. The thing is that I don't want one photo to show in multiple boxes. So every pictureBox should contain different images. I have searched Google for the past few hours but I haven't been able to get any useful information. What I have now, when pressing the button, every pictureBox goes blank. Though here is my code in the button1_Click :
{
List<string> name = new List<string>();
name.Add("0.jpg");
name.Add("1.jpg");
name.Add("2.jpg");
name.Add("3.png");
List<PictureBox> box = new List<PictureBox>();
box.Add(pictureBox1);
box.Add(pictureBox2);
box.Add(pictureBox3);
box.Add(pictureBox4);
a = 4;
ResourceManager rm = MatchGame.Properties.Resources.ResourceManager;
for (int i = 0; i < box.Count; i++)
{
int randomPic = new Random().Next(0, name.Count);
string randomName = name[randomPic];
name.Remove(randomName);
Image img = rm.GetObject(randomName) as Image;
box[i].Image = img;`
}
}
An easy way would be to simply shuffle in random order your name list
List<string> name = new List<string>();
name.Add("0.jpg");
name.Add("1.jpg");
name.Add("2.jpg");
name.Add("3.png");
List<PictureBox> box = new List<PictureBox>();
box.Add(pictureBox1);
box.Add(pictureBox2);
box.Add(pictureBox3);
box.Add(pictureBox4);
// 2 lines code for shuffle every kind of IEnumerable
Random r = new Random();
name = name.OrderBy(x => r.Next()).ToList();
ResourceManager rm = MatchGame.Properties.Resources.ResourceManager;
for (int i = 0; i < box.Count; i++)
{
// no need to remove elements from name list
string randomName = name[i];
Image img = rm.GetObject(randomName) as Image;
box[i].Image = img;`
}
this will assure that every picture picked once and only once (as long, of course, number of pictureboxes is the same of images stored in resource).
Be sure that every rm.GetObject returns a different image.
As a side note, never create a new Random() within a loop: instantiate a single Random and keep calling .Next on it (see this question). The above code would be wrong in this way:
name = name.OrderBy(x => new Random.Next()).ToList();
What you could do is store the picture references in a Dictionary.
Associating the picture names with the PictureBox indexes - then all you need to do is check the dictionary values and see if the picturename is in the dictionary. If it is in the dictionary - then just let the while loop do another loop - to pick another image. To Recycle that all you would need to do is clear the dictionary and the process could start over again.
Dictionary<int, string> MyActivePictures = new Dictionary<int, string>();
Use a concurrentDictionary if you are multithreading.
// Where MyActivePictures < PictureBoxControl , picturename > is the Dictionary
Dictionary<int, string> MyActivePictures = new Dictionary<int, string>();
// i is your PictureBoxes index as you loop through them
int i = 0;
if(box.Count < name.Length){
do
{
int randomPic = new Random().Next(0, name.Count);
string randomName = name[randomPic];
if(!MyActivePictures.Values.Contains[randomName])
{
name.Remove(randomName);
Image img = rm.GetObject(randomName) as Image;
box[i].Image = img;
MyActivePictures[i]=randomName;
i++;
}
if (i > name.Length) // exits the loop in case there are more
{
i = box.Count + 1;
}
}while (i < box.Count);
}
I should add that if it is not possible for the above code to get a unique picture - for example picture boxes > number of images. Then this code will hang in an endless loop. You will need to factor in for that scenario in the while loop - if(the iterator I of pictureboxes value > total images - exit the loop. so if(i > totalimages) exit loop.
I added the additional code to handle this: however
You could just simply include that test in the while condition as well - it is easier ( (i names.Length))

Creating a multi-level bullet list with Word.Interop

I need to create a multi-level bullet list via Microsoft.Office.Interop.Word and I am currently struggling with its (horrible) API (again).
I've just created the following example (not dynamic yet, just for demonstration purposes) in a VSTO document-level project for Microsoft Office Word 2010 in the programming language C#:
Word.Paragraph paragraph = null;
Word.Range range = this.Content;
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = "Item 1";
paragraph.Range.ListFormat.ApplyBulletDefault(Word.WdDefaultListBehavior.wdWord10ListBehavior);
// ATTENTION: We have to outdent the paragraph AFTER its list format has been set, otherwise this has no effect.
// Without this, the the indent of "Item 2" differs from the indent of "Item 1".
paragraph.Outdent();
paragraph.Range.InsertParagraphAfter();
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = "Item 1.1";
// ATTENTION: We have to indent the paragraph AFTER its text has been set, otherwise this has no effect.
paragraph.Indent();
paragraph.Range.InsertParagraphAfter();
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = "Item 1.2";
paragraph.Range.InsertParagraphAfter();
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = "Item 2";
paragraph.Outdent();
The code does exactly what I want (after a lot of try and error!), but it's horrible in my opinion. The format has to be applied at a VERY specific point and I have to manually indent and outdent the created paragraphs.
So my question is: Does a better approach exist to create a multi-level bullet list via Word.Interop, e.g. via shorthand methods that I haven't discovered yet?
My goal is to create a multi-level list from XML data (more specific a CustomXMLNode object)
Two other questions related to bullet lists exist on Stack Overflow, but both do not help me (the source code above is one answer to the second question):
Bullet points in Word with c# Interop
https://stackoverflow.com/questions/3768414/ms-word-list-with-sub-lists
EDIT (2013-08-08):
I've just hacked something together that outputs two arrays as a bullet list with two levels (the array with the sub-items is used for each root-item, to keep it simple). By introducing recursion, one would be able to create a bullet list with infinite levels (theoretically). But the problem remains, the code is a mess...
string[] rootItems = new string[]
{
"Root Item A", "Root Item B", "Root Item C"
};
string[] subItems = new string[]
{
"Subitem A", "Subitem B"
};
Word.Paragraph paragraph = null;
Word.Range range = this.Content;
bool appliedListFormat = false;
bool indented = false;
for (int i = 0; i < rootItems.Length; ++i)
{
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = rootItems[i];
if (!appliedListFormat)
{
paragraph.Range.ListFormat.ApplyBulletDefault(Word.WdDefaultListBehavior.wdWord10ListBehavior);
appliedListFormat = true;
}
paragraph.Outdent();
paragraph.Range.InsertParagraphAfter();
for (int j = 0; j < subItems.Length; ++j)
{
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = subItems[j];
if (!indented)
{
paragraph.Indent();
indented = true;
}
paragraph.Range.InsertParagraphAfter();
}
indented = false;
}
// Delete the last paragraph, since otherwise the list ends with an empty sub-item.
paragraph.Range.Delete();
EDIT (2013-08-12):
Last friday I thought I have achieved what I wanted to, but this morning I noticed, that my solution only works if the insertion point is at the end of the document. I've created the following simple example to demonstrate the (erroneous) behavior. To conclude my problem: I am able to create multi-level bullet lists at the end of the document only. As soon as I change the current selection (e.g. to the start of the document), the list is destroyed. As far as I can see, this is related to the (automatic or non-automatic) extension of the Range objects. I've tried a lot so far (I'm almost losing it), but it's all cargo-cult to me. The only thing I want to do is to insert one element after another (is it impossible to create a content control inside a paragraph, so that the text of the paragraph is followed by the content control?) and to to that in any Range of a Document. I will create a Gist on GitHub with my actual CustomXMLPart binding class this evening. Eventually someone can help me to fix that bothersome problem.
private void buttonTestStatic_Click(object sender, RibbonControlEventArgs e)
{
Word.Range range = Globals.ThisDocument.Application.Selection.Range;
Word.ListGallery listGallery = Globals.ThisDocument.Application.ListGalleries[Word.WdListGalleryType.wdBulletGallery];
Word.Paragraph paragraph = null;
Word.ListFormat listFormat = null;
// TODO At the end of the document, the ranges are automatically expanded and inbetween not?
paragraph = range.Paragraphs.Add();
listFormat = paragraph.Range.ListFormat;
paragraph.Range.Text = "Root Item A";
this.ApplyListTemplate(listGallery, listFormat, 1);
paragraph.Range.InsertParagraphAfter();
paragraph = paragraph.Range.Paragraphs.Add();
listFormat = paragraph.Range.ListFormat;
paragraph.Range.Text = "Child Item A.1";
this.ApplyListTemplate(listGallery, listFormat, 2);
paragraph.Range.InsertParagraphAfter();
paragraph = paragraph.Range.Paragraphs.Add();
listFormat = paragraph.Range.ListFormat;
paragraph.Range.Text = "Child Item A.2";
this.ApplyListTemplate(listGallery, listFormat, 2);
paragraph.Range.InsertParagraphAfter();
paragraph = paragraph.Range.Paragraphs.Add();
listFormat = paragraph.Range.ListFormat;
paragraph.Range.Text = "Root Item B";
this.ApplyListTemplate(listGallery, listFormat, 1);
paragraph.Range.InsertParagraphAfter();
}
private void ApplyListTemplate(Word.ListGallery listGallery, Word.ListFormat listFormat, int level = 1)
{
listFormat.ApplyListTemplateWithLevel(
listGallery.ListTemplates[level],
ContinuePreviousList: true,
ApplyTo: Word.WdListApplyTo.wdListApplyToSelection,
DefaultListBehavior: Word.WdDefaultListBehavior.wdWord10ListBehavior,
ApplyLevel: level);
}
EDIT (2013-08-12): I've set up a GitHub repository here which demonstrates my problem with the Word.Range objects. The OnClickButton method in the file Ribbon.cs invokes my custom mapper class. The comments there describe the problem. I know that my problems are related to the argument Word.Range object reference, but all other solutions I tried (e.g. modifying the range inside of the class) failed even harder. The best solution I've achieved so far, is to specify the Document.Content range as the argument for the MapToCustomControlsIn method. This inserts a nicely formatted multi-level bullet list (with custom XML parts bound to content controls) to the end of the document. What I want is to insert that list at a custom posiztion into the document (e.g. the current selection via Word.Selection.Range).
Florian Wolters example almost there, but the first child item numbering always not correct when I tried.
Someone gave me inspiration by suggesting using Macro and VBA script then convert to C#.
Below is the sample code tested works at my side. Hope it helps.
using Microsoft.Office.Interop.Word;
using System.Reflection;
namespace OfficeUtility
{
public class NumberListGenerate
{
public void GenerateList()
{
Application app = null;
Document doc = null;
string filePath = "c:\\output.docx";
string pdfPath = "c:\\export.pdf";
try
{
app = new Application();
app.Visible = false; // Open Microsoft Office in background
doc = app.Documents.Open(filePath, Missing.Value, false);
Range range = doc.Range();
string search = "$list";
// Find in document to generate list
while (range.Find.Execute(search))
{
ListGallery listGallery =
app.ListGalleries[WdListGalleryType.wdNumberGallery];
// Select found location
range.Select();
// Apply multi level list
app.Selection.Range.ListFormat.ApplyListTemplateWithLevel(
listGallery.ListTemplates[1],
ContinuePreviousList: false,
ApplyTo: WdListApplyTo.wdListApplyToWholeList,
DefaultListBehavior: WdDefaultListBehavior.wdWord10ListBehavior);
// First level
app.Selection.TypeText("Root Item A"); // Set text to key in
app.Selection.TypeParagraph(); // Simulate typing in MS Word
// Go to 2nd level
app.Selection.Range.ListFormat.ListIndent();
app.Selection.TypeText("Child Item A.1");
app.Selection.TypeParagraph();
app.Selection.TypeText("Child Item A.2");
app.Selection.TypeParagraph();
// Back to 1st level
app.Selection.Range.ListFormat.ListOutdent();
app.Selection.TypeText("Root Item B");
app.Selection.TypeParagraph();
// Go to 2nd level
app.Selection.Range.ListFormat.ListIndent();
app.Selection.TypeText("Child Item B.1");
app.Selection.TypeParagraph();
app.Selection.TypeText("Child Item B.2");
app.Selection.TypeParagraph();
// Delete empty item generated by app.Selection.TypeParagraph();
app.Selection.TypeBackspace();
}
// Save document
doc.Save();
// Export to pdf
doc.ExportAsFixedFormat(pdfPath, WdExportFormat.wdExportFormatPDF);
}
catch (System.Exception ex)
{
LogError(ex);
}
finally
{
if (doc != null)
{
// Need to close the document to prevent deadlock
doc.Close(false);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(doc);
}
if (app != null)
{
app.Quit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(app);
}
}
}
}
}
A summary of what needs to be done.
1.) Select the Range
2.) Configure the ListFormat Property of the Range. Must set DefaultListBehavior
to WdDefaultListBehavior.wdWord10ListBehavior
3.)Add your Level one text.
4.)Add a paragraph
5.)Add your level two text.
6.)Set ListLevelTier to level 2
public void GenerateMultiLevelList()
{
//A Range to write Text
Word.Range writRange = ActiveDoc.Range(0);
writRange.Text = "Tier 1 Bullet Text";
//Moving the Range to the End of Previously Entered Text
writRange = ActiveDoc.Range(writRange.End - 1);
//Formating the Range as a Bullet Point List
writRange.ListFormat.ApplyListTemplate(BulletListTemplate, true, Word.WdListApplyTo.wdListApplyToWholeList, Word.WdDefaultListBehavior.wdWord10ListBehavior);
//Adding a Paragraph
writRange.Paragraphs.Add();
writRange = ActiveDoc.Range(writRange.End - 1);
writRange.Text = "Tier 2 Bullet Text";
//Setting the List Level to 2
writRange.SetListLevel(2);
}

Using a string in place of an object when accessing it

First off I'm sure that I am using the wrong terminology here but I will fix it if someone comments on it. Please be gentle.
So I have multiple charts on a page and I am performing virtually identical actions on each. For demonstrative purposes lets call my charts something like: chart1, chart2, ..., chartn where n is somewhere in the vicinity of 20. What I would like to do is drop this in a for loop and perform all the work in one smaller chunk of code, especially if I have to tweak it later.
So my question is whether or not I can vary the n part representing the object (terminology?) so I can get this done more efficiently.
i.e.:
for(int i = 0; i < 20; i++)
{
String chartName = "chart" + i;
chartName.Series.Clear();
}
I have a feeling you can't do this with a string so I was looking into doing a foreach but I don't know how to do this with charts.
Thanks a lot!
You should put the charts in a list. For example, this makes a list of Chart objects (or whatever your chart type is):
List<Chart> charts = new List<Chart>();
Then you can add charts:
charts.Add(new Chart());
And use them:
for (int i = 0; i < charts.Count; i++)
{
charts[i].Series.Clear();
}
Of course, you can make the charts variable a field in your class.
You can directly initialize a list (or array, or dictionary1) like this:
List<Chart> charts = new List<Charts>()
{
new Chart(),
new Chart(),
existingChart1,
existingChart2
};
Or, if you create a new array of objects using that syntax...
Chart[] arrayOfCharts = new []
{
new Chart(),
new Chart(),
existingChart1,
existingChart2
};
...then you can add multiple objects at once using AddRange:
charts.AddRange(arrayOfCharts);
1) You can use this so-called collection initializer syntax on any object that has a public Add method.
Can you access your chart from a list/array/collection of charts like this?
for (int i = 0; i <= 19; i++) {
String chartName = "chart" + i;
Charts(chartName).Series.Clear();
}
or maybe
for (int i = 0; i <= 19; i++) {
String chartName = "chart" + i;
Charts(i).Series.Clear();
}

How to add items one at a time to to a new line a word document using word interop

I am trying to add these three types of content into a word doc. This is how I am trying to do it now. However, each item replaces the last one. Adding images always adds to the beginning of the page. I have a loop that calls a function to create the headers and tables, and then adds images after. I think the problem is ranges. I use a starting range of object start = 0;
How can I get these to add one at a time to to a new line in the document?
foreach (var category in observedColumns)
{
CreateHeadersAndTables();
createPictures();
}
Adding Headers:
object start = 0;
Word.Range rng = doc.Range(ref start , Missing.Value);
Word.Paragraph heading;
heading = doc.Content.Paragraphs.Add(Missing.Value);
heading.Range.Text = category;
heading.Range.InsertParagraphAfter();
Adding Tables:
Word.Table table;
table = doc.Content.Tables.Add(rng, 1, 5);
Adding Pictures:
doc.Application.Selection.InlineShapes.AddPicture(#path);
A simple approach will be using paragraphs to handle the Range objects and simply insert a new paragraph one by one.
Looking at the API documentation reveals that Paragraphs implements an Add method which:
Returns a Paragraph object that represents a new, blank paragraph
added to a document. (...) If Range isn't specified, the new paragraph is added after the selection or range or at the end of the document.
Source: http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.paragraphs.add(v=office.14).aspx
In that way, it gets straight forward to append new content to the document.
For completeness I have included a sample that shows how a solution might work. The sample loops through a for loop, and for each iteration it inserts:
A new line of text
A table
A picture
The sample has is implemented as a C# console application using:
.NET 4.5
Microsoft Office Object Library version 15.0, and
Microsoft Word Object Library version 15.0
... that is, the MS Word Interop API that ships with MS Office 2013.
using System;
using System.IO;
using Microsoft.Office.Interop.Word;
using Application = Microsoft.Office.Interop.Word.Application;
namespace StackOverflowWordInterop
{
class Program
{
static void Main()
{
// Open word and a docx file
var wordApplication = new Application() { Visible = true };
var document = wordApplication.Documents.Open(#"C:\Users\myUserName\Documents\document.docx", Visible: true);
// "10" is chosen by random - select a value that fits your purpose
for (var i = 0; i < 10; i++)
{
// Insert text
var pText = document.Paragraphs.Add();
pText.Format.SpaceAfter = 10f;
pText.Range.Text = String.Format("This is line #{0}", i);
pText.Range.InsertParagraphAfter();
// Insert table
var pTable = document.Paragraphs.Add();
pTable.Format.SpaceAfter = 10f;
var table = document.Tables.Add(pTable.Range, 2, 3, WdDefaultTableBehavior.wdWord9TableBehavior);
for (var r = 1; r <= table.Rows.Count; r++)
for (var c = 1; c <= table.Columns.Count; c++)
table.Cell(r, c).Range.Text = String.Format("This is cell {0} in table #{1}", String.Format("({0},{1})", r,c) , i);
// Insert picture
var pPicture = document.Paragraphs.Add();
pPicture.Format.SpaceAfter = 10f;
document.InlineShapes.AddPicture(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "img_1.png"), Range: pPicture.Range);
}
// Some console ascii-UI
Console.WriteLine("Press any key to save document and close word..");
Console.ReadLine();
// Save settings
document.Save();
// Close word
wordApplication.Quit();
}
}
}

Categories

Resources