using System;
using System.Collections.Generic;
using System.ComponentModel;
using FastReport.Table;
using FastReport.Data;
using FastReport.Utils;
using System.Drawing.Design;
namespace FastReport.Matrix
{
///
/// Describes how the even style is applied to a matrix.
///
public enum MatrixEvenStylePriority
{
///
/// The even style is applied to matrix rows.
///
Rows,
///
/// The even style is applied to matrix columns.
///
Columns
}
///
/// Represents the matrix object that is used to print pivot table (also known as cross-tab).
///
///
/// The matrix consists of the following elements: columns, rows and data cells. Each element is
/// represented by the descriptor. The class is used
/// for columns and rows; the is used for data cells.
/// The property holds three collections of descriptors - Columns,
/// Rows and Cells.
/// To create the matrix in a code, you should perform the following actions:
///
/// -
/// create an instance of the MatrixObject and add it to the report;
///
/// -
/// create descriptors for columns, rows and cells and add it to the
/// collections inside the property;
///
/// -
/// call the method to create the matrix template
/// that will be used to create a result;
///
/// -
/// modify the matrix template (change captions, set the visual appearance).
///
///
/// To connect the matrix to a datasource, use the property. If
/// this property is not set, the result matrix will be empty. In this case you may use
/// the event handler to fill the matrix.
///
/// This example demonstrates how to create a matrix in a code.
///
/// // create an instance of MatrixObject
/// MatrixObject matrix = new MatrixObject();
/// matrix.Name = "Matrix1";
/// // add it to the report title band of the first report page
/// matrix.Parent = (report.Pages[0] as ReportPage).ReportTitle;
///
/// // create two column descriptors
/// MatrixHeaderDescriptor column = new MatrixHeaderDescriptor("[MatrixDemo.Year]");
/// matrix.Data.Columns.Add(column);
/// column = new MatrixHeaderDescriptor("[MatrixDemo.Month]");
/// matrix.Data.Columns.Add(column);
///
/// // create one row descriptor
/// MatrixHeaderDescriptor row = new MatrixHeaderDescriptor("[MatrixDemo.Name]");
/// matrix.Data.Rows.Add(row);
///
/// // create one data cell
/// MatrixCellDescriptor cell = new MatrixCellDescriptor("[MatrixDemo.Revenue]", MatrixAggregateFunction.Sum);
/// matrix.Data.Cells.Add(cell);
///
/// // connect matrix to a datasource
/// matrix.DataSource = Report.GetDataSource("MatrixDemo");
///
/// // create the matrix template
/// matrix.BuildTemplate();
///
/// // change the style
/// matrix.Style = "Green";
///
/// // change the column and row total's text to "Grand Total"
/// matrix.Data.Columns[0].TemplateTotalCell.Text = "Grand Total";
/// matrix.Data.Rows[0].TemplateTotalCell.Text = "Grand Total";
///
///
public partial class MatrixObject : TableBase
{
#region Fields
private bool autoSize;
private bool cellsSideBySide;
private bool keepCellsSideBySide;
private DataSourceBase dataSource;
private string filter;
private bool showTitle;
private string style;
private MatrixData data;
private string manualBuildEvent;
private string modifyResultEvent;
private string afterTotalsEvent;
private MatrixHelper helper;
private bool saveVisible;
private MatrixStyleSheet styleSheet;
private object[] columnValues;
private object[] rowValues;
private int columnIndex;
private int rowIndex;
private MatrixEvenStylePriority matrixEvenStylePriority;
private bool splitRows;
private bool printIfEmpty;
#endregion
#region Properties
///
/// Allows to fill the matrix in code.
///
///
/// In most cases the matrix is connected to a datasource via the
/// property. When you run a report, the matrix is filled with datasource values automatically.
/// Using this event, you can put additional values to the matrix or even completely fill it
/// with own values (if is set to null. To do this, call the
/// Data.AddValue method. See the
/// method for more details.
///
/// This example shows how to fill a matrix with own values.
///
/// // suppose we have a matrix with one column, row and data cell.
/// // provide 3 one-dimensional arrays with one element in each to the AddValue method
/// Matrix1.Data.AddValue(
/// new object[] { 1996 },
/// new object[] { "Andrew Fuller" },
/// new object[] { 123.45f });
/// Matrix1.Data.AddValue(
/// new object[] { 1997 },
/// new object[] { "Andrew Fuller" },
/// new object[] { 21.35f });
/// Matrix1.Data.AddValue(
/// new object[] { 1997 },
/// new object[] { "Nancy Davolio" },
/// new object[] { 421.5f });
///
/// // this code will produce the following matrix:
/// // | 1996 | 1997 |
/// // --------------+--------+--------+
/// // Andrew Fuller | 123.45| 21.35|
/// // --------------+--------+--------+
/// // Nancy Davolio | | 421.50|
/// // --------------+--------+--------+
///
///
public event EventHandler ManualBuild;
///
/// Allows to modify the prepared matrix elements such as cells, rows, columns.
///
public event EventHandler ModifyResult;
///
/// Allows to modify the prepared matrix elements such as cells, rows, columns.
///
public event EventHandler AfterTotals;
///
/// Gets or sets a value that determines whether the matrix must calculate column/row sizes automatically.
///
[DefaultValue(true)]
[Category("Behavior")]
public bool AutoSize
{
get { return autoSize; }
set
{
autoSize = value;
foreach (TableColumn column in Columns)
{
column.AutoSize = AutoSize;
}
foreach (TableRow row in Rows)
{
row.AutoSize = AutoSize;
}
}
}
///
/// Gets or sets a value that determines how to print multiple data cells.
///
///
/// This property can be used if matrix has two or more data cells. Default property value
/// is false - that means the data cells will be stacked.
///
[DefaultValue(false)]
[Category("Behavior")]
public bool CellsSideBySide
{
get { return cellsSideBySide; }
set
{
if (cellsSideBySide != value)
{
cellsSideBySide = value;
if (IsDesigning)
{
foreach (MatrixCellDescriptor descr in Data.Cells)
{
descr.TemplateColumn = null;
descr.TemplateRow = null;
}
BuildTemplate();
}
}
}
}
///
/// Gets or sets a value indicating that the side-by-side cells must be kept together on the same page.
///
[DefaultValue(false)]
[Category("Behavior")]
public bool KeepCellsSideBySide
{
get { return keepCellsSideBySide; }
set { keepCellsSideBySide = value; }
}
///
/// Gets or sets a data source.
///
///
/// When you create the matrix in the designer by drag-drop data columns into it,
/// this property will be set automatically. However you need to set it if you create
/// the matrix in code.
///
[Category("Data")]
public DataSourceBase DataSource
{
get { return dataSource; }
set
{
if (dataSource != value)
{
if (dataSource != null)
dataSource.Disposed -= new EventHandler(DataSource_Disposed);
if (value != null)
value.Disposed += new EventHandler(DataSource_Disposed);
}
dataSource = value;
}
}
///
/// Gets the row filter expression.
///
///
/// This property can contain any valid boolean expression. If the expression returns false,
/// the corresponding data row will be skipped.
///
[Category("Data")]
[Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))]
public string Filter
{
get { return filter; }
set { filter = value; }
}
///
/// Gets or sets a value indicating whether to show a title row.
///
[DefaultValue(false)]
[Category("Behavior")]
public bool ShowTitle
{
get { return showTitle; }
set
{
showTitle = value;
if (IsDesigning)
BuildTemplate();
}
}
///
/// Gets or sets a matrix style.
///
[Category("Appearance")]
[Editor("FastReport.TypeEditors.MatrixStyleEditor, FastReport", typeof(UITypeEditor))]
public new string Style
{
get { return style; }
set
{
style = value;
Helper.UpdateStyle();
}
}
///
/// Gets or sets even style priority for matrix cells.
///
[Category("Behavior")]
[DefaultValue(MatrixEvenStylePriority.Rows)]
public MatrixEvenStylePriority MatrixEvenStylePriority
{
get { return matrixEvenStylePriority; }
set { matrixEvenStylePriority = value; }
}
///
/// Gets or sets need split rows.
///
[Category("Behavior")]
[DefaultValue(false)]
public bool SplitRows
{
get { return splitRows; }
set { splitRows = value; }
}
///
/// Gets or sets a value indicating that empty matrix should be printed.
///
[Category("Behavior")]
[DefaultValue(true)]
public bool PrintIfEmpty
{
get { return printIfEmpty; }
set { printIfEmpty = value; }
}
///
/// Gets or sets a script method name that will be used to handle the
/// event.
///
///
/// See the event for more details.
///
[Category("Build")]
public string ManualBuildEvent
{
get { return manualBuildEvent; }
set { manualBuildEvent = value; }
}
///
/// Gets or sets a script method name that will be used to handle the
/// event.
///
///
/// See the event for more details.
///
[Category("Build")]
public string ModifyResultEvent
{
get { return modifyResultEvent; }
set { modifyResultEvent = value; }
}
///
/// Gets or sets a script method name that will be used to handle the
/// event.
///
///
/// See the event for more details.
///
[Category("Build")]
public string AfterTotalsEvent
{
get { return afterTotalsEvent; }
set { afterTotalsEvent = value; }
}
///
/// Gets the object that holds the collection of descriptors used
/// to build a matrix.
///
///
/// See the class for more details.
///
[Browsable(false)]
public MatrixData Data
{
get { return data; }
}
///
/// Gets or sets array of values that describes the currently printing column.
///
///
/// Use this property when report is running. It can be used to highlight matrix elements
/// depending on values of the currently printing column. To do this:
///
/// -
/// select the cell that you need to highlight;
///
/// -
/// click the "Highlight" button on the "Text" toolbar;
///
/// -
/// add a new highlight condition. Use the Matrix.ColumnValues to
/// refer to the value you need to analyze. Note: these values are arrays of System.Object,
/// so you need to cast it to actual type before making any comparisons. Example of highlight
/// condition: (int)Matrix1.ColumnValues[0] == 2000.
///
///
///
///
[Browsable(false)]
public object[] ColumnValues
{
get { return columnValues; }
set { columnValues = value; }
}
///
/// Gets or sets array of values that describes the currently printing row.
///
///
/// Use this property when report is running. It can be used to highlight matrix elements
/// depending on values of the currently printing row. To do this:
///
/// -
/// select the cell that you need to highlight;
///
/// -
/// click the "Highlight" button on the "Text" toolbar;
///
/// -
/// add a new highlight condition. Use the Matrix.RowValues to
/// refer to the value you need to analyze. Note: these values are arrays of System.Object,
/// so you need to cast it to actual type before making any comparisons. Example of highlight
/// condition: (string)Matrix1.RowValues[0] == "Andrew Fuller".
///
///
///
///
[Browsable(false)]
public object[] RowValues
{
get { return rowValues; }
set { rowValues = value; }
}
///
/// Gets or sets the index of currently printing column.
///
///
/// This property may be used to print even columns with alternate color. To do this:
///
/// -
/// select the cell that you need to highlight;
///
/// -
/// click the "Highlight" button on the "Text" toolbar;
///
/// -
/// add a new highlight condition that uses the Matrix.ColumnIndex,
/// for example: Matrix1.ColumnIndex % 2 == 1.
///
///
///
///
[Browsable(false)]
public int ColumnIndex
{
get { return columnIndex; }
set { columnIndex = value; }
}
///
/// Gets or sets the index of currently printing row.
///
///
/// This property may be used to print even rows with alternate color. To do this:
///
/// -
/// select the cell that you need to highlight;
///
/// -
/// click the "Highlight" button on the "Text" toolbar;
///
/// -
/// add a new highlight condition that uses the Matrix.RowIndex,
/// for example: Matrix1.RowIndex % 2 == 1.
///
///
///
///
[Browsable(false)]
public int RowIndex
{
get { return rowIndex; }
set { rowIndex = value; }
}
internal MatrixStyleSheet StyleSheet
{
get { return styleSheet; }
}
private MatrixHelper Helper
{
get { return helper; }
}
internal bool IsResultMatrix
{
get { return !IsDesigning && Data.Columns.Count == 0 && Data.Rows.Count == 0; }
}
private BandBase ParentBand
{
get
{
BandBase parentBand = this.Band;
if (parentBand is ChildBand)
parentBand = (parentBand as ChildBand).GetTopParentBand;
return parentBand;
}
}
private DataBand FootersDataBand
{
get
{
DataBand dataBand = null;
if (ParentBand is GroupFooterBand)
dataBand = ((ParentBand as GroupFooterBand).Parent as GroupHeaderBand).GroupDataBand;
else if (ParentBand is DataFooterBand)
dataBand = ParentBand.Parent as DataBand;
return dataBand;
}
}
private bool IsOnFooter
{
get
{
DataBand dataBand = FootersDataBand;
if (dataBand != null)
{
return DataSource == dataBand.DataSource;
}
return false;
}
}
#endregion
#region Private Methods
private void CreateResultTable()
{
SetResultTable(new TableResult());
// assign properties from this object. Do not use Assign method: TableResult is incompatible with MatrixObject.
ResultTable.OriginalComponent = OriginalComponent;
ResultTable.Alias = Alias;
ResultTable.Border = Border.Clone();
ResultTable.Fill = Fill.Clone();
ResultTable.Bounds = Bounds;
ResultTable.PrintOnParent = PrintOnParent;
ResultTable.RepeatHeaders = RepeatHeaders;
ResultTable.RepeatRowHeaders = RepeatRowHeaders;
ResultTable.RepeatColumnHeaders = RepeatColumnHeaders;
ResultTable.Layout = Layout;
ResultTable.WrappedGap = WrappedGap;
ResultTable.AdjustSpannedCellsWidth = AdjustSpannedCellsWidth;
ResultTable.SetReport(Report);
ResultTable.AfterData += new EventHandler(ResultTable_AfterData);
}
private void DisposeResultTable()
{
ResultTable.Dispose();
SetResultTable(null);
}
private void ResultTable_AfterData(object sender, EventArgs e)
{
OnModifyResult(e);
}
private void DataSource_Disposed(object sender, EventArgs e)
{
dataSource = null;
}
private void WireEvents(bool wire)
{
if (IsOnFooter)
{
DataBand dataBand = FootersDataBand;
if (wire)
dataBand.BeforePrint += new EventHandler(dataBand_BeforePrint);
else
dataBand.BeforePrint -= new EventHandler(dataBand_BeforePrint);
}
}
private void dataBand_BeforePrint(object sender, EventArgs e)
{
bool firstRow = (sender as DataBand).IsFirstRow;
if (firstRow)
Helper.StartPrint();
object match = true;
if (!String.IsNullOrEmpty(Filter))
match = Report.Calc(Filter);
if (match is bool && (bool)match == true)
Helper.AddDataRow();
}
#endregion
#region Protected Methods
///
protected override void DeserializeSubItems(FRReader reader)
{
if (String.Compare(reader.ItemName, "MatrixColumns", true) == 0)
reader.Read(Data.Columns);
else if (String.Compare(reader.ItemName, "MatrixRows", true) == 0)
reader.Read(Data.Rows);
else if (String.Compare(reader.ItemName, "MatrixCells", true) == 0)
reader.Read(Data.Cells);
else
base.DeserializeSubItems(reader);
}
#endregion
#region Public Methods
///
public override void Assign(Base source)
{
base.Assign(source);
MatrixObject src = source as MatrixObject;
AutoSize = src.AutoSize;
CellsSideBySide = src.CellsSideBySide;
KeepCellsSideBySide = src.KeepCellsSideBySide;
DataSource = src.DataSource;
Filter = src.Filter;
ShowTitle = src.ShowTitle;
Style = src.Style;
MatrixEvenStylePriority = src.MatrixEvenStylePriority;
SplitRows = src.SplitRows;
PrintIfEmpty = src.PrintIfEmpty;
data = src.Data;
}
///
public override void Serialize(FRWriter writer)
{
if (writer.SerializeTo != SerializeTo.SourcePages)
{
writer.Write(Data.Columns);
writer.Write(Data.Rows);
writer.Write(Data.Cells);
}
else
RefreshTemplate(true);
base.Serialize(writer);
MatrixObject c = writer.DiffObject as MatrixObject;
if (AutoSize != c.AutoSize)
writer.WriteBool("AutoSize", AutoSize);
if (CellsSideBySide != c.CellsSideBySide)
writer.WriteBool("CellsSideBySide", CellsSideBySide);
if (KeepCellsSideBySide != c.KeepCellsSideBySide)
writer.WriteBool("KeepCellsSideBySide", KeepCellsSideBySide);
if (DataSource != c.DataSource)
writer.WriteRef("DataSource", DataSource);
if (Filter != c.Filter)
writer.WriteStr("Filter", Filter);
if (ShowTitle != c.ShowTitle)
writer.WriteBool("ShowTitle", ShowTitle);
if (Style != c.Style)
writer.WriteStr("Style", Style);
if (MatrixEvenStylePriority != c.MatrixEvenStylePriority)
writer.WriteValue("MatrixEvenStylePriority", MatrixEvenStylePriority);
if (SplitRows != c.SplitRows)
writer.WriteBool("SplitRows", SplitRows);
if (PrintIfEmpty != c.PrintIfEmpty)
writer.WriteBool("PrintIfEmpty", PrintIfEmpty);
if (ManualBuildEvent != c.ManualBuildEvent)
writer.WriteStr("ManualBuildEvent", ManualBuildEvent);
if (ModifyResultEvent != c.ModifyResultEvent)
writer.WriteStr("ModifyResultEvent", ModifyResultEvent);
if (AfterTotalsEvent != c.AfterTotalsEvent)
writer.WriteStr("AfterTotalsEvent", AfterTotalsEvent);
}
///
/// Creates or updates the matrix template.
///
///
/// Call this method after you modify the matrix descriptors using the
/// object's properties.
///
public void BuildTemplate()
{
Helper.BuildTemplate();
}
#endregion
#region Report Engine
///
public override void InitializeComponent()
{
base.InitializeComponent();
WireEvents(true);
}
///
public override void FinalizeComponent()
{
base.FinalizeComponent();
WireEvents(false);
}
///
public override string[] GetExpressions()
{
List expressions = new List();
expressions.AddRange(base.GetExpressions());
Helper.UpdateDescriptors();
List descrList = new List();
descrList.AddRange(Data.Columns.ToArray());
descrList.AddRange(Data.Rows.ToArray());
descrList.AddRange(Data.Cells.ToArray());
foreach (MatrixDescriptor descr in descrList)
{
expressions.Add(descr.Expression);
if (descr.TemplateCell != null)
descr.TemplateCell.AllowExpressions = false;
}
if (!String.IsNullOrEmpty(Filter))
expressions.Add(Filter);
return expressions.ToArray();
}
///
public override void SaveState()
{
saveVisible = Visible;
BandBase parent = Parent as BandBase;
if (!Visible || (parent != null && !parent.Visible))
return;
// create the result table that will be rendered in the preview
CreateResultTable();
Visible = false;
if (parent != null && !PrintOnParent)
{
parent.Height = Top;
parent.CanGrow = false;
parent.CanShrink = false;
parent.AfterPrint += ResultTable.GeneratePages;
}
}
///
public override void GetData()
{
base.GetData();
if (!IsOnFooter)
{
Helper.StartPrint();
Helper.AddDataRows();
}
}
///
public override void OnAfterData(EventArgs e)
{
base.OnAfterData(e);
Helper.FinishPrint();
if (PrintOnParent)
ResultTable.AddToParent(Parent);
}
///
public override void RestoreState()
{
BandBase parent = Parent as BandBase;
if (!saveVisible || (parent != null && !parent.Visible))
return;
if (parent != null && !PrintOnParent)
parent.AfterPrint -= ResultTable.GeneratePages;
DisposeResultTable();
Visible = saveVisible;
}
///
/// This method fires the ManualBuild event and the script code connected to the ManualBuildEvent.
///
/// Event data.
public void OnManualBuild(EventArgs e)
{
if (ManualBuild != null)
ManualBuild(this, e);
InvokeEvent(ManualBuildEvent, e);
}
///
/// This method fires the ModifyResult event and the script code connected to the ModifyResultEvent.
///
/// Event data.
public void OnModifyResult(EventArgs e)
{
if (ModifyResult != null)
ModifyResult(this, e);
InvokeEvent(ModifyResultEvent, e);
}
///
/// This method fires the AfterTotals event and the script code connected to the AfterTotalsEvent.
///
/// Event data.
public void OnAfterTotals(EventArgs e)
{
if (AfterTotals != null)
AfterTotals(this, e);
InvokeEvent(AfterTotalsEvent, e);
}
///
/// Adds a value in the matrix.
///
/// Array of column values.
/// Array of row values.
/// Array of data values.
///
/// This is a shortcut method to call the matrix Data.AddValue.
/// See the method for more details.
///
public void AddValue(object[] columnValues, object[] rowValues, object[] cellValues)
{
Data.AddValue(columnValues, rowValues, cellValues, 0);
}
///
/// Gets the value of the data cell with the specified index.
///
/// Zero-based index of the data cell.
/// The cell's value.
///
/// Use this method in the cell's expression if the cell has custom totals
/// (the total function is set to "Custom"). The example:
/// Matrix1.Value(0) / Matrix1.Value(1)
/// will return the result of dividing the first data cell's value by the second one.
///
public Variant Value(int index)
{
object value = Helper.CellValues[index];
if (value == null)
value = 0;
return new Variant(value);
}
#endregion
///
/// Initializes a new instance of the class.
///
public MatrixObject()
{
autoSize = true;
data = new MatrixData();
manualBuildEvent = "";
afterTotalsEvent = "";
helper = new MatrixHelper(this);
InitDesign();
styleSheet = new MatrixStyleSheet();
styleSheet.Load(ResourceLoader.GetStream("cross.frss"));
style = "";
filter = "";
splitRows = false;
printIfEmpty = true;
}
}
}