Printing Tables with MigraDoc

I found working with MigraDoc tables to be cumbersome in real-life scenarios, so I created an abstraction that requires only that the user pass in a list of columns and a list of rows.

First of all I created these classes:

    ///Need to use MigraDoc.DocumentObjectModel and MigraDoc.DocumentObjectModel.Tables
 
    /// <summary>
    /// A class containing content for a table row
    /// </summary>
    public class TableRow
    {
        /// <summary>
        /// The row's cells
        /// </summary>
        private List<TableCell> cells;
 
        /// <summary>
        /// Constructors
        /// </summary>
        public TableRow()
            : this("")
        {
        }
 
        public TableRow(string style)
        {
            cells = new List<TableCell>();
            this.Style = style;
        }
 
        /// <summary>
        /// Add the row and content to a MigraDoc table
        /// </summary>
        /// <param name="table">The table to add the row and content to</param>
        public virtual Row GenerateRow(Table table)
        {
            Row row = table.AddRow();
 
            //Set the header formatting
            if (this.IsHeader)
            {
                row.HeadingFormat = true;
                row.Format.Font.Bold = true;
                row.Shading.Color = new Color(0x90, 0x90, 0x90);
            }
 
            //Set the row style
            if ((this.Style != "") && (this.Style != String.Empty))
            {
                row.Style = this.Style;
            }
 
            //Add content to cells
            int cellIndex = 0;
            for (int i = 0; i < this.Cells.Count; i++)
            {
                TableCell cell = this.Cells[i];
 
                cell.AddContent(row.Cells[cellIndex]);
 
                if (cell.CellSpan > 1)
                {
                    row.Cells[cellIndex].MergeRight = cell.CellSpan;
                    cellIndex += cell.CellSpan;
                }
                else
                {
                    cellIndex++;
                }
            }
 
            return row;
        }
 
        #region Properties
 
        /// <summary>
        /// Is the row a table header?
        /// </summary>
        public bool IsHeader { get; set; }
 
        /// <summary>
        /// The row's cells
        /// </summary>
        public List<TableCell> Cells
        {
            get { return cells; }
        }
 
        /// <summary>
        /// The style to apply to the row
        /// </summary>
        public string Style { get; set; }
 
        #endregion
    }
 
    /// <summary>
    /// A class containing the definiton of table column
    /// </summary>
    public class TableColumn
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="width">The width of the column</param>
        public TableColumn(int width)
        {
            this.Width = width;
        }
 
        /// <summary>
        /// The width of the column
        /// </summary>
        public int Width { get; set; }
    }
 
    /// <summary>
    /// A class defining the contents of a cell
    /// </summary>
    public class TableCell
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="content">The content of the cell</param>
        public TableCell(string content)
            : this(content, 0)
        {
        }
 
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="content">The content of the cell</param>
        /// <param name="cellSpan">How many columns the cell spans</param>
        public TableCell(string content, int cellSpan)
            : this(content, cellSpan, false)
        {
        }
 
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="content">The content of the cell</param>
        /// <param name="cellSpan">How many columns the cell spans</param>
        /// <param name="paragraphAlignment">The cell alignment</param>
        public TableCell(string content, int cellSpan, ParagraphAlignment paragraphAlignment)
            : this(content, cellSpan, false, paragraphAlignment)
        {
        }
 
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="content">The content of the cell</param>
        /// <param name="cellSpan">How many columns the cell spans</param>
        /// <param name="fixedWidth">Whether the cell displays fixed-width text</param>
        public TableCell(string content, int cellSpan, bool fixedWidth)
            : this(content, cellSpan, fixedWidth, ParagraphAlignment.Left)
        {
        }
 
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="content">The content of the cell</param>
        /// <param name="cellSpan">How many columns the cell spans</param>
        /// <param name="fixedWidth">Whether the cell displays fixed-width text</param>
        /// <param name="paragraphAlignment">The cell alignment</param>
        public TableCell(string content, int cellSpan, bool fixedWidth, ParagraphAlignment paragraphAlignment)
        {
            this.Content = content;
            this.CellSpan = cellSpan;
            this.FixedWidth = fixedWidth;
            this.ParagraphAlignment = paragraphAlignment;
        }
 
        /// <summary>
        /// Adds fixed-width text to a MigraDoc paragraph
        /// </summary>
        /// <param name="paragraph">The paragaraph to add text to</param>
        /// <param name="text">The text</param>
        private void AddFixedWidthText(Paragraph paragraph, string text)
        {
            //For some reason, a parapraph converts all sequences of white
            //space to a single space.  Thus we need to split the text and add
            //the spaces using the AddSpace function.
            int charIndex = 0;
            int sectionStartIndex = 0;
            bool inWord = ((text.Length > 0) && (text[0] != ' '));
 
            paragraph.Format.Font.Name = "Courier New";
 
            while (charIndex < text.Length)
            {
                if ((this.Content[charIndex] == ' ') && (inWord))
                {
                    paragraph.AddText(text.Substring(sectionStartIndex, (charIndex - sectionStartIndex)));
                    inWord = false;
                    sectionStartIndex = charIndex;
                }
                else if (!inWord)
                {
                    paragraph.AddSpace(charIndex - sectionStartIndex);
                    inWord = true;
                    sectionStartIndex = charIndex;
                }
 
                charIndex++;
            }
        }
 
        /// <summary>
        /// Write the cell content to a MigraDoc cell
        /// </summary>
        /// <param name="cell">The cell to add content to</param>
        public virtual void AddContent(Cell cell)
        {
            if (this.Content != null)
            {
                Paragraph paragraph = cell.AddParagraph();
                paragraph.Format.Alignment = this.ParagraphAlignment;
 
                if (this.FixedWidth)
                {
                    AddFixedWidthText(paragraph, this.Content);
                }
                else
                {
                    paragraph.AddText(this.Content);
                }
 
            }
        }
 
        /// <summary>
        /// How many columns the cell spans
        /// </summary>
        public int CellSpan { get; set; }
 
        /// <summary>
        /// The content of the cell
        /// </summary>
        public string Content { get; set; }
 
        /// <summary>
        /// Whether the cell displays fixed-width text
        /// </summary>
        public bool FixedWidth { get; set; }
 
        /// <summary>
        /// The cell's text alignment
        /// </summary>
        public ParagraphAlignment ParagraphAlignment { get; set; }
    }
 
    /// <summary>
    /// A class describing a cell that contains an image
    /// </summary>
    public class TableImageCell : TableCell
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="image">The image for the cell to contain</param>
        public TableImageCell(System.Drawing.Image image)
            : base("")
        {
            this.CellImage = image;
        }
 
        /// <summary>
        /// Add the image to a MigraDoc cell
        /// </summary>
        /// <param name="cell">The cell to add content to</param>
        public override void AddContent(Cell cell)
        {
            MigraDoc.DocumentObjectModel.Shapes.Image image = cell.AddImage(this.CellImage);
            image.Top = 1;
        }
 
        /// <summary>
        /// The image for the cell to contain
        /// </summary>
        public System.Drawing.Image CellImage { get; set; }
    }

Next I created a method to generate a table from them:

        /// <summary>
        /// Create the table content and add it to the given table
        /// </summary>
        /// <param name="detailsTable">The table to generate</param>
        /// <param name="columnDefinitions">The definitions of the columns</param>
        /// <param name="rows">The content of the rows</param>
        /// <param name="hasBorder">Whether or not the table has a border</param>
        public static void GenerateTable(Table table, List<TableColumn> columnDefinitions, List<TableRow> rowContents, bool hasBorder)
        {
            //Define the table's parameters
            table.Style = "Table";
            if (hasBorder)
            {
                table.Borders.Width = 0.25;
                table.Borders.Left.Width = 0.5;
                table.Borders.Right.Width = 0.5;
            }
            table.Rows.LeftIndent = 0;
 
            //Before you can add a row, you must define the columns
            Column column;
            foreach (TableColumn columnDefinition in columnDefinitions)
            {
                column = table.AddColumn();
                column.Width = columnDefinition.Width;
            }
 
            //Add rows
            foreach (TableRow rowContent in rowContents)
            {
                rowContent.GenerateRow(table);
            }
 
            table.SetEdge(0, 0, columnDefinitions.Count, rowContents.Count, Edge.Box, BorderStyle.Single, 0.75, Color.FromRgbColor(0xFF, new Color(0x00, 0x00, 0x00)));
        }

An example of usage, proprietary content stripped, untested in this form:

  /// <summary>
  /// Adds content to given section
  /// </summary>
  /// <param name="section">The document section to add content to</param>
  public override void GenerateContent(Section section)
  {
      // Generate the title
      Paragraph paragraph = section.AddParagraph();
      paragraph.Style = "Title";
      string title = "My Table";
      paragraph.AddFormattedText(title, TextFormat.Bold);
 
      //Generate a table representing the bids
      List<int> columnIndexesToPrint = new List<int>();
      List<TableColumn> columnDefinitions = new List<TableColumn>();
      List<TableRow> rowContent = new List<TableRow>(MyDataGridView.Rows.Count + 1); //+1 for header
      int tableWidth = 0;
 
      //Generate the list of columns and define the header row
      TableRow headerRow = new TableRow();
      headerRow.IsHeader = true;
      foreach (System.Windows.Forms.MyDataGridViewViewColumn column in MyDataGridView.Columns)
      {
          //We don't want to print columns that aren't visible on the screen
          if (column.Visible)
          {
              int columnIndex = column.Index;
 
              columnIndexesToPrint.Add(columnIndex);
              columnDefinitions.Add(new TableColumn(column.Width));
              tableWidth += column.Width;
              headerRow.Cells.Add(new TableCell(MyDataGridView.Columns[columnIndex].HeaderText));
          }
      }
      rowContent.Add(headerRow);
 
      //Adjust bid column width to fit page
      if (tableWidth > this.PageWidth * PrintableDocumentGenerator.ImagePixelToMigraDocPointRatio)
      {
          columnDefinitions[2].Width -= (tableWidth - (int)Math.Ceiling(this.PageWidth * PrintableDocumentGenerator.ImagePixelToMigraDocPointRatio));
      }
 
      //Add the data grid view rows as table rows
      foreach (System.Windows.Forms.MyDataGridViewViewRow gridViewRow in MyDataGridView.Rows)
      {
          HighlightableRow row = new HighlightableRow(gridViewRow.Selected); //Because image cells don't use paragraphs, we cannot just use the "Highlighted" style directly, we need to have a class that sets the row background as well.
          foreach (System.Windows.Forms.MyDataGridViewViewCell cell in gridViewRow.Cells)
          {
              if (columnIndexesToPrint.Contains(cell.ColumnIndex))
              {
                  if (cell.Value != null)
                  {
                      if (cell is System.Windows.Forms.MyDataGridViewViewImageCell)
                      {
                          row.Cells.Add(new TableImageCell(cell.Value as System.Drawing.Image));
                      }
                      else if (cell is System.Windows.Forms.MyDataGridViewViewCheckBoxCell)
                      {
                          row.Cells.Add(new TableCell((bool)cell.Value ? "X" : ""));
                      }
                      else
                      {
                          row.Cells.Add(new TableCell(cell.Value.ToString()));
                      }
                  }
                  else
                  {
                      row.Cells.Add(new TableCell(""));
                  }
              }
          }
          rowContent.Add(row);
      }
 
      GenerateTable(section.AddTable(), columnDefinitions, rowContent, false);
  }
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License