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); }
page revision: 0, last edited: 16 Sep 2009 16:25