MigraDoc: Forcing embed tables to break at logical points - c#

I have a large form I'm transforming into a PDF. The first 1/6th of it looks like this:
http://i.imgur.com/y4pO8Th.png
The number of entered fields however, varies from 1 to 20 per section, and I need to be able to make this document break pages intelligently. My plan was to originally draw the tables piece by piece and just manage the Y-coordinate by grabbing the number of rows in all previous tables. This worked, but falls apart when I get to a page break, and I start needing some semi-complicated logic to make it work, and it's the kind of logic that gets messier and messier with each additional table added.
My second plan was to reproduce the table structure of the HTML document in the PDF, which I manage to do successfully...
private void DrawPDF()
{
Document tDoc = new Document();
MigraDoc.DocumentObjectModel.Style style = tDoc.Styles["Normal"];
style.Font.Name = tPdfFont;
style.Font.Size = 10;
Section tSec = tDoc.AddSection();
MigraDoc.DocumentObjectModel.Tables.Table masterTable = new MigraDoc.DocumentObjectModel.Tables.Table();
masterTable = tSec.AddTable();
masterTable.Borders.Visible = false;
Column leftColumn = masterTable.AddColumn("365pt");
Column spacer = masterTable.AddColumn("10pt");
Column rightColumn = masterTable.AddColumn("365pt");
Row tFS = masterTable.AddRow();
Cell tCell = tFS.Cells[0];
//
// Farm Assets Column
//
{
MigraDoc.DocumentObjectModel.Tables.Table tAssetsTable = new MigraDoc.DocumentObjectModel.Tables.Table();
tAssetsTable.Borders.Visible = false;
Column tColumn = tAssetsTable.AddColumn("365pt");
tCell.Elements.Add(tAssetsTable);
//
// Current Farm Assets
//
for (int i = 0; i < 10; i++) // Drawn 10 times to force it to draw over the 1st page.
{
Section thisSection = tDoc.AddSection();
Row tAssetsRow = tAssetsTable.AddRow();
Cell tAssetsCell = tAssetsRow.Cells[0];
MigraDoc.DocumentObjectModel.Tables.Table table = new MigraDoc.DocumentObjectModel.Tables.Table();
table = thisSection.AddTable();
table.Borders.Width = 0.2;
table.Rows.LeftIndent = 0;
Column columnData = table.AddColumn("295pt");
columnData.Borders.Left.Visible = false;
Column columnValue = table.AddColumn("70pt");
Row rowA = table.AddRow();
rowA.Shading.Color = Color.FromRgbColor((byte)255, Color.Parse("0xa2a2d2"));
rowA.Cells[0].AddParagraph("CURRENT FARM ASSETS");
rowA.Cells[1].AddParagraph("$ Value");
rowA.Cells[1].Format.Alignment = ParagraphAlignment.Right;
Row row1 = table.AddRow();
row1.Borders.Bottom.Visible = false;
row1.Cells[0].AddParagraph("Cash: Savings: ($" + MP.FormFinancialStatement.CurrentStaticAssets.Savings + ") Checking: ($" + MP.FormFinancialStatement.CurrentStaticAssets.Checking + ")");
row1.Cells[1].AddParagraph(MP.FormFinancialStatement.CurrentStaticAssets.CashTotal);
row1.Cells[1].Format.Alignment = ParagraphAlignment.Right;
Row row2 = table.AddRow();
row2.Borders.Bottom.Visible = false;
row2.Cells[0].AddParagraph("Invest: Time Cret $" + MP.FormFinancialStatement.CurrentStaticAssets.TimeCret + " Other: $" + MP.FormFinancialStatement.CurrentStaticAssets.OtherInvestments + "");
row2.Cells[1].AddParagraph(MP.FormFinancialStatement.CurrentStaticAssets.InvestTotal);
row2.Cells[1].Format.Alignment = ParagraphAlignment.Right;
Row row3 = table.AddRow();
row3.Borders.Bottom.Visible = false;
row3.Cells[0].AddParagraph("Replacement Account");
row3.Cells[1].AddParagraph(MP.FormFinancialStatement.CurrentStaticAssets.ReplacementAccount);
row3.Cells[1].Format.Alignment = ParagraphAlignment.Right;
Row row4 = table.AddRow();
row4.Cells[0].AddParagraph("Accouts and Notes Recievable");
row4.Cells[1].AddParagraph(MP.FormFinancialStatement.CurrentStaticAssets.AccountsNotesReceivable);
row4.Cells[1].Format.Alignment = ParagraphAlignment.Right;
MigraDoc.DocumentObjectModel.Tables.Table clone = (MigraDoc.DocumentObjectModel.Tables.Table)table.Clone();
tAssetsCell.Elements.Add(clone);
}
}
MigraDoc.Rendering.DocumentRenderer docRenderer = new DocumentRenderer(tDoc);
docRenderer.PrepareDocument();
docRenderer.RenderObject(gfx, 30, 85, "740pt", masterTable);
}
But alas, this does not actually break pages correctly. I tried sectioning off each individual table, hoping that'd do page break magic, but it does not.
How can I structure this to allow for good page breaks?

You can use the KeepWith property of the table rows to keep blocks together on one page. Only use this for chunks that will surely fit on one page.
See also:
https://stackoverflow.com/a/6831048/1015447
https://stackoverflow.com/a/1327228/1015447

Related

Migradoc Image Gallery

Background
I am currently using Kendo-UI as my front-end library and I was using the saveAs method to generate reports based on the DOM that was being rendered, but ran into some serious performance issues (especially in IE). So I decided that I would handle the PDF generation on the server-side using Migradoc.
What I'm Trying To Do
Right now I have a listview (see demo) that essentially represents an image gallery. What I'm having a difficult time with is trying to figure out how to have the image/text go from left-to-right.
What I've Done
Here is my current code:
// Title
var title = section.AddParagraph("Photos");
title.Format.SpaceAfter = Unit.FromPoint(9);
title.Format.SpaceBefore = Unit.FromPoint(9);
title.Format.Font.Bold = true;
title.Format.Font.Color = new Color(33, 37, 41);
title.Format.Font.Name = "Segoe UI";
title.Format.Font.Size = Unit.FromPoint(11);
title.Format.Borders.Bottom = new Border()
{
Color = new Color(222, 226, 230),
Style = BorderStyle.Single
};
// listview
foreach (var photo in _photos)
{
var photoDocument = _documentService.Get(photo.DocumentId);
var textFrame = section.AddTextFrame();
textFrame.AddImage("base64:" + Convert.ToBase64String(photoDocument.ThumbnailBinaryData));
paragraph = textFrame.AddParagraph(photo.Filename);
paragraph.Format.Alignment = ParagraphAlignment.Center;
if (photo.CreatedOn != null)
{
var dateValue = (DateTimeOffset) photo.CreatedOn;
paragraph.AddLineBreak();
paragraph.AddText(dateValue.ToString("yyyy-MM-dd"));
}
}
The issue with this approach is that Migradoc is placing the photos vertically and the text basically on top of each other (see image below).
Question
How can I make it so that the images go from left-to-right until there is no longer any room in which case it starts to wrap on the next line?
Update
I'm able to get the photos to line up left-to-right, but without any text using:
var paragraph = section.AddParagraph();
foreach (var photo in _photos)
{
var photoDocument = _documentService.Get(photo.DocumentId);
paragraph.AddImage("base64:" + Convert.ToBase64String(photoDocument.ThumbnailBinaryData));
}
However, I still haven't figured out how to set the text below the image and have the images appear on the same line.
Update #2
After spending a long time thinking about this, ultimately what I did was create a table, calculate the maximum number of columns that can fit on the page, and then dynamically add columns as I go. I'm not sure if this is the right solution or not, but it works for me:
if (_photos.Any())
{
var columnWidth = Unit.FromInch(1.75);
var maximumColumns = (int)Math.Floor((11.5 - pageSetup.LeftMargin.Inch - pageSetup.RightMargin.Inch) / columnWidth.Inch); // 11.5 (page width) - 0.25 (left margin) - 0.25 (right margin) divided by columnWidth
var table = section.AddTable();
table.Style = "Table";
if (maximumColumns > photos.Count())
{
columnWidth = Unit.FromInch(Math.Floor((11.5 - pageSetup.LeftMargin.Inch - pageSetup.RightMargin.Inch) / photos.Count()));
maximumColumns = photos.Count();
}
Enumerable.Range(1, maximumColumns).ForEach(i => table.AddColumn(columnWidth));
var row = table.AddRow();
var columnIndex = 0;
foreach (var photo in _photos)
{
if (columnIndex == maximumColumns - 1)
{
row = table.AddRow();
columnIndex = 0;
}
else
{
columnIndex++;
}
var photoDocument = _documentService.Get(photo.DocumentId);
var photoParagraph = row.Cells[columnIndex].AddParagraph();
photoParagraph.AddImage("base64:" + Convert.ToBase64String(photoDocument.ThumbnailBinaryData));
photoParagraph.AddLineBreak();
photoParagraph.AddText(photo.Filename);
photoParagraph.Format.Alignment = ParagraphAlignment.Center;
if (photo.CreatedOn != null)
{
var dateValue = (DateTimeOffset)photo.CreatedOn;
photoParagraph.AddLineBreak();
photoParagraph.AddText(dateValue.ToString("yyyy-MM-dd"));
}
}
}

Minimum height of Row [Migradoc]

I want to set the minimum height of row. however it seems there is limit
i am using below code [http://forum.pdfsharp.de/viewtopic.php?f=2&t=2812 ]
var spacerRow = t1.AddRow();
spacerRow.Height = "0.1mm";
var para2 = new Paragraph();
para2.Format.LineSpacingRule = LineSpacingRule.Exactly;
para2.Format.LineSpacing = "0.1mm";
para2.Format.SpaceBefore = 0;
para2.Format.SpaceAfter = 0;
spacerRow.Cells[0].Add(para2);
but the height is not reducing any further.
the spacer row is between the borderd rows as show in attached picture.
If you want to do it for all rows:
Table table = new Table();
table.Format.Alignment = ParagraphAlignment.Center;
table.Rows.HeightRule = RowHeightRule.Exactly;
table.Rows.Height = 5;
For a single row:
row = table.AddRow();
row.HeightRule = RowHeightRule.Exactly;
row.Height = 5;

C# Mulit-Series Bar chart displaying incorrect information for 2/10 entries

I am attempting to dynamically create a bar chart in C# based on the following datatable (which will change, hence the need for dynamic creation).
Here's the datatables that I'm reading from currently:
(people of the future: these images contained work stuff and I don't want to get fired, so I've removed them =O )
The top table is where I add the series (s24, s26, s27) data. The second table is where I slim it down and add the "Total" series.
So here are the columns that I'm trying to chart from:
Config - this is where I want to get my individual series: ie: s24, s26, s27, etc.
HRC_Count - this is the actual value I'm trying to graph
HRC_Description and HRC - these are labels for the Y axis
Total_HRC_Count - this is for my "Total" series (black bar on chart)
//get distinct Configs (to be added as Series in chart)
//this creates a dataview to parse for the series in the chart
DataView view = new DataView(dt);
DataTable distinctValues = new DataTable();
distinctValues = view.ToTable(true, "Config");
DataView dv = new DataView(distinctValues);
dv.Sort = "Config ASC";
DataTable dt_temp = new DataTable();
dt_temp = dv.ToTable();
distinctValues = dt_temp;
GraphDataNew(distinctValues);
then I call GraphDataNew and parse the dataview for the series:
private void GraphDataNew(DataTable dt_distinct)
{
chart_new.Series.Clear();
foreach (DataRow row in dt_distinct.Rows)
{
string s = row["Config"].ToString();
chart_new.Series.Add(s);
chart_new.Series[s].ChartType = SeriesChartType.Bar;
chart_new.Series[s].IsValueShownAsLabel = true;
chart_new.Series[s].Sort(PointSortOrder.Ascending, "X");
chart_new.Series[s].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.String;
chart_new.Series[s].YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Double;
}
So now I have my Series (Configs) set up, now I add actual data:
DataView dv = new DataView(dt);
dv.Sort = "HRC_Description ASC, Config ASC"; // sort by description first, then config second
string HRC_Description = "";
string Config = "";
double HRC_Count = 0;
DataTable dt_temp = new DataTable();
dt_temp = dv.ToTable();
// add actual data
//loop through dt_temp and add datapoints and custom labels to chart
for (int x = 0; x < dt_temp.Rows.Count; x++)
{
HRC_Description = (dt_temp.Rows[x]["HRC_Description"].ToString().Trim());
Config = dt_temp.Rows[x]["Config"].ToString().Trim();
HRC_Count = Double.Parse(dt_temp.Rows[x]["HRC_Count"].ToString().Trim());
chart_new.Series[Config].Points.AddXY(HRC_Description, HRC_Count);
int y = chart_new.Series[Config].Points.Count;
chart_new.Series[Config].Points[y - 1].AxisLabel = HRC_Description;
}
Then I add the "Total" series bar:
if (1==1) // fake condition for this question
{
chart_new.Series.Add("Total");
chart_new.Series["Total"].ChartType = SeriesChartType.Bar;
chart_new.Series["Total"].Color = Color.Black;
chart_new.Series["Total"].IsValueShownAsLabel = true;
chart_new.Series["Total"].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.String;
chart_new.Series["Total"].YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Double;
DataView view1 = new DataView(dt_temp);
DataTable distinctValues1 = view1.ToTable(true, "HRC_Description", "Total_HRC_Count");
dt_temp = distinctValues1;
double Total_HRC_Count = 0;
for (int x = 0; x < dt_temp.Rows.Count; x++)
{
HRC_Description = (dt_temp.Rows[x]["HRC_Description"].ToString().Trim());
Total_HRC_Count = Double.Parse(dt_temp.Rows[x]["Total_HRC_Count"].ToString().Trim());
chart_new.Series["Total"].Points.AddXY(HRC_Description, Total_HRC_Count);
}
}
This is what the chart looks like:
[![Example Chart][2]][2]
However, as you can see in the attached chart, my data is all off. There are 10 of the HRC_Description, but only 9 show up, and one is repeated. On top of that, it seems that whenever I have more than 2 series, the data gets mixed up. Does anyone have any tips on what I can do to fix this? It's driving me crazy! Thanks!!
I can't fully analyze your data but I can give you a hint, which should help in solving your problem:
I see that you add the datapoints with strings as the x-values.
This is ok (sort of; well, no, not really) but the x-values of the datapoints that actually are addded really are not strings but double and they all contain 0.
The strings only go into the labels, not the x-values! (So the values are lost)
But to be grouped together the points in your various series need some criterion and as the x-values won't work (even though they do look as if they did) the position of the points is used instead. Which dervies from the positions, i.e. from the order and number of points you add.
So only those bars will be grouped together which are added to the same position and since there are no real positions the order in which they are added is all that counts.
So you need to make sure to always use the same order and to never leave a spot empty when you add your points. If a values is missing in a series you need to add an empty dummy points (set its DataPoint.IsEmpty=true)
Easy to get wrong..!
You can check if this is the cause by checking the number of datapoints in each series. If they are not all the same this is the problem..
Here is an example: Note how the second, yellow and the 3rd, red series both are missing values but how only the yellow one is off; note the last point especially!
private void button17_Click(object sender, EventArgs e)
{
chart3.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
chart3.Series.Clear();
Series s1 = chart3.Series.Add("S1");
Series s2 = chart3.Series.Add("S2");
Series s3 = chart3.Series.Add("S3");
Random R = new Random(42);
s1.ChartType = SeriesChartType.Bar;
s2.ChartType = SeriesChartType.Bar;
s3.ChartType = SeriesChartType.Bar;
for (int i = 0; i < 10; i++)
{
// this series is complete..:
s1.Points.AddXY(i + "", R.Next(100));
int r = R.Next(5);
// this series misses some points..:
if (r==0) s2.Points.AddXY(i + "", R.Next(100));
// this series inserts empty points and so misses no points.:
if (r == 0) s3.Points.AddXY(i + "", R.Next(100));
else { int p = s3.Points.AddXY(i + "", R.Next(100)); s3.Points[p].IsEmpty = true; }
}
// now add a value at "10" for all series..;
s1.Points.AddXY("10", 100);
s2.Points.AddXY("10", 100);
s3.Points.AddXY("10", 100);
}

Migradoc Header with table

I can make a header in Migradoc like this:
//Create Header
Paragraph paragraph = section.Headers.Primary.AddParagraph();
paragraph.AddText("Roto");
paragraph.Format.Font.Size = 9;
paragraph.Format.Alignment = ParagraphAlignment.Center;
And I can make make a simple table like this:
// Create the HEADER table for the top of every page
this.table = section.AddTable();
this.table.Style = "Table";
this.table.Borders.Color = TableBorder;
this.table.Borders.Width = 0.25;
this.table.Borders.Left.Width = 0.5;
this.table.Borders.Right.Width = 0.5;
this.table.Rows.LeftIndent = 0;
Column column = this.table.AddColumn("8cm");
column.Format.Alignment = ParagraphAlignment.Center;
column = this.table.AddColumn("8cm");
column.Format.Alignment = ParagraphAlignment.Center;
// Create the header of the table
Row row = table.AddRow();
//row = table.AddRow();
row.HeadingFormat = true;
row.Format.Alignment = ParagraphAlignment.Center;
row.Format.Font.Bold = true;
row.Shading.Color = TableBlue;
row.Cells[0].AddParagraph("Rotary");
row.Cells[0].MergeRight = 1;
row = table.AddRow();
row.HeadingFormat = true;
row.Format.Alignment = ParagraphAlignment.Center;
row.Format.Font.Bold = true;
row.Shading.Color = TableBlue;
row.Cells[0].AddParagraph("Part No.:");
row.Cells[0].Format.Alignment = ParagraphAlignment.Left;
row.Cells[1].AddParagraph("Tested by:");
row.Cells[1].Format.Alignment = ParagraphAlignment.Left;
row = table.AddRow();
row.Cells[0].MergeRight = 1;
How do I get the table into the header so it appears at the top of every page?
EDIT:
So to make it work I changed:
this.table = section.AddTable();
to:
this.table = section.Headers.Primary.AddTable();
If you want to have the same header on every page:
Use section.Headers.Primary.AddTable() instead of section.Headers.Primary.AddParagraph().
By setting row.HeadingFormat = true; for the first n rows of your table, you mark this rows as header rows. When the table grows and breaks over several pages, the header rows will be repeated on every page (but in the "normal" page body, not the header area). This is the typical usage of heading rows. If you don't add other rows to your header table, HeadingFormat = true will not have any effect.

Changing the margins of a Word Document

I have created a web part with a button that once clicked generates a word document containing the list item values of particular list. I want to be able to change the margins for the document (top, bottom margins), but I am unsure as to how to proceed. Can anyone shed some light on how to achieve this?
So far the code I have is a as follows:
void GenerateBadges_Click(object sender, EventArgs e)
{
string Title = null;
string jobTitle = null;
WordprocessingDocument document = WordprocessingDocument.Create(#"C:\sample-
badges.docx", WordprocessingDocumentType.Document);
MainDocumentPart mainDocumenPart = document.AddMainDocumentPart();
mainDocumenPart.Document = new Document();
Body documentBody = new Body();
mainDocumenPart.Document.Append(documentBody);
SPWeb web = SPContext.Current.Web;
SPList list = web.Lists["SampleList"];
SPListItemCollection collListItems = list.Items;
//getting the internal name for the Title and JobTitle fields of the list
string jobTitleField = collListItems.Fields["JobTitle"].InternalName;
string titleField = collListItems.Fields["Title"].InternalName;
//adding a table to the document
//creating a properties object to add border to the table (wNo border will be required)
Table table = new Table();
TableProperties tblProps = new TableProperties();
TableBorders tblBorders = new TableBorders();
tblBorders.TopBorder = new TopBorder();
tblBorders.TopBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.BottomBorder = new BottomBorder();
tblBorders.BottomBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.RightBorder = new RightBorder();
tblBorders.RightBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.LeftBorder = new LeftBorder();
tblBorders.LeftBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.InsideHorizontalBorder = new InsideHorizontalBorder();
tblBorders.InsideHorizontalBorder.Val = BorderValues.Single;
tblBorders.InsideVerticalBorder = new InsideVerticalBorder();
tblBorders.InsideVerticalBorder.Val = BorderValues.Single;
tblProps.Append(tblBorders);
table.Append(tblProps);
int x = collListItems.Count;
//creatin the table rows/cells
for (int i = 0; (i * 2) < x; i++)
{
TableRow row = new TableRow();
// get the indexes for left and right cells as pairs (i.e. 0 + 1, 2 + 3, 4 + 5 etc)
int leftIndexer = i * 2;
int rightIndexer = (i * 2) + 1;
if (leftIndexer == x)
{
break;
}
//getting the values from the list for the left table cell
Title = collListItems[leftIndexer][titleField].ToString();
jobTitle = collListItems[leftIndexer][jobTitleField].ToString();
// attach content to row as cell
row.Append(new TableCell(new Paragraph(new Run(new Text(Title)))));
// get right cell contents, if there is a value for this index
if (rightIndexer < x)
{
//getting the values from the list for the right cell
Title = collListItems[rightIndexer][titleField].ToString();
jobTitle = collListItems[rightIndexer][jobTitleField].ToString();
// attach to table row as right cell
row.Append(new TableCell(new Paragraph(new Run(new Text(Title)))));
}
// attach row to table
table.Append(row);
}
//add the table to the document - table needs to be wired into the for each loop above
documentBody.Append(table);
//Saving/Disposing of the created word Document
document.MainDocumentPart.Document.Save();
document.Dispose();
The key part to be able to change the page margins is to firs t create a "PageMagrin" object and then a "SectionProperties" object. Lastly we need to append the page margin object in to the section properties object. Lastly append the section properties object to a body object.
Additionally it is very useful to create a word document, then save it as .xml. Then open it in notepad, notepad++ or even visual studio 2010. This displays all of th elements that make up the word document with this you are then able to determine which parts or elements of the document you need to modify.
Below is the actual code used:
//setting the page margins go here
PageMargin pageMargins = new PageMargin();
pageMargins.Left = 600;
pageMargins.Right = 600;
pageMargins.Bottom = 500;
pageMargins.Top = 500;
//pageMargins.Header = 1500; //not needed for now
//pageMargins.Footer = 1500; //not needed for now
//Important needed to access properties (sections) to set values for all elements.
SectionProperties sectionProps = new SectionProperties();
sectionProps.Append(pageMargins);
documentBody.Append(sectionProps);
Hope this helps others, I had a tough time figuring this out
It is a bit hard to tell what exactly you want to do - the following links contain details and source code for page margins, headers, footers etc.:
http://msdn.microsoft.com/en-us/library/ee355228%28office.12%29.aspx
http://sharepointweblog.blogspot.com/2009/08/wordprocessingml-insert-page-number-in.html
IF the above is not what you asked please provide more details on what you achieve...

Categories