using System; using System.Collections.Generic; using System.Drawing; using System.ComponentModel; using FastReport.Utils; namespace FastReport.Table { /// /// Specifies the layout that will be used when printing a big table. /// public enum TableLayout { /// /// The table is printed across a pages then down. /// AcrossThenDown, /// /// The table is printed down then across a pages. /// DownThenAcross, /// /// The table is wrapped. /// Wrapped } /// /// The base class for table-type controls such as and /// . /// public partial class TableBase : BreakableComponent, IParent { #region Fields private TableRowCollection rows; private TableColumnCollection columns; private TableStyleCollection styles; private int fixedRows; private int fixedColumns; private bool printOnParent; private float wrappedGap; private bool repeatHeaders; private bool repeatRowHeaders; private bool repeatColumnHeaders; private bool adjustSpannedCellsWidth; private bool lockCorrectSpans; private bool serializingToPreview; private bool lockColumnRowChange; private TableLayout layout; private List spanList; private TableResult resultTable; private TableCellData printingCell; //private static float FLeftRtl; #endregion #region Properties /// /// Gets a collection of table rows. /// [Browsable(false)] public TableRowCollection Rows { get { return rows; } } /// /// Gets a collection of table columns. /// [Browsable(false)] public TableColumnCollection Columns { get { return columns; } } internal TableStyleCollection Styles { get { return styles; } } /// /// Gets or sets the number of fixed rows that will be repeated on each page. /// [DefaultValue(0)] [Category("Layout")] public int FixedRows { get { int value = fixedRows; if (value >= Rows.Count) value = Rows.Count - 1; if (value < 0) value = 0; return value; } set { fixedRows = value; } } /// /// Gets or sets the number of fixed columns that will be repeated on each page. /// [DefaultValue(0)] [Category("Layout")] public int FixedColumns { get { int value = fixedColumns; if (value >= Columns.Count) value = Columns.Count - 1; if (value < 0) value = 0; return value; } set { fixedColumns = value; } } /// /// Gets or sets the value that determines whether to print the dynamic table (or matrix) on its parent band directly. /// /// /// By default the dynamic table (matrix) is printed on its own band and is splitted on pages if necessary. /// [DefaultValue(false)] [Category("Layout")] public bool PrintOnParent { get { return printOnParent; } set { printOnParent = value; } } /// /// Gets or sets a value that determines whether is necessary to repeat table header on each page. /// /// /// To define a table header, set the and /// properties. /// [DefaultValue(true)] [Category("Behavior")] public bool RepeatHeaders { get { return repeatHeaders; } set { repeatHeaders = value; } } /// /// Gets or sets a value that determines whether is necessary to repeat table Row header on each page. /// /// /// To define a table Row header, set the /// properties. /// [Browsable(false)] [DefaultValue(false)] [Category("Behavior")] public virtual bool RepeatRowHeaders { get { return repeatRowHeaders; } set { repeatRowHeaders = value; } } /// /// Gets or sets a value that determines whether is necessary to repeat table Column header on each page. /// /// /// To define a table Column header, set the /// properties. /// [Browsable(false)] [DefaultValue(false)] [Category("Behavior")] public virtual bool RepeatColumnHeaders { get { return repeatColumnHeaders; } set { repeatColumnHeaders = value; } } /// /// Gets or sets the table layout. /// /// /// This property affects printing the big table that breaks across pages. /// [DefaultValue(TableLayout.AcrossThenDown)] [Category("Behavior")] public TableLayout Layout { get { return layout; } set { layout = value; } } /// /// Gets or sets gap between parts of the table in wrapped layout mode. /// /// /// This property is used if you set the property to Wrapped. /// [DefaultValue(0f)] [Category("Behavior")] [TypeConverter("FastReport.TypeConverters.UnitsConverter, FastReport")] public float WrappedGap { get { return wrappedGap; } set { wrappedGap = value; } } /// /// Gets or sets a value that determines whether to adjust the spanned cell's width when breaking the table across pages. /// /// /// If set to true, the spanned cell's width will be adjusted to accomodate all contained text. /// [DefaultValue(false)] [Category("Behavior")] public bool AdjustSpannedCellsWidth { get { return adjustSpannedCellsWidth; } set { adjustSpannedCellsWidth = value; } } /// /// Gets or sets the table cell. /// /// Column index. /// Row index. /// The TableCell object that represents a cell. [Browsable(false)] public TableCell this[int col, int row] { get { if (col < 0 || col >= columns.Count || row < 0 || row >= rows.Count) return null; return rows[row][col]; } set { if (col < 0 || col >= columns.Count || row < 0 || row >= rows.Count) return; rows[row][col] = value; } } /// /// Gets or sets a number of columns in the table. /// [Category("Appearance")] public virtual int ColumnCount { get { return Columns.Count; } set { int n = value - Columns.Count; for (int i = 0; i < n; i++) { TableColumn column = new TableColumn(); Columns.Add(column); } while (value < Columns.Count) Columns.RemoveAt(Columns.Count - 1); } } /// /// Gets or sets a number of rows in the table. /// [Category("Appearance")] public virtual int RowCount { get { return Rows.Count; } set { int n = value - Rows.Count; for (int i = 0; i < n; i++) { TableRow row = new TableRow(); Rows.Add(row); } while (value < Rows.Count) Rows.RemoveAt(Rows.Count - 1); } } internal bool IsResultTable { get { return this is TableResult; } } /// /// Gets a table which contains the result of rendering dynamic table. /// /// /// Use this property to access the result of rendering your table in dynamic mode. /// It may be useful if you want to center or right-align the result table on a page. /// In this case, you need to add the following code at the end of your ManualBuild event handler: /// /// // right-align the table /// Table1.ResultTable.Left = Engine.PageWidth - Table1.ResultTable.CalcWidth() - 1; /// /// [Browsable(false)] public TableResult ResultTable { get { return resultTable; } } internal TableCellData PrintingCell { get { return printingCell; } set { printingCell = value; } } internal bool LockCorrectSpans { get { return lockCorrectSpans; } set { lockCorrectSpans = value; } } #endregion #region Private Methods private delegate void DrawCellProc(FRPaintEventArgs e, TableCell cell); private void DrawCells(FRPaintEventArgs e, DrawCellProc proc) { float top = 0; for (int y = 0; y < Rows.Count; y++) { float left = 0; float height = Rows[y].Height; for (int x = 0; x < Columns.Count; x++) { TableCell cell = this[x, y]; float width = Columns[x].Width; cell.Left = left; cell.Top = top; if (!IsInsideSpan(cell) && (!IsPrinting || cell.Printable)) { cell.SetPrinting(IsPrinting); #if !MONO || (WPF || AVALONIA) if (cell.IsVisible(e)) #endif proc(e, cell); } left += width; } top += height; } } private void DrawCellsRtl(FRPaintEventArgs e, DrawCellProc proc) { float top = 0; for (int y = 0; y < Rows.Count; y++) { float left = 0; float height = Rows[y].Height; //bool thereIsColSpan = false; //for (int i = Columns.Count - 1; i >= 0; i--) //{ // TableCell cell = this[i, y]; // if (cell.ColSpan > 1) // { // thereIsColSpan = true; // } //} for (int x = Columns.Count - 1; x >= 0; x--) { TableCell cell = this[x, y]; bool thereIsColSpan = false; if (cell.ColSpan > 1) { thereIsColSpan = true; } float width = Columns[x].Width; //if (thereIsColSpan) //{ // width *= cell.ColSpan - 1; // left -= width; //} if (!IsInsideSpan(cell) && (!IsPrinting || cell.Printable)) { cell.Left = left; cell.Top = top; cell.SetPrinting(IsPrinting); proc(e, cell); if (thereIsColSpan) width *= cell.ColSpan; left += width; } //if (!thereIsColSpan) // left += width; //else // left -= width; } top += height; } } private void DrawFill(FRPaintEventArgs e, TableCell cell) { cell.DrawBackground(e); } private void DrawText(FRPaintEventArgs e, TableCell cell) { cell.DrawText(e); } private void DrawBorders(FRPaintEventArgs e, TableCell cell) { cell.Border.Draw(e, cell.AbsBounds); } private void DrawTable(FRPaintEventArgs e) { DrawCells(e, DrawFill); DrawCells(e, DrawText); DrawDesign_Borders(e); DrawCells(e, DrawBorders); DrawDesign_SelectedCells(e); } private void DrawTableRtl(FRPaintEventArgs e) { DrawCellsRtl(e, DrawFill); DrawCellsRtl(e, DrawText); DrawDesign_BordersRtl(e); DrawCellsRtl(e, DrawBorders); DrawDesign_SelectedCellsRtl(e); } #endregion #region Public Methods /// public override void Assign(Base source) { base.Assign(source); TableBase src = source as TableBase; FixedRows = src.FixedRows; FixedColumns = src.FixedColumns; PrintOnParent = src.PrintOnParent; RepeatHeaders = src.RepeatHeaders; RepeatRowHeaders = src.RepeatRowHeaders; RepeatColumnHeaders = src.RepeatColumnHeaders; Layout = src.Layout; WrappedGap = src.WrappedGap; AdjustSpannedCellsWidth = src.AdjustSpannedCellsWidth; } /// public override void Draw(FRPaintEventArgs e) { if (ColumnCount == 0 || RowCount == 0) return; lockColumnRowChange = true; Width = Columns[Columns.Count - 1].Right; Height = Rows[Rows.Count - 1].Bottom; lockColumnRowChange = false; base.Draw(e); // draw table Right to Left if needed if (Config.RightToLeft) { DrawTableRtl(e); } else { DrawTable(e); } Border.Draw(e, new RectangleF(AbsLeft, AbsTop, Width, Height)); DrawDesign(e); } /// public override bool IsVisible(FRPaintEventArgs e) { if (RowCount == 0 || ColumnCount == 0) return false; Width = Columns[Columns.Count - 1].Right; Height = Rows[Rows.Count - 1].Bottom; RectangleF objRect = new RectangleF(AbsLeft * e.ScaleX, AbsTop * e.ScaleY, Width * e.ScaleX + 1, Height * e.ScaleY + 1); return e.Graphics.IsVisible(objRect); } internal void SetResultTable(TableResult table) { resultTable = table; } /// /// Gets data of the table cell with specified column and row numbers. /// /// The column number. /// The row number. /// TableCellData instance containing data of the table cell. public TableCellData GetCellData(int col, int row) { if (col < 0 || col >= columns.Count || row < 0 || row >= rows.Count) return null; return rows[row].CellData(col); } internal List GetSpanList() { if (spanList == null) { spanList = new List(); for (int y = 0; y < Rows.Count; y++) { for (int x = 0; x < Columns.Count; x++) { TableCellData cell = GetCellData(x, y); if (cell.ColSpan > 1 || cell.RowSpan > 1) spanList.Add(new Rectangle(x, y, cell.ColSpan, cell.RowSpan)); } } } return spanList; } internal void ResetSpanList() { spanList = null; } internal void CorrectSpansOnRowChange(int rowIndex, int correct) { if (lockCorrectSpans || (correct == 1 && rowIndex >= Rows.Count)) return; for (int y = 0; y < rowIndex; y++) { for (int x = 0; x < Columns.Count; x++) { TableCellData cell = GetCellData(x, y); if (rowIndex < y + cell.RowSpan) cell.RowSpan += correct; } } ResetSpanList(); } internal void CorrectSpansOnColumnChange(int columnIndex, int correct) { if (lockCorrectSpans || (correct == 1 && columnIndex >= Columns.Count)) return; for (int y = 0; y < Rows.Count; y++) { for (int x = 0; x < columnIndex; x++) { TableCellData cell = GetCellData(x, y); if (columnIndex < x + cell.ColSpan) cell.ColSpan += correct; } // correct cells Rows[y].CorrectCellsOnColumnChange(columnIndex, correct); } ResetSpanList(); } public bool IsInsideSpan(TableCell cell) { Point address = cell.Address; List spans = GetSpanList(); foreach (Rectangle span in spans) { if (span.Contains(address) && span.Location != address) return true; } return false; } /// /// Creates unique names for all table elements such as rows, columns, cells. /// public void CreateUniqueNames() { if (Report == null) return; FastNameCreator nameCreator = new FastNameCreator(Report.AllNamedObjects); foreach (TableRow row in Rows) { if (String.IsNullOrEmpty(row.Name)) nameCreator.CreateUniqueName(row); } foreach (TableColumn column in Columns) { if (String.IsNullOrEmpty(column.Name)) nameCreator.CreateUniqueName(column); } for (int y = 0; y < Rows.Count; y++) { for (int x = 0; x < Columns.Count; x++) { TableCell cell = this[x, y]; if (String.IsNullOrEmpty(cell.Name)) { nameCreator.CreateUniqueName(cell); cell.Font = DrawUtils.DefaultReportFont; } if (cell.Objects != null) { foreach (ReportComponentBase obj in cell.Objects) { if (String.IsNullOrEmpty(obj.Name)) nameCreator.CreateUniqueName(obj); } } } } } /// public override void Serialize(FRWriter writer) { TableBase c = writer.DiffObject as TableBase; serializingToPreview = writer.SerializeTo == SerializeTo.Preview; base.Serialize(writer); if (FixedRows != c.FixedRows) writer.WriteInt("FixedRows", FixedRows); if (FixedColumns != c.FixedColumns) writer.WriteInt("FixedColumns", FixedColumns); if (PrintOnParent != c.PrintOnParent) writer.WriteBool("PrintOnParent", PrintOnParent); if (RepeatHeaders != c.RepeatHeaders) writer.WriteBool("RepeatHeaders", RepeatHeaders); if (RepeatRowHeaders != c.RepeatRowHeaders) writer.WriteBool("RepeatRowHeaders", RepeatRowHeaders); if (RepeatColumnHeaders != c.RepeatColumnHeaders) writer.WriteBool("RepeatColumnHeaders", RepeatColumnHeaders); if (Layout != c.Layout) writer.WriteValue("Layout", Layout); if (WrappedGap != c.WrappedGap) writer.WriteFloat("WrappedGap", WrappedGap); if (AdjustSpannedCellsWidth != c.AdjustSpannedCellsWidth) writer.WriteBool("AdjustSpannedCellsWidth", AdjustSpannedCellsWidth); } internal void EmulateOuterBorder() { for (int y = 0; y < RowCount; y++) { for (int x = 0; x < ColumnCount; x++) { TableCell cell = this[x, y]; if (x == 0 && (Border.Lines & BorderLines.Left) != 0) { cell.Border.LeftLine.Assign(Border.LeftLine); cell.Border.Lines |= BorderLines.Left; } if (x + cell.ColSpan == ColumnCount && (Border.Lines & BorderLines.Right) != 0) { cell.Border.RightLine.Assign(Border.RightLine); cell.Border.Lines |= BorderLines.Right; } if (y == 0 && (Border.Lines & BorderLines.Top) != 0) { cell.Border.TopLine.Assign(Border.TopLine); cell.Border.Lines |= BorderLines.Top; } if (y + cell.RowSpan == RowCount && (Border.Lines & BorderLines.Bottom) != 0) { cell.Border.BottomLine.Assign(Border.BottomLine); cell.Border.Lines |= BorderLines.Bottom; } } } } #endregion #region IParent Members /// public bool CanContain(Base child) { return child is TableRow || child is TableColumn || child is TableCell; } /// public virtual void GetChildObjects(ObjectCollection list) { foreach (TableColumn column in Columns) { // Apply visible expression if needed. if (serializingToPreview && !String.IsNullOrEmpty(column.VisibleExpression)) column.Visible = CalcVisibleExpression(column.VisibleExpression); if (!serializingToPreview || column.Visible) list.Add(column); } foreach (TableRow row in Rows) { // Apply visible expression if needed. if (serializingToPreview && !String.IsNullOrEmpty(row.VisibleExpression)) row.Visible = CalcVisibleExpression(row.VisibleExpression); if (!serializingToPreview || row.Visible) list.Add(row); } } /// public void AddChild(Base child) { if (child is TableRow) Rows.Add(child as TableRow); else if (child is TableColumn) Columns.Add(child as TableColumn); } /// public void RemoveChild(Base child) { if (child is TableRow) Rows.Remove(child as TableRow); else if (child is TableColumn) Columns.Remove(child as TableColumn); } /// public int GetChildOrder(Base child) { if (child is TableColumn) return Columns.IndexOf(child as TableColumn); else if (child is TableRow) return Rows.IndexOf(child as TableRow); return 0; } /// public void SetChildOrder(Base child, int order) { lockCorrectSpans = true; int oldOrder = child.ZOrder; if (oldOrder != -1 && order != -1 && oldOrder != order) { if (child is TableColumn) { if (order > Columns.Count) order = Columns.Count; if (oldOrder <= order) order--; Columns.Remove(child as TableColumn); Columns.Insert(order, child as TableColumn); } else if (child is TableRow) { if (order > Rows.Count) order = Rows.Count; if (oldOrder <= order) order--; Rows.Remove(child as TableRow); Rows.Insert(order, child as TableRow); } } lockCorrectSpans = false; } /// public void UpdateLayout(float dx, float dy) { } #endregion #region Report Engine /// public override void SaveState() { base.SaveState(); foreach (TableRow row in Rows) { row.SaveState(); } foreach (TableColumn column in Columns) { column.SaveState(); } for (int y = 0; y < Rows.Count; y++) { for (int x = 0; x < Columns.Count; x++) { this[x, y].SaveState(); } } CanGrow = true; CanShrink = true; } /// public override void RestoreState() { base.RestoreState(); foreach (TableRow row in Rows) { row.RestoreState(); } foreach (TableColumn column in Columns) { column.RestoreState(); } for (int y = 0; y < Rows.Count; y++) { for (int x = 0; x < Columns.Count; x++) { this[x, y].RestoreState(); } } } /// /// Calculates and returns the table width, in pixels. /// public float CalcWidth() { // first pass, calc non-spanned cells for (int x = 0; x < Columns.Count; x++) { TableColumn column = Columns[x]; if (!column.AutoSize) continue; float columnWidth = IsDesigning ? 16 : -1; // calc the max column width for (int y = 0; y < Rows.Count; y++) { TableCellData cell = GetCellData(x, y); if (cell.ColSpan == 1) { float cellWidth = cell.CalcWidth(); if (cellWidth > columnWidth) columnWidth = cellWidth; } } // update column width if (columnWidth != -1) column.Width = columnWidth; } // second pass, calc spanned cells for (int x = 0; x < Columns.Count; x++) { Columns[x].MinimumBreakWidth = 0; for (int y = 0; y < Rows.Count; y++) { TableCellData cell = GetCellData(x, y); if (cell.ColSpan > 1) { float cellWidth = cell.CalcWidth(); if (AdjustSpannedCellsWidth && cellWidth > Columns[x].MinimumBreakWidth) Columns[x].MinimumBreakWidth = cellWidth; // check that spanned columns have enough width float columnsWidth = 0; for (int i = 0; i < cell.ColSpan; i++) { columnsWidth += Columns[Math.Min(Columns.Count-1,x + i)].Width; } // if cell is bigger than sum of column width, increase the last column width TableColumn lastColumn = Columns[Math.Min(Columns.Count-1,x + cell.ColSpan - 1)]; if (columnsWidth < cellWidth && lastColumn.AutoSize) lastColumn.Width += cellWidth - columnsWidth; } } } // finally, calculate the table width float width = 0; for (int i = 0; i < Columns.Count; i++) { width += Columns[i].Width; } lockColumnRowChange = true; Width = width; lockColumnRowChange = false; return width; } /// public override float CalcHeight() { if (ColumnCount * RowCount > 1000) Config.ReportSettings.OnProgress(Report, Res.Get("ComponentsMisc,Table,CalcBounds"), 0, 0); // calc width CalcWidth(); // first pass, calc non-spanned cells for (int y = 0; y < Rows.Count; y++) { TableRow row = Rows[y]; if (!row.AutoSize) continue; float rowHeight = IsDesigning ? 16 : -1; // calc the max row height for (int x = 0; x < Columns.Count; x++) { TableCellData cell = GetCellData(x, y); if (cell.RowSpan == 1) { float cellHeight = cell.CalcHeight(cell.Width); if (cellHeight > rowHeight) rowHeight = cellHeight; } } // update row height if (rowHeight != -1) row.Height = rowHeight; } // second pass, calc spanned cells for (int y = 0; y < Rows.Count; y++) { for (int x = 0; x < Columns.Count; x++) { TableCellData cell = GetCellData(x, y); if (cell.RowSpan > 1) { float cellHeight = cell.CalcHeight(cell.Width); // check that spanned rows have enough height float rowsHeight = 0; for (int i = 0; i < cell.RowSpan; i++) { if (y + i < Rows.Count) rowsHeight += Rows[y + i].Height; else { // Error, we don't have row, rowSpan has incorrect cell.RowSpan--; } } // if cell is bigger than sum of row heights, increase the last row height if (y + cell.RowSpan - 1 < Rows.Count) { TableRow lastRow = Rows[y + cell.RowSpan - 1]; if (rowsHeight < cellHeight && lastRow.AutoSize) lastRow.Height += cellHeight - rowsHeight; } } } } // finally, calculate the table height float height = 0; for (int i = 0; i < Rows.Count; i++) { height += Rows[i].Visible ? Rows[i].Height : 0; } return height; } private bool CanBreakRow(int rowIndex, float rowHeight) { if (!Rows[rowIndex].CanBreak) return false; // check each cell in the row for (int i = 0; i < ColumnCount; i++) { TableCell breakable = this[i, rowIndex]; // use clone object because Break method will modify the Text property using (TableCell clone = new TableCell()) { clone.AssignAll(breakable); clone.Height = rowHeight; clone.SetReport(Report); if (!clone.Break(null)) return false; } } return true; } private void BreakRow(TableBase breakTo, int rowIndex, float rowHeight, float newRowHeight) { // set rows height TableRow rowTo = breakTo.Rows[rowIndex]; Rows[rowIndex].Height = rowHeight; rowTo.Height = newRowHeight; // break each cell in the row for (int i = 0; i < ColumnCount; i++) { TableCell cell = this[i, rowIndex]; TableCell cellTo = breakTo[i, rowIndex]; cell.Height = rowHeight; cell.Break(cellTo); // fix height if row is not autosized if (!rowTo.AutoSize) { float h = cellTo.CalcHeight(); if (h > rowTo.Height) rowTo.Height = h; } } } /// public override bool Break(BreakableComponent breakTo) { if (Rows.Count == 0) return true; if (Height < Rows[0].Height && !Rows[0].CanBreak) return false; TableBase tableTo = breakTo as TableBase; if (tableTo == null) return true; // find the break row index int breakRowIndex = 0; int breakRowIndexAdd = 0; bool rowBroken = false; float rowsHeight = 0; while (breakRowIndex < Rows.Count) { rowsHeight += Rows[breakRowIndex].Height; if (rowsHeight > Height) { float breakRowHeight = Rows[breakRowIndex].Height - (rowsHeight - Height); if (CanBreakRow(breakRowIndex, breakRowHeight)) { BreakRow(tableTo, breakRowIndex, breakRowHeight, rowsHeight - Height); breakRowIndexAdd = 1; rowBroken = true; } break; } breakRowIndex++; } // get the span list List spans = GetSpanList(); // break the spans foreach (Rectangle span in spans) { if (span.Top < breakRowIndex + breakRowIndexAdd && span.Bottom > breakRowIndex) { TableCell cell = this[span.Left, span.Top]; TableCell cellTo = tableTo[span.Left, span.Top]; // update cell spans cell.RowSpan = breakRowIndex + breakRowIndexAdd - span.Top; cellTo.RowSpan = span.Bottom - breakRowIndex; // break the cell if (!rowBroken && !cell.Break(cellTo)) cell.Text = ""; // set the top span cell to the correct place tableTo[span.Left, span.Top] = new TableCell(); tableTo[span.Left, breakRowIndex] = cellTo; } } // remove unused rows from source (this table) while (breakRowIndex + breakRowIndexAdd < Rows.Count) { this.Rows.RemoveAt(Rows.Count - 1); } // remove unused rows from copy (tableTo) for (int i = 0; i < breakRowIndex; i++) { tableTo.Rows.RemoveAt(0); } return true; } private List GetAggregateCells(TableCell aggregateCell) { List list = new List(); // columnIndex, rowIndex is a place where we will print a result. // To collect aggregate values that will be used to calculate a result, we need to go // to the left and top from this point and collect every cell which OriginalCell is equal to // the aggregateCell value. We have to stop when we meet the same row or column. int columnIndex = PrintingCell.Address.X; int rowIndex = PrintingCell.Address.Y; TableColumn startColumn = ResultTable.Columns[columnIndex]; TableRow startRow = ResultTable.Rows[rowIndex]; TableColumn aggregateColumn = Columns[aggregateCell.Address.X]; TableRow aggregateRow = Rows[aggregateCell.Address.Y]; // check if result is in the same row/column as aggregate cell bool sameRow = startRow.OriginalComponent == aggregateRow.OriginalComponent; bool sameColumn = startColumn.OriginalComponent == aggregateColumn.OriginalComponent; for (int y = rowIndex; y >= 0; y--) { if (y != rowIndex && ResultTable.Rows[y].OriginalComponent == startRow.OriginalComponent) break; for (int x = columnIndex; x >= 0; x--) { if (x != columnIndex && ResultTable.Columns[x].OriginalComponent == startColumn.OriginalComponent) break; TableCellData cell = ResultTable.GetCellData(x, y); if (cell.OriginalCell == aggregateCell) list.Add(cell); if (sameColumn) break; } if (sameRow) break; } return list; } /// /// Calculates a sum of values in a specified cell. /// /// The cell. /// The object that contains calculated value. /// /// This method can be called from the ManualBuild event handler only. /// public object Sum(TableCell aggregateCell) { List list = GetAggregateCells(aggregateCell); Variant result = 0; bool firstTime = true; foreach (TableCellData cell in list) { if (cell.Value != null) { Variant varValue = new Variant(cell.Value); if (firstTime) result = varValue; else result += varValue; firstTime = false; } } return result.Value; } /// /// Calculates a minimum of values in a specified cell. /// /// The cell. /// The object that contains calculated value. /// /// This method can be called from the ManualBuild event handler only. /// public object Min(TableCell aggregateCell) { List list = GetAggregateCells(aggregateCell); Variant result = float.PositiveInfinity; bool firstTime = true; foreach (TableCellData cell in list) { if (cell.Value != null) { Variant varValue = new Variant(cell.Value); if (firstTime || varValue < result) result = varValue; firstTime = false; } } return result.Value; } /// /// Calculates a maximum of values in a specified cell. /// /// The cell. /// The object that contains calculated value. /// /// This method can be called from the ManualBuild event handler only. /// public object Max(TableCell aggregateCell) { List list = GetAggregateCells(aggregateCell); Variant result = float.NegativeInfinity; bool firstTime = true; foreach (TableCellData cell in list) { if (cell.Value != null) { Variant varValue = new Variant(cell.Value); if (firstTime || varValue > result) result = varValue; firstTime = false; } } return result.Value; } /// /// Calculates an average of values in a specified cell. /// /// The cell. /// The object that contains calculated value. /// /// This method can be called from the ManualBuild event handler only. /// public object Avg(TableCell aggregateCell) { List list = GetAggregateCells(aggregateCell); Variant result = 0; int count = 0; bool firstTime = true; foreach (TableCellData cell in list) { if (cell.Value != null) { Variant varValue = new Variant(cell.Value); if (firstTime) result = varValue; else result += varValue; count++; firstTime = false; } } return result / (count == 0 ? 1 : count); } /// /// Calculates number of repeats of a specified cell. /// /// The cell. /// The object that contains calculated value. /// /// This method can be called from the ManualBuild event handler only. /// public object Count(TableCell aggregateCell) { List list = GetAggregateCells(aggregateCell); int count = 0; foreach (TableCellData cell in list) { if (cell.Value != null) count++; } return count; } #endregion /// /// Initializes a new instance of the class. /// public TableBase() { rows = new TableRowCollection(this); columns = new TableColumnCollection(this); styles = new TableStyleCollection(); repeatHeaders = true; repeatRowHeaders = false; repeatColumnHeaders = false; CanGrow = true; CanShrink = true; } } }