Hello, (Sorry for the delay, didn't see this post).
I could share the changes, but I've made too many other customizations to just patch in. Instead, I think it's pretty easy to share how I did it. You will have to start modifying MigraDoc. There may be other ways to do this, but I couldn't seem to get any response to the problem, so I did the best I could. One of the changes was to make properties out of lots of member variables, so I'll try to help out with the tweaks if some of those found their way into this code.
In class: MigraDoc.DocumentObjectModel.Table.Cell
Add a new Cell method that permits adding a table. This makes it possible to say cell.AddTable( myNestedTable) to get the table added into the cell.
Code:
public void AddTable(Table table)
{
Elements.Add(table);
}
So, now we have the table in the Cell's elements collection.
See how that works... Before doing anything else.... However, you will see that tall cells will run off the bottom of the page and lose the content.
I added a CalcPageSize overload to the FormattedDocument class to handle the case where a single row is too tall to fit on a single page. This will resize that page height to ensure that the entire table row fits... Poor solution, but no content is lost anyway.
Code:
XSize CalcPageSize(PageSetup pageSetup, ArrayList renderInfos)
{
double ContentHeight = 0.0;
// renderInfos holds the objects being rendered on this page
foreach (var renInfo in renderInfos)
{
// This computation only needs to be done for Tables where
// only a single row is being rendered on *this* page. This
// is likely a very tall row. (rows don't span pages yet...)
// If a row is too tall to fit on the remainder of the page it was
// pushed to the next page which might be what we have here.
// It's also possible that this is the last row in the table and was
// just pushed to a new page, or that we have a table with 1 row.
// ------------------------------------------------------------------------------
TableRenderInfo tri = renInfo as TableRenderInfo;
if (tri != null)
{
TableFormatInfo tfi = tri.FormatInfo as TableFormatInfo;
// Only rendering a single ROW here, make sure it fits on the page.
if (tfi != null && (int)tfi.ConnectedRowsMap[tfi.StartRow] == tfi.EndRow)
{
Dictionary<int, double> MCH = new Dictionary<int,double>();
foreach (FormattedCell cell in tfi.FormattedCells.Values)
{
if (cell != null)
{
int row = cell.Cell.Row.Index;
int col = cell.Cell.Column.Index;
// This indicates that we're only drawing a single row
if (row == tfi.StartRow && row == (int)tfi.ConnectedRowsMap[tfi.StartRow])
{
//Debug.WriteLine("CRM["+ tfi.StartRow + "]="+(int)tfi.ConnectedRowsMap [tfi.StartRow]+" Cell: " + row + "," + col + " -> h:" + cell.ContentHeight);
double CH = cell.ContentHeight + pageSetup.TopMargin.Point + pageSetup.BottomMargin.Point;
ContentHeight = Math.Max(CH, ContentHeight);
}
else if ((int)tfi.ConnectedRowsMap [tfi.StartRow] > tfi.StartRow)
{
double v = 0.0;
if (MCH.TryGetValue(col, out v))
{
MCH [col] += cell.ContentHeight;
}
else
{
MCH.Add(col,cell.ContentHeight);
}
//Debug.WriteLine("CRM["+ tfi.StartRow + "]="+(int)tfi.ConnectedRowsMap [tfi.StartRow]+" Cell: " + row + "," + col + " -> h:" + cell.ContentHeight + " MCH: " + MCH [col]);
ContentHeight = Math.Max(MCH[col] + pageSetup.TopMargin.Point + pageSetup.BottomMargin.Point, ContentHeight);
}
//else Debug.WriteLine("CRM["+ tfi.StartRow + "]="+(int)tfi.ConnectedRowsMap [tfi.StartRow]+" Cell: " + row + "," + col + " -> h:" + cell.ContentHeight);
}
}
}
}
}
XSize pgSize = new XSize(pageSetup.PageWidth.Point, Math.Max(pageSetup.PageHeight.Point, ContentHeight));
return pgSize;
}
The above is called in the IareaProvider.StoreRenderInfos method in the same class... I only needed to call this from this one spot.
Code:
void IAreaProvider.StoreRenderInfos(ArrayList renderInfos)
{
pageRenderInfos.Add(CurrentPage, renderInfos);
XSize pageSize = CalcPageSize(CurrentSection.PageSetup, renderInfos); // <<<== note the renderinfos being passed.
PageOrientation pageOrientation = CalcPageOrientation(CurrentSection.PageSetup);
PageInfo pageInfo = new PageInfo(pageSize.Width, pageSize.Height, pageOrientation);
PageInfos.Add(CurrentPage, pageInfo);
PageFieldInfos.Add(CurrentPage, CurrentFieldInfos);
}
Now, The following stuff I added while attempting to find a way to cause a row to split across pages. (didn't succeed here the way I wanted, since the rendering process for tables isn't done in a way I can understand. Just Can't wrap my head around it.). I made the following changes to help pass along the constraining area into the cell formatter in hopes of being able to constrain a table row to a page or inject another row on the following page if and continue what didn't fit.
In class: MigraDoc.Rendering.TableRenderer we need to make a few changes. First, FormatCells() needs to be handed it's constraining area.
Code:
void FormatCells(Area constrainingArea) // <<== added Area parameter.
{
// Added Cell Comparer here to allow traversing the
// Formatted Cells in a natural order.
formattedCells = new SortedList(new CellComparer());
foreach (Cell cell in mergedCells)
{
/// Here we need to detect that all the cell content doesn't fit.
FormattedCell formattedCell = new FormattedCell(cell, DocRenderer, mergedCells.GetEffectiveBorders(cell), FieldInfos, 0, 0) { ConstrainingArea = constrainingArea };
formattedCell.Format(Gfx);
try
{
formattedCells.Add(cell, formattedCell);
}
catch (Exception)
{
Debug.WriteLine("Tried to add same cell twice.");
}
}
}
And in InitFormat, we need to actually USE the constraining area we're handed, by passing that along to FormatCells we edited above.
Code:
/// <summary>
/// InitFormat here didn't use the area it is passed. -JAH
/// </summary>
/// <param name="area"></param>
/// <param name="previousFormatInfo"></param>
void InitFormat(Area area, FormatInfo previousFormatInfo)
{
TableFormatInfo prevTableFormatInfo = (TableFormatInfo)previousFormatInfo;
TableRenderInfo tblRenderInfo = new TableRenderInfo { TableObject = table };
RenderInfo = tblRenderInfo;
if (prevTableFormatInfo != null)
{
mergedCells = prevTableFormatInfo.MergedCells;
formattedCells = prevTableFormatInfo.FormattedCells;
bottomBorderMap = prevTableFormatInfo.BottomBorderMap;
lastHeaderRow = prevTableFormatInfo.LastHeaderRow;
connectedRowsMap = prevTableFormatInfo.ConnectedRowsMap;
startRow = prevTableFormatInfo.EndRow + 1;
}
else
{
mergedCells = new MergedCellList(table);
FormatCells(area); // <<=== Pass along the constraining Area passed into this method.
CalcLastHeaderRow();
CreateConnectedRows();
CreateBottomBorderMap();
if (doHorizontalBreak)
{
CalcLastHeaderColumn();
CreateConnectedColumns();
}
startRow = lastHeaderRow + 1;
}
((TableFormatInfo)tblRenderInfo.FormatInfo).MergedCells = mergedCells;
((TableFormatInfo)tblRenderInfo.FormatInfo).FormattedCells = formattedCells;
((TableFormatInfo)tblRenderInfo.FormatInfo).BottomBorderMap = bottomBorderMap;
((TableFormatInfo)tblRenderInfo.FormatInfo).ConnectedRowsMap = connectedRowsMap;
((TableFormatInfo)tblRenderInfo.FormatInfo).LastHeaderRow = lastHeaderRow;
}
In the FormattedCell class, I added the ConstrainingArea property and a new Enum to the RowHeightRule called RowHeightRule.Constrain.
Code:
public Area ConstrainingArea { get; set; } //<== in FormattedCell class
Code:
namespace MigraDoc.DocumentObjectModel.Tables
{
/// <summary>
/// Specifies the calculation rule of the row height.
/// </summary>
public enum RowHeightRule
{
AtLeast, // Minimum row height
Auto, // Calculate row height
Exactly, // Specific Row Height
Constrain // Fit within this area or split across pages **NEW**!!!
}
}
I updated the FormattedCell.InnerHeight property as follows:
Code:
internal XUnit InnerHeight
{
get
{
Row row = Cell.Row;
XUnit verticalPadding = row.TopPadding.Point;
verticalPadding += row.BottomPadding.Point;
switch (row.HeightRule)
{
case RowHeightRule.Exactly:
return row.Height.Point;
case RowHeightRule.Auto:
return verticalPadding + contentHeight;
case RowHeightRule.Constrain: // <<== Added this case...
return (contentHeight < 1) ? ConstrainingArea.Height : (XUnit)Math.Min(ConstrainingArea.Height, (verticalPadding + contentHeight));
case RowHeightRule.AtLeast:
default:
return Math.Max(row.Height, verticalPadding + contentHeight);
}
}
}