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; } } }