Click here to Skip to main content
15,886,707 members
Articles / Programming Languages / C#

MDI Case Study Purchasing - Part XI - MigraDoc

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
29 Dec 2015CPOL8 min read 16.8K   391   5   5

Introduction

Now that we have dealt with the major issues, we will start expanding our application with some more advanced features. In Part XI we will use the tools provided in the MigraDoc library to create a universal output document that can be rendered to both a printer or a PDF file. MigraDoc is a powerful, and better yet, FREE library for creating documents. The library downloads and full documentations can be found at

Official PDFSharp & MigraDoc Website

MigraDoc - Creating A Document

MigraDoc and PDFSharp are both fairly extensive libraries with many features. I encourage you to explore it's features, as others not covered here could be of benefit to you. For our use, we will need two of the MigraDoc libraries, MigraDoc.Rendering and MigraDoc.DocumentObjectModel, as well as the PDFSharp library. You can either get them from the source package with this article, or you can go to the site above and download the latest. You will have to build them to get the library dll files needed for referencing in your project. I would suggest just using the libraries I've included with the source.

The heart of our document output will be the MigraDoc.DocumentObjectModel.Document. In our PurchaseOrderForm class, let's create a method that will create and return this document object

C#
public Document GetDocument()
{
   return new Document();
}

This method will be the singular method for retrieving an output document. Whether we are printing, or outputting a PDF file, we will only be using this method to generate the Document object. Once we get the document object, we can then render it however we like. In this case we will use both a PDF renderer and a printer renderer. So let's create our document. The GetDocument() method is going to get pretty lengthy, so let's handle it is sections, each in a #region tag. I'll place some commenting in the code sections to give an idea of what things are for. First let's create the Document Setup #region. If you're unfamiliar with #region, it's a way to compartmentalize your code in Visual Studio, as you can collapse a #region of code that is enclosed between a #region and #endregion tag, and #region tags can be nested. They are a good way to organize your code. We are going to be placing a number of things onto our document. We are aiming to have it look as near to our UI document representation as we can. That means we are going to put the logo, the three addresses, a box with the PO number, and the items. We will be forming tables to hold our data, and an Image to hold our logo. You'll see how each of the items below are used as we go. We will be printing 15 items per page. Here is our Document Setup #region

C#
#region Document Setup
int itemsPerPage = 15; // number of items to output per page
int pageNumber = 1; // current page number, starts at 1
int totalPages = 0; // total number of pages to be added to document
int countThisPage = 0; // number of items added to current page so far

// here we will calculate the number of page our document needs based on the itemsPerPage variable
foreach (Item it in _purchaseOrder.Items)
{
    if (countThisPage >= itemsPerPage) countThisPage = 0;
    if (countThisPage == 0) totalPages++;
    countThisPage++;
}

// pageString will be output to the document as a string in the format of 
// "Page x of y"
String pageString;

// logFilename is the path for the logo image file we want added to the page
String logoFilename = "truckLogo.png";

// here we initialize our document object, as well as the building components we will be using
Document document = new Document();

// image is the Image object that will hold our logo, note that it needs to be declared with its
// full FQN so that it can be distinguished from System.Drawing.Image
MigraDoc.DocumentObjectModel.Shapes.Image image;

// txtFrame and paragraph are objects we will reuse each time we place text onto the document
// TextFrame will hold the Paragraph object, and Paragraph will hold the actual String
TextFrame txtFrame;
Paragraph paragraph;

// top and left are the starting top and left positions for where we will begins placing our 
// components, and as we place components, top and left will be incremented 
TopPosition top;
LeftPosition left;

// table will be our grid for adding our items to the document
Table table;
Row row;
Column column;
Cell cell;

// these are our hard margins, note that MigraDoc uses an object called Unit to define positions
// each time we place an object we will describe where with Unit instances
Unit leftMargin = new Unit(25.0);
Unit rightMargin = new Unit(70.0);
Unit topMargin = new Unit(12.0);
Unit bottomMargin = new Unit(6.0);

// here we define meta data for our document
document.Info.Title = "Purchase Order " + _purchaseOrder.PurchaseOrderNumber;
document.Info.Comment = "Ordered from - " + _purchaseOrder.Vendor.Address.CompanyName;
document.Info.Author = "MDI Purchasing, Inc";

// here we define the format and layout specifics of our document
document.DefaultPageSetup.PageFormat = PageFormat.Letter;
document.DefaultPageSetup.Orientation = MigraDoc.DocumentObjectModel.Orientation.Landscape;
document.DefaultPageSetup.LeftMargin = leftMargin;
document.DefaultPageSetup.RightMargin = rightMargin;
document.DefaultPageSetup.TopMargin = topMargin;
document.DefaultPageSetup.BottomMargin = bottomMargin;

Style docstyle = document.Styles["Normal"];
docstyle.Font.Name = "Lucida Sans";
docstyle.Font.Size = 9.0;
#endregion

The process for putting text onto our document will flow as such: TextFrame -> Paragraph or
TextFrame -> Table -> Cell -> Paragraph. The Paragraph will hold the actual text String. So after our document setup, we need to loop through our items, creating a new page for every 15 items, which is defined by itemsPerPage. Since we've already calculated how many pages we need, we can simply use this to create our loop, we'll use a while loop

C#
while (pageNumber <= totalPages)
{
    #region Create Page

    // create page

    #endregion
​    pageNumber++;
}

We will next our #region tags to make this method more organized, so that it makes more sense visually as you examine the code. So we will have several #region tags nested within the Create Page #region. First we need to add a page to our document. The page is a MigraDoc.DocumentObjectModel.Section object. We will pass the document format specifics to the section. One nice thing about the MigraDoc library, as you add objects, the Add() methods return the object that was added, so lets add a Section to our document and define it's format and layout properties

C#
#region Initialize New Section
Section docsection = document.AddSection();
docsection.PageSetup.PageFormat = document.DefaultPageSetup.PageFormat;
docsection.PageSetup.Orientation = document.DefaultPageSetup.Orientation;
docsection.PageSetup.LeftMargin = document.DefaultPageSetup.LeftMargin;
docsection.PageSetup.RightMargin = document.DefaultPageSetup.RightMargin;
docsection.PageSetup.TopMargin = document.DefaultPageSetup.TopMargin;
docsection.PageSetup.BottomMargin = document.DefaultPageSetup.BottomMargin;
#endregion

Now we have a page, so lets start adding our document components to it. First we will add our logo

C#
#region Add Logo
image = docsection.AddImage(logoFilename);
image.Left = ShapePosition.Left;
image.Top = ShapePosition.Top;
image.RelativeHorizontal = RelativeHorizontal.Margin;
image.RelativeVertical = RelativeVertical.Margin;
image.ScaleHeight = 0.75;
image.ScaleWidth = 075;
#endregion

Let's stop here for a moment, because if you're like me, you like to see results as you work. Now we have an actual document with the logo on a page, so lets skip over and create the output methods to actual render our PDF and you can see it actually start working. Then we'll come back and finish filling in the rest of the document. So let's skip over to MDIForm, and let's add a button to our tool strip, name it createPdfToolStripButton, and drag it over beside the Print Preview tool stip button. Don't worry about it's icon right now, but set it's Enabled property to False

Screenshot 1

Now double click the button to create its Click event handler. Here we will get the document from our active form, and render it as a PDF, and save it to our desktop, and then open it. Of course, if you haven't already, take a moment to install Adobe Reader. Here is what our event handler needs to look like

C#
private void createPdfToolStripButton_Click(object sender, EventArgs e)
{
    PurchaseOrderForm activeChildForm = this.ActiveMdiChild as PurchaseOrderForm;
    MigraDoc.DocumentObjectModel.Document document = activeChildForm.GetDocument();
    MigraDoc.Rendering.PdfDocumentRenderer pdfRender = new MigraDoc.Rendering.PdfDocumentRenderer();
    pdfRender.Document = document;
    pdfRender.RenderDocument();
    if(pdfRender.PageCount > 0)
    {
        String fileName = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\newDoc.pdf";
        pdfRender.Save(fileName);
        System.Diagnostics.Process.Start(fileName);
    }
}

Lastly, we need to go to the OnMdiChildActivate method in MDIForm, and enable our button when appropriate

C#
protected override void OnMdiChildActivate(object sender, EventArgs e)
{
   ....
   createPdfToolStripButton.Enabled = false;
   ....
   if (activeMdiChild != null)
   {
      ....
      createPdfToolStripButton.Enabled = true;
   }
   base.OnMdiChildActivate(e);
}

Now you can test this out, create a new form, and click the button to create the PDF and you'll get a single page document with the logo in the top left corner. Now as we add components to our page, you can test them out. At the end of the article we will cover rendering the document to a printer, so if you're in a hurry for that feel free to skip down to the bottom. Now let's move back to our GetDocument() method in PurchaseOrderForm and continue filling out our document. Next we'll add the box for our PurchaseOrderNumber value. First, be sure that your project is in Debug mode, rather than release

Screenshot 2

In this next region of code you will see an #if DEBUG block. If you aren't familiar with with this, it allows is to take an action that only compiles into our executable if we are in Debug mode. Since we haven't provided a method for the user to set the PurchaseOrderNumber property yet, we'll use this to set a test value to it that only happens in Debug mode. Here is the #region for adding the PurchaseOrderNumber frame to the document

C#
#region Add PO Box
// here we add a TextFrame to the document and define its layout settings
txtFrame = docsection.AddTextFrame();
txtFrame.Top = ShapePosition.Top;
txtFrame.RelativeVertical = RelativeVertical.Margin;
txtFrame.Left = ShapePosition.Left;
txtFrame.Left = txtFrame.Left.Position + 298.5;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.FillFormat.Color = Colors.LightGray;
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.DashStyle = DashStyle.Solid;
txtFrame.LineFormat.Width = new Unit(0.5);
txtFrame.Width = new Unit(150.0);
txtFrame.Height = new Unit(15.0);
txtFrame.MarginTop = new Unit(2.5);

// here we add a Paragraph to the TextFrame and define its layout settings
// this first TextFrame will be the label portion of out PurchaseOrderForm box
paragraph = txtFrame.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.Font.Bold = true;
paragraph.AddText("Purchase Order Number");

// here we set our top variable to the next position we will want the next TextFrame
// to be placed at, in this case 15 below the last TextFrame
top = txtFrame.Top.Position + 15.0;

// here the next TextFrame will be the box holding the actual value of PurchaseOrderNumber
// note that once we finish with a portion of the form design, we no longer need it, therefore
// we can reuse the variable to hold the next component
txtFrame = docsection.AddTextFrame();
txtFrame.Top = top;
txtFrame.RelativeVertical = RelativeVertical.Line;
txtFrame.Left = ShapePosition.Left;
txtFrame.Left = txtFrame.Left.Position + 298.5;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.FillFormat.Color = Colors.White;
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.DashStyle = DashStyle.Solid;
txtFrame.LineFormat.Width = new Unit(0.5);
txtFrame.Width = new Unit(150.0);
txtFrame.Height = new Unit(20.0);
txtFrame.MarginTop = new Unit(3.0);
paragraph = txtFrame.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.Font.Bold = true;
paragraph.Format.Font.Size = 11.0;

// here is the #if DEBUG block, anything inside this block only hits the compiler when building
// in Debug mode
#if DEBUG
_purchaseOrder.PurchaseOrderNumber = "0000123456";
#endif


paragraph.AddText(_purchaseOrder.PurchaseOrderNumber);
#endregion

 

Now you can test run your application again, try running it in Debug mode AND in Release mode, and you'll see that the test value only shows up in Debug mode. Using #if DEBUG can be helpful in testing functionality as you design it, just don't forget to switch to Release mode to build your live product.

Next, let's use the same process as above to add our Billing address to the document, except this time we will also be using a Table to more efficiently layout the address

C#
#region Add Billing Address Box
txtFrame = docsection.AddTextFrame();
txtFrame.Top = ShapePosition.Top;
txtFrame.RelativeVertical = RelativeVertical.Margin;
txtFrame.Left = ShapePosition.Left;
txtFrame.Left = txtFrame.Left.Position + 457;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.FillFormat.Color = Colors.LightGray;
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.DashStyle = DashStyle.Solid;
txtFrame.LineFormat.Width = new Unit(0.5);
txtFrame.Width = new Unit(290.0);
txtFrame.Height = new Unit(15.0);
txtFrame.MarginTop = new Unit(2.5);
paragraph = txtFrame.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.Font.Bold = true;
paragraph.AddText("Billing Address");
top = txtFrame.Top.Position + 15.0;
txtFrame = docsection.AddTextFrame();
txtFrame.Top = top;
txtFrame.RelativeVertical = RelativeVertical.Margin;
txtFrame.Left = ShapePosition.Left;
txtFrame.Left = txtFrame.Left.Position + 457;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.FillFormat.Color = Colors.White;
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.DashStyle = DashStyle.Solid;
txtFrame.LineFormat.Width = new Unit(0.5);
txtFrame.Width = new Unit(290.0);
txtFrame.Height = new Unit(70.0);

// here instead of directly adding a Paragraph, we add a Table
table = txtFrame.AddTable();
table.Format.Alignment = ParagraphAlignment.Left;
table.Format.LeftIndent = 3.0;

// here we only need 1 column in this table, we add it, and set its alignment
column = table.AddColumn(new Unit(70.0));
column.Format.Alignment = ParagraphAlignment.Left;
column = table.AddColumn(new Unit(220.0));
column.Format.Alignment = ParagraphAlignment.Left;

// next we add a row to the table
row = table.AddRow();

// here we define a variable for the height of our table rows
Unit billaddrowheight = new Unit(14.0);
row.Height = new Unit(billaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;

// here we add our Paragraph to the cell, sine we don't need to adjust any settings for this
// Paragraph we can just add it with the text and move on. MergeRight tells the table to take
// the cell in question and combine it with the number of specified cells to the right
// because we are adding the "Phone" and "Fax" label we will need 2 columns for those last 2 rows
// so for all other rows we can Merge them to create one cell
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.BillingAddress.CompanyName);

// now we add a new row for each line of the rest of the lines of our address, and add the
// values to the cells
row = table.AddRow();
row.Height = new Unit(billaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.BillingAddress.AddressLine1);
row = table.AddRow();
row.Height = new Unit(billaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.BillingAddress.City + ", " + 
                          _purchaseOrder.BillingAddress.State + "  " + 
                          _purchaseOrder.BillingAddress.ZipCode);
row = table.AddRow();
row.Height = new Unit(billaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].AddParagraph("Phone:");
row.Cells[1].AddParagraph(_purchaseOrder.BillingAddress.PhoneNumber);
row = table.AddRow();
row.Height = new Unit(billaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].AddParagraph("Fax:");
row.Cells[1].AddParagraph(_purchaseOrder.BillingAddress.FaxNumber);

// lastly we use SetEdge to set the borders for out table, this method takes a starting
// column and row index, then a column and row count to apply the edge to, which edges types to effect
// then a border style to apply, and border size. 
table.SetEdge(0, 0, 2, 5, Edge.Box, MigraDoc.DocumentObjectModel.BorderStyle.None, 0.0);
#endregion

Next let's use the same process to add our Vendor address and Shipping address to the document

C#
#region Add Vendor Address Box
txtFrame = docsection.AddTextFrame();
txtFrame.Top = top.Position + 80;
txtFrame.RelativeVertical = RelativeVertical.Margin;
txtFrame.Left = ShapePosition.Left;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.FillFormat.Color = Colors.LightGray;
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.DashStyle = DashStyle.Solid;
txtFrame.LineFormat.Width = new Unit(0.5);
txtFrame.Width = new Unit(290.0);
txtFrame.Height = new Unit(15.0);
paragraph = txtFrame.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.Font.Bold = true;
paragraph.AddText("Vendor Address");
top = txtFrame.Top.Position + 15.0;
txtFrame = docsection.AddTextFrame();
txtFrame.Top = top;
txtFrame.RelativeVertical = RelativeVertical.Margin;
txtFrame.Left = ShapePosition.Left;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.FillFormat.Color = Colors.White;
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.DashStyle = DashStyle.Solid;
txtFrame.LineFormat.Width = new Unit(0.5);
txtFrame.Width = new Unit(290.0);
txtFrame.Height = new Unit(84.0);
table = txtFrame.AddTable();
table.Format.Alignment = ParagraphAlignment.Left;
table.Format.LeftIndent = 3.0;
column = table.AddColumn(new Unit(70.0));
column.Format.Alignment = ParagraphAlignment.Left;
column = table.AddColumn(new Unit(220.0));
column.Format.Alignment = ParagraphAlignment.Left;
row = table.AddRow();
Unit venaddrowheight = new Unit(14.0);
row.Height = new Unit(venaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.VendorAddress.CompanyName);
row = table.AddRow();
row.Height = new Unit(venaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.VendorAddress.AddressLine1);
row = table.AddRow();
row.Height = new Unit(venaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.VendorAddress.City + ", " + 
                          _purchaseOrder.VendorAddress.State + "  " + 
                          _purchaseOrder.VendorAddress.ZipCode);
row = table.AddRow();
row.Height = new Unit(venaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row = table.AddRow();
row.Height = new Unit(venaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].AddParagraph("Phone:");
row.Cells[1].AddParagraph(_purchaseOrder.VendorAddress.PhoneNumber);
row = table.AddRow();
row.Height = new Unit(venaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].AddParagraph("Fax:");
row.Cells[1].AddParagraph(_purchaseOrder.VendorAddress.FaxNumber);
table.SetEdge(0, 0, 2, 6, Edge.Box, MigraDoc.DocumentObjectModel.BorderStyle.None, 0.0);
#endregion

And the shipping address

C#
#region Add Shipping Address Box
txtFrame = docsection.AddTextFrame();
txtFrame.Top = top.Position - 15;
txtFrame.RelativeVertical = RelativeVertical.Margin;
txtFrame.Left = ShapePosition.Left;
txtFrame.Left = txtFrame.Left.Position + 457;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.FillFormat.Color = Colors.LightGray;
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.DashStyle = DashStyle.Solid;
txtFrame.LineFormat.Width = new Unit(0.5);
txtFrame.Width = new Unit(290.0);
txtFrame.Height = new Unit(15.0);
txtFrame.MarginTop = new Unit(2.5);
paragraph = txtFrame.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.Font.Bold = true;
paragraph.AddText("Shipping Address");
top = txtFrame.Top.Position + 15.0;
txtFrame = docsection.AddTextFrame();
txtFrame.Top = top;
txtFrame.RelativeVertical = RelativeVertical.Margin;
txtFrame.Left = ShapePosition.Left;
txtFrame.Left = txtFrame.Left.Position + 457;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.FillFormat.Color = Colors.White;
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.DashStyle = DashStyle.Solid;
txtFrame.LineFormat.Width = new Unit(0.5);
txtFrame.Width = new Unit(290.0);
txtFrame.Height = new Unit(84.0);
table = txtFrame.AddTable();
table.Format.Alignment = ParagraphAlignment.Left;
table.Format.LeftIndent = 3.0;
column = table.AddColumn(new Unit(70.0));
column.Format.Alignment = ParagraphAlignment.Left;
column = table.AddColumn(new Unit(220.0));
column.Format.Alignment = ParagraphAlignment.Left;
Unit shipaddrowheight = new Unit(14.0);
row = table.AddRow();
row.Height = new Unit(shipaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].AddParagraph("Attention:");
row.Cells[1].AddParagraph(_purchaseOrder.ShippingAddress.Attention);
row = table.AddRow();
row.Height = new Unit(shipaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.ShippingAddress.CompanyName);
row = table.AddRow();
row.Height = new Unit(shipaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.ShippingAddress.AddressLine1);
row = table.AddRow();
row.Height = new Unit(shipaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.ShippingAddress.AddressLine2);
row = table.AddRow();
row.Height = new Unit(shipaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph(_purchaseOrder.ShippingAddress.City + ", " +
                          _purchaseOrder.ShippingAddress.State + "  " + 
                          _purchaseOrder.ShippingAddress.ZipCode);
row = table.AddRow();
row.Height = new Unit(shipaddrowheight);
row.VerticalAlignment = VerticalAlignment.Center;
row.Cells[0].AddParagraph("Reference:");
row.Cells[1].AddParagraph(_purchaseOrder.ShippingAddress.Reference);
table.SetEdge(0, 0, 2, 3, Edge.Box, MigraDoc.DocumentObjectModel.BorderStyle.None, 0.0);
#endregion

 Now let's go back to our Item class, and let's add a property to return the extended cost of the item

C#
public double ExtendedCost { get { return _cost * _purchaseQuantity; } }

And lastly, let's add our items, again mostly the same way as we added our addresses, except we will do it in 2 steps. We don't want our table to be dynamic in appearance, we want it to be the same size on every page, so first we will create the items box itself, then we will go back and fill it with the items data. So first, let's build the item box

C#
#region Add Items Table
txtFrame = docsection.AddTextFrame();
txtFrame.Left = ShapePosition.Left;
txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
txtFrame.Top = top.Position + 94;
txtFrame.RelativeVertical = RelativeVertical.Margin;
txtFrame.Width = document.DefaultPageSetup.PageHeight - 
                 document.DefaultPageSetup.LeftMargin - 
                 document.DefaultPageSetup.RightMargin;
txtFrame.Height = new Unit(287);
txtFrame.LineFormat.Color = Colors.Black;
txtFrame.LineFormat.Width = new Unit(0.5);
table = txtFrame.AddTable();
table.LeftPadding = 0;
table.RightPadding = 0;

// here we define the width of each column, we need to define the Description column width last
// because we want it to take the remainder of the space left after we set hard widths for the rest
// of the columns, also, because the description is going to occupy 2 columns, we find the width and
// divide by 2 and actually add 2 columns
Unit linecolwidth = new Unit(55);
Unit qtycolwidth = new Unit(55);
Unit pncolwidth = new Unit(105);
Unit vpncolwidth = new Unit(105);
Unit costcolwidth = new Unit(80);
Unit extcostwidth = new Unit(79);
Unit desccolwidth = new Unit(document.DefaultPageSetup.PageHeight - 
                             document.DefaultPageSetup.LeftMargin - 
                             document.DefaultPageSetup.RightMargin - 
                             linecolwidth - qtycolwidth - pncolwidth - 
                             vpncolwidth - costcolwidth - extcostwidth);
Unit desccolwidth1 = new Unit(desccolwidth / 2);
Unit desccolwidth2 = new Unit(desccolwidth / 2);
column = table.AddColumn(linecolwidth);
column = table.AddColumn(qtycolwidth);
column = table.AddColumn(pncolwidth);
column = table.AddColumn(vpncolwidth);
column = table.AddColumn(desccolwidth1);
column = table.AddColumn(desccolwidth2);
column = table.AddColumn(costcolwidth);
column = table.AddColumn(extcostwidth);
row = table.AddRow();
row.Height = new Unit(15);
row.Shading.Color = Colors.LightGray;
row.Format.Alignment = ParagraphAlignment.Center;
row.VerticalAlignment = VerticalAlignment.Center;
row.Format.Font.Bold = true;
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph("Buyer");
row.Cells[2].MergeRight = 1;
row.Cells[2].AddParagraph("Buyer Contact");
row.Cells[4].AddParagraph("Shipping Method");
row.Cells[5].AddParagraph("Freight Terms");
row.Cells[6].AddParagraph("Date");
row.Cells[7].AddParagraph("Page");
row = table.AddRow();
row.Format.LeftIndent = new Unit(2.0);
row.Format.Alignment = ParagraphAlignment.Center;
row.VerticalAlignment = VerticalAlignment.Center;
row.Height = new Unit(15);
row.Cells[0].MergeRight = 1;
// Buyer name and contact, ship via, and ship terms, and PO Date we will 
// add to our data object at a later date
// for now we will just output something as a place holder
row.Cells[0].AddParagraph("John Doe");
row.Cells[2].MergeRight = 1;
row.Cells[2].AddParagraph("john.dow@mdipurchasing.com");
row.Cells[4].Format.Alignment = ParagraphAlignment.Center;
row.Cells[4].AddParagraph("UPS Ground");
row.Cells[5].Format.Alignment = ParagraphAlignment.Center;
row.Cells[5].AddParagraph("Prepaid and Add");
row.Cells[6].Format.Alignment = ParagraphAlignment.Center;
row.Cells[6].AddParagraph(DateTime.Today.ToString("M/d/yyyy"));

// here is where we output our page number string, Page x of y
row.Cells[7].Format.Alignment = ParagraphAlignment.Center;
pageString = String.Format("Page {0} of {1}", pageNumber, totalPages);
row.Cells[7].AddParagraph(pageString);
row = table.AddRow();
row.Height = new Unit(15);
row.Format.LeftIndent = new Unit(2.0);
row.VerticalAlignment = VerticalAlignment.Center;
row.Format.Alignment = ParagraphAlignment.Center;
row.Format.Font.Bold = true;
row.Shading.Color = Colors.LightGray;
row.Cells[0].AddParagraph("Line");
row.Cells[1].AddParagraph("Qty");
row.Cells[2].MergeRight = 1;
row.Cells[2].AddParagraph("Part");
row.Cells[4].MergeRight = 1;
row.Cells[4].AddParagraph("Description");
row.Cells[6].AddParagraph("Cost");
row.Cells[7].AddParagraph("Ext. Cost");

// here we go ahead and add the entire itemsPerPage number of rows, we will fill
// them with data in the next step
for (int i = 0; i < itemsPerPage; i++)
{
    row = table.AddRow();
    row.Height = new Unit(16.0);
    row.VerticalAlignment = VerticalAlignment.Center;
    row.Cells[2].MergeRight = 1;
    row.Cells[4].MergeRight = 1;
}

// here we again set our edges, this time will also set some interior, and vertical only edges
table.SetEdge(0, 0, 8, 3, Edge.Interior, MigraDoc.DocumentObjectModel.BorderStyle.Single, 0.5);
table.SetEdge(0, 3, 8, itemsPerPage, Edge.Box, MigraDoc.DocumentObjectModel.BorderStyle.Single, 0.5);
table.SetEdge(0, 3, 8, itemsPerPage, Edge.Vertical, MigraDoc.DocumentObjectModel.BorderStyle.Single, 0.5);
#endregion

Now let's place our item data into the table

C#
#region Add Items To Item Box

// itemstart will tell us which item in the whole collection to start with
// itemmax will tell us how many total items we want to output to this page
// which will be either itemsPerPage, or less in the remaining items are fewer
// itemsPrinted is the count of total items printed for the entire document, and
// serves as our starting point for this page, so we set itemsstart to itemsPrinted
int itemstart = itemsPrinted;
int itemmax = 0;
for (int i = 0; (i < itemsPerPage && (itemmax + itemstart) < _purchaseOrder.Items.Count); i++)
{
    if (i < itemsPerPage) itemmax++;
}

// now we step through our item collection, starting at our starting point established
// above, and continuing for the the itemmax amount of items
Item printItem;
for (int i = 0; i < itemsPerPage && itemsPrinted < (itemstart + itemmax); i++)
{
    // get the row we want to add item data to
    row = table.Rows[(i % itemsPerPage) + 3];

    // get the item we want to add data from
    printItem = _purchaseOrder.Items[itemsPrinted];
    row.Cells[0].Format.Alignment = ParagraphAlignment.Center;
    row.Cells[0].AddParagraph((itemsPrinted + 1).ToString());
    row.Cells[1].Format.Alignment = ParagraphAlignment.Center;
    row.Cells[1].AddParagraph(printItem.PurchaseQuantity.ToString());
    row.Cells[2].Format.Alignment = ParagraphAlignment.Left;
    row.Cells[2].Format.LeftIndent = new Unit(4.0);
    row.Cells[2].AddParagraph(printItem.ProductNumber);

    // note we skip #3, this is because we've merged column 2 to the right 1, so 3 is merged into 2
    // anytime you merge, you have to skip those columns that were merged into the column before
    row.Cells[4].Format.Alignment = ParagraphAlignment.Left;
    row.Cells[4].Format.LeftIndent = new Unit(4.0);
    row.Cells[4].AddParagraph(printItem.Description);

    // again we skip #5 because we merged column 4 right 1
    row.Cells[6].Format.Alignment = ParagraphAlignment.Right;
    row.Cells[6].Format.RightIndent = new Unit(4.0);
    row.Cells[6].AddParagraph(printItem.Cost.ToString("F02"));
    row.Cells[7].Format.Alignment = ParagraphAlignment.Right;
    row.Cells[7].Format.RightIndent = new Unit(4.0);
    row.Cells[7].AddParagraph(printItem.ExtendedCost.ToString("F2"));
    itemsPrinted++;
}
#endregion

Now, for one final portion of our document. Whenever we are on our last page, we want to output a box below our items for comments, instructions, our document grand total, and a signature. In our PurchaseOrder class, let's add a readonly property to return our document total, which will be an aggregate of all of the items' ExtendedCost, we'll call this property PurchaseOrderTotal

public double PurchaseOrderTotal
{
    get
    {
        double total = 0;
        foreach (Item i in Items) total += i.ExtendedCost;
        return total;
    }
}

Now we have everything we need to place our document "footer" so here's what that looks like

C#
#region Add Comments Signatur And PO Total Box

// here check to see if this is the last page to print, and if so, proceed with footer
if (pageNumber == totalPages)
{
    top = txtFrame.Top.Position + txtFrame.Height;
    txtFrame = docsection.AddTextFrame();
    txtFrame.Top = top;
    txtFrame.RelativeVertical = RelativeVertical.Margin;
    txtFrame.Left = ShapePosition.Left;
    txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
    txtFrame.FillFormat.Color = Colors.LightGray;
    txtFrame.LineFormat.Color = Colors.Black;
    txtFrame.LineFormat.Width = new Unit(0.5);
    txtFrame.Height = new Unit(15);
    txtFrame.Width = new Unit(linecolwidth + qtycolwidth + pncolwidth + 
                              vpncolwidth + desccolwidth + costcolwidth);
    table = txtFrame.AddTable();
    table.Format.Font.Bold = true;
    table.LeftPadding = 0;
    table.Format.LeftIndent = new Unit(4.0);
    table.Format.RightIndent = new Unit(4.0);
    column = table.AddColumn(new Unit(txtFrame.Width - costcolwidth));
    column.Format.Alignment = ParagraphAlignment.Left;
    column = table.AddColumn(new Unit(costcolwidth));
    column.Format.Alignment = ParagraphAlignment.Right;
    row = table.AddRow();
    row.Height = new Unit(15);
    row.VerticalAlignment = VerticalAlignment.Center;
    row.Cells[0].AddParagraph("Comments");
    row.Cells[1].AddParagraph("Total");
    table.SetEdge(0, 0, 2, 1, Edge.Vertical, MigraDoc.DocumentObjectModel.BorderStyle.Single, 0.5);
    left = txtFrame.Left.Position + txtFrame.Width;
    txtFrame = docsection.AddTextFrame();
    txtFrame.Top = top;
    txtFrame.RelativeVertical = RelativeVertical.Margin;
    txtFrame.Left = left;
    txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
    txtFrame.Height = new Unit(15);
    txtFrame.Width = new Unit(extcostwidth);
    txtFrame.LineFormat.Width = new Unit(0.5);
    txtFrame.LineFormat.Color = Colors.Black;
    table = txtFrame.AddTable();
    table.Format.Alignment = ParagraphAlignment.Right;
    column = table.AddColumn(new Unit(extcostwidth) - 0.5);
    column.RightPadding = 0;
    row = table.AddRow();
    row.Height = new Unit(15);
    row.VerticalAlignment = VerticalAlignment.Center;
    row.Cells[0].AddParagraph(_purchaseOrder.PurchaseOrderTotal.ToString("F02"));
    txtFrame = docsection.AddTextFrame();
    txtFrame.Top = top.Position + 15;
    txtFrame.RelativeVertical = RelativeVertical.Margin;
    txtFrame.Height = new Unit(60);
    txtFrame.Width = document.DefaultPageSetup.PageHeight - document.DefaultPageSetup.LeftMargin - 
                     document.DefaultPageSetup.RightMargin - costcolwidth - extcostwidth;
    txtFrame.Left = ShapePosition.Left;
    txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
    txtFrame.LineFormat.Color = Colors.Black;
    txtFrame.LineFormat.Width = new Unit(0.5);
    txtFrame.MarginTop = new Unit(4.0);
    txtFrame.MarginBottom = new Unit(4.0);
    txtFrame.MarginLeft = new Unit(4.0);
    txtFrame.MarginRight = new Unit(4.0);
    paragraph = txtFrame.AddParagraph();
    paragraph.Format.Alignment = ParagraphAlignment.Left;
    paragraph.Format.LineSpacing = new Unit(8.0);
    // for now we will put test data into the comments section until we add comments to our data model
    // but it could easily be left blank for someone to hand write comments after printing
    paragraph.AddText("Sample comments for output testing");
    left = txtFrame.Left.Position + txtFrame.Width;
    txtFrame = docsection.AddTextFrame();
    txtFrame.Top = top.Position + 15;
    txtFrame.RelativeVertical = RelativeVertical.Margin;
    txtFrame.Left = left;
    txtFrame.RelativeHorizontal = RelativeHorizontal.Margin;
    txtFrame.Width = new Unit(costcolwidth + extcostwidth);
    txtFrame.Height = new Unit(60);
    txtFrame.LineFormat.Width = new Unit(0.5);
    txtFrame.LineFormat.Color = MigraDoc.DocumentObjectModel.Colors.Black;
    txtFrame.MarginTop = new Unit(4.0);
    txtFrame.MarginBottom = new Unit(4.0);
    txtFrame.MarginLeft = new Unit(4.0);
    txtFrame.MarginRight = new Unit(4.0);
    paragraph = txtFrame.AddParagraph();
    paragraph.Format.Alignment = ParagraphAlignment.Left;
    paragraph.AddText("\nSignature   ");
    image = paragraph.AddImage("signature.png");
    image.ScaleWidth = 0.5;
    image.ScaleHeight = 0.5;
}
#endregion

The last 3 lines adds a signature file to the document, if you wanted to leave the document signature blank, and sign it manually, just leave thow last three lines off. Additionally you could sign a piece of paper, scan it and use it as your signature, but if you do you'll likely need to tinker with the scale so that it looks right. Just tweak image.ScaleHeight and image.ScaleWidth, though I suggest using the same value for both so that the image doesn't look skewed.

Signature

So that's it for the document, it's now complete. We've already handled rendering it to a PDF for test purposes, up above. Now let's handle rendering to a printer. We already created a print method way back to handle just a basic concept of GDI printing. Now we don't need that, though I will be leaving it in the source for reference but commented out. You can safely delete the printDoc_BeginPrint and printDoc_PrintPage methods. Your new printToolStripButton_Click should look like

C#
private void printToolStripButton_Click(object sender, EventArgs e)
{
​    PrintDialog printDialog = new PrintDialog();

    if (printDialog.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
    {

        MigraDoc.DocumentObjectModel.Document document = 
                              (this.ActiveMdiChild as PurchaseOrderForm).GetDocument();
        MigraDoc.Rendering.DocumentRenderer renderer = 
                              new MigraDoc.Rendering.DocumentRenderer(document);
        renderer.PrepareDocument();

        MigraDoc.Rendering.Printing.MigraDocPrintDocument printer = 
                              new MigraDoc.Rendering.Printing.MigraDocPrintDocument(renderer);
        printer.PrinterSettings = printDialog.PrinterSettings;
        printer.DocumentName = document.Info.Title;
        printer.Print();
    }
}

This really only scratches the surface of what MigraDoc and PDFSharp can do. I hope it's a helpful addition to this study, giving you a way to easily add document output to your application.

 

 

Points of Interest

  • MigraDoc and PDFSharp libraries
  • PDF file output
  • Printing

History

Keep a running update of any changes or improvements you've made here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionTextFrames in MigraDoc Pin
Member 914127125-May-17 5:25
Member 914127125-May-17 5:25 
PraiseGreat series Pin
Member 1099361523-Feb-16 5:23
Member 1099361523-Feb-16 5:23 
GeneralRe: Great series Pin
stebo072826-Feb-16 4:16
stebo072826-Feb-16 4:16 
QuestionRules Pin
NelsonCosta18-Jan-16 5:42
NelsonCosta18-Jan-16 5:42 
AnswerRe: Rules Pin
stebo072830-Jan-16 12:46
stebo072830-Jan-16 12:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.