7

The solution for C# you can find in the Edit-Part of the Question. Special Thanks to Bruno Lowagie

I'm trying to create an invoice via iTextSharp in C#. It works very well but im gettin problems while i try to print the subtotal on every page.

Here is a part of the code im using to create the table for the products:

PdfPTable table = new PdfPTable(5);
        table.WidthPercentage = 100;
        float[] widths = new float[] { 10f, 30f, 120f, 30f, 30f };
        table.SetWidths(widths);
        table.SkipLastFooter = true;
        table.SpacingAfter = 10;                    

        //Cells to Write Header & Footer
        AddCell(table, "Pos.", PdfPCell.ALIGN_RIGHT, BORDER_LTB, 0);
        AddCell(table, "Menge", PdfPCell.ALIGN_RIGHT, BORDER_TB);
        AddCell(table, "Text", PdfPCell.ALIGN_LEFT, BORDER_TB);
        AddCell(table, "Einzelpreis ", PdfPCell.ALIGN_RIGHT, BORDER_TB);
        AddCell(table, "Summe", PdfPCell.ALIGN_RIGHT, BORDER_RTB);

        AddCell(table, "SUBTOTAL", PdfPCell.ALIGN_LEFT, BORDER_LTB, 7, 4);
        AddCell(table, "", PdfPCell.ALIGN_LEFT, BORDER_RTB);

        table.HeaderRows = 2;
        table.FooterRows = 1;

        //Cells with positions of invoice
        for (int i = 0; i < beleg.Einzel.Count; i++)
        {
            AddCell(table, beleg.Einzel[i].be_lfd_nr.ToString(), PdfPCell.ALIGN_LEFT, BORDER_LTB);
            AddCell(table, beleg.Einzel[i].be_art_menge.ToString("N", _gbVars.NFI3D), PdfPCell.ALIGN_RIGHT, BORDER_TB);
            AddCell(table, (beleg.Einzel[i].be_art_bez.ToString() + "\n" + beleg.Einzel[i].be_pos_text.ToString()).Trim(), PdfPCell.ALIGN_LEFT, BORDER_TB);
            AddCell(table, beleg.Einzel[i].be_summe_preis.ToString("N", _gbVars.NFI2D), PdfPCell.ALIGN_RIGHT, BORDER_TB);
            AddCell(table, beleg.Einzel[i].be_summe_netto.ToString("N", _gbVars.NFI2D), PdfPCell.ALIGN_RIGHT, BORDER_RTB);
        }

        table.SplitLate = false;
        document.Add(table);

Border_RTB and so on are Constants for my Border-Styles.

This code generates a table witch looks like this:

+------------------------------------------+
| Pos | Menge | Text | Einzelpreis | Summe |
+------------------------------------------+
|  1  |   2   | Text |       10.00 | 20.00 |
+------------------------------------------+
|  2  |   1   | Text |       10.00 | 10.00 |
+------------------------------------------+
|  3  |   4   | Text |       10.00 | 40.00 |
+------------------------------------------+
|  4  |   2   | Text |       10.00 | 20.00 |
+------------------------------------------+
|SUBTOTAL                          |       |
+------------------------------------------+

The Table can flow to the next page an i whant to write the subtotal of every page at the footer. The event "OnEndPage" does not help me because this event does not "know" where in the table the page is broken.

Can someone tell me how i get the subtotal on every page? Is there any solution how i can sum up something on a page an print it out on the pdf-file?

Sorry if my description is not that good. Having Problems with my english. :-/

EDIT:

Here is the Solution. Special Thanks to Bruno. He has shown me the way. In my Version, the Subtotal does NOT Reset on every Page. I guess this was a misstake in my description of the problem.

The new Clases:

public class Totals
    {
        public double subtotal = 0;
        public double total = 0;
    }


    public class SubTotalEvent : IPdfPCellEvent
    {
        Double price;   
        Totals totals;
        bool printTotals = false;       //If we just whant to print out the Subtotal, this will be true.
        bool CellWrittenOnce = false;   //Bool to SUM price just once (if row flows to a second page)

        public SubTotalEvent(Totals totals, double price) {
            printTotals = false;
            this.totals = totals;
            this.price = price;
        }

        public SubTotalEvent(Totals totals) {
            this.totals = totals;
            printTotals = true;
        }

        public void CellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
            if (printTotals)
            {
                PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS];
                ColumnText.ShowTextAligned(canvas, Element.ALIGN_LEFT, new Phrase(totals.subtotal.ToString()), position.GetLeft(0) + 2, position.GetBottom(0) + 2, 0);
                return;
            }      
            if (!CellWrittenOnce) { 
                totals.subtotal += price;
                totals.total += price;
            }
            CellWrittenOnce = true;
        }
    }

Code to create the Table:

PdfPTable table = new PdfPTable(5);
        table.WidthPercentage = 100;
        float[] widths = new float[] { 10f, 30f, 120f, 30f, 30f };
        table.SetWidths(widths);
        table.SkipLastFooter = true;
        table.SpacingAfter = 10;                    

        AddCell(new PdfPCell(new Phrase("item")));
        AddCell(new PdfPCell(new Phrase("amount")));
        AddCell(new PdfPCell(new Phrase("text")));
        AddCell(new PdfPCell(new Phrase("price")));
        AddCell(new PdfPCell(new Phrase("sum")));

        Totals totals = new Totals();
        PdfPCell cel = new PdfPCell(new Phrase("Subtotal"));
        cel.Colspan = 4;
        table.AddCell(cel);
        cel = new PdfPCell();
        cel.CellEvent = new SubTotalEvent(totals);
        table.AddCell(cel);


        table.HeaderRows = 2;
        table.FooterRows = 1;

        for (int i = 0; i < beleg.Einzel.Count; i++)
        {
            AddCell(new PdfPCell(new Phrase(i.toString())));
            AddCell(new PdfPCell(new Phrase("2")));
            AddCell(new PdfPCell(new Phrase("ItemText")));
            AddCell(new PdfPCell(new Phrase("10.00")));

            cell = new PdfPCell(new Phrase("20.00"));
            cell.CellEvent = new SubTotalEvent(totals, 20.00);
            table.AddCell(cell);
        }

        table.SplitLate = false;
        document.Add(table);
Martin S.
  • 142
  • 9
  • 2
    You need to implement the `PdfPageEventHelper` class. Your implementation would execute each time a page is created. See this tutorial on how to implement it http://www.mazsoft.com/blog/post/2008/04/30/Code-sample-for-using-iTextSharp-PDF-library – Darren Wainwright Sep 26 '16 at 12:12
  • 1
    I implement the `PdfPageEventHelper` class. But how can i figure out in `public override void OnEndPage(PdfWriter writer, Document doc)` where i am in the table? The Table is already written at this point. This event is called at `document.Add(table);` – Martin S. Sep 26 '16 at 12:21
  • Some things aren't immediately clear to me from your problem description, so I'm not sure if I have an answer yet. Are the following assumptions correct: 1. your invoice document consists of a single, large table that can possibly span multiple pages. 2. Your invoice is not a form, you have access to all information when generating the document. 3. You want to print the subtotal consisting of all the products on the current page + the sum of subtotals until the current page in the header/footer of that page. – Samuel Huylebroeck Sep 26 '16 at 13:36
  • 1. The products are all in a large single table witch can span mutiple pages. The Products have a description so the row-height can vary. 2. My invoice is stored in a mysql-Database. I'm having acces to everything. 3. Yes, i whant to print the subtotals + sum of subtotals until the current page in my footer of the product table. – Martin S. Sep 26 '16 at 14:01

1 Answers1

2

Please take a look at the SubTotal example.

First we create a class to keep track of the subtotal and the total:

class Totals {
    double subtotal = 0;
    double total = 0;
}

Then we implement the PdfPCellEvent interface that we will use on column 5:

class SubTotalEvent implements PdfPCellEvent {

    Double price;
    Totals totals;

    public SubTotalEvent(Totals totals, double price) {
        this.totals = totals;
        this.price = price;
    }

    public SubTotalEvent(Totals totals) {
        this.totals = totals;
        price = null;
    }

    @Override
    public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
        if (price == null) {
            PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS];
            ColumnText.showTextAligned(canvas, Element.ALIGN_LEFT,
                    new Phrase(String.valueOf(totals.subtotal)),
                    position.getLeft() + 2, position.getBottom() + 2, 0);
            totals.subtotal = 0;
            return;
        }
        totals.subtotal += price;
        totals.total += price;
    }

}

We have defined two constructors: one for a normal cell containing a price. One for a footer cell containing a subtotal (in this case we don't pass a price).

This is how we use this cell event:

public void createPdf(String dest) throws IOException, DocumentException {

    Totals totals = new Totals();

    Document document = new Document();
    PdfWriter.getInstance(document, new FileOutputStream(dest));
    document.open();
    PdfPTable table = new PdfPTable(5);
    table.setWidths(new int[]{1, 1, 1, 3, 3});
    // header
    table.addCell("Pos");
    table.addCell("Menge");
    table.addCell("Text");
    table.addCell("Einzerpreis");
    table.addCell("Summe");
    // footer
    PdfPCell cell = new PdfPCell(new Phrase("Subtotal"));
    cell.setColspan(4);
    table.addCell(cell);
    cell = new PdfPCell();
    cell.setCellEvent(new SubTotalEvent(totals));
    table.addCell(cell);
    // definitions
    table.setHeaderRows(2);
    table.setFooterRows(1);
    // table body
    for(int r = 0; r < 50; ){
        table.addCell(String.valueOf(++r));
        table.addCell("1");
        table.addCell("text");
        table.addCell("10.0");
        cell = new PdfPCell(new Phrase("10.0"));
        cell.setCellEvent(new SubTotalEvent(totals, 10));
        table.addCell(cell);
    }
    document.add(table);
    // extra footer
    table = new PdfPTable(5);
    table.setWidths(new int[]{1, 1, 1, 3, 3});
    cell = new PdfPCell(new Phrase("Grand total"));
    cell.setColspan(4);
    table.addCell(cell);
    table.addCell(String.valueOf(totals.total));
    document.add(table);
    document.close();
}

The result looks like this:

enter image description here

On the first page, we have 46 body rows each for an item with price 10. In the footer, we have a subtotal of 460. On the second page, we have 4 body rows each for an item with price 10. In the footer, we have a subtotal of 40. We added an extra table to mimic an extra footer for the Grand Total: 500.

Bruno Lowagie
  • 75,994
  • 9
  • 109
  • 165
  • We are almost there! Thank you very mutch! There was one problem left: If, for example, row 46 would have a larger hight and flows over two pages, the subtotal in your example on page one would be correct. But on page two it would be 50, not 40. But i fixed that with a additional bool-variable.For all readers: The code above from Bruno Lowagie is JAVA-Code. For C# you have to translate. But it works very well! Awesome. :-) – Martin S. Sep 27 '16 at 06:58