Here's my scenario. I'm currently working on incorporating a dynamic print feature for a Silverlight application and I'm using Telerik Reporting 2011 Q1 to do it.
The "pages" of the application which the user is to be able to print consist of charts, custom controls and datagrids. The way I send this to the report generator is by exporting the charts and usercontrols as byte arrays and the datagrids as csv and then store this data (including a GUID for the client) serverside. The GUID is sent as parameter to the report generator which fetches the data and re-creates the images and generates the table.
Now I'm doing this dynamically to be able to use the same report for all the pages and also because the datagrids are themselves dynamic and can consist of anything between 3-40 columns (selectable by the user).
One of the issue I've encountered is when I try to get a row grouping created for one of the tables and keep getting "Object reference not set to an instance of an object". The table works just fine if I stick to ColumnGroups and a detail RowGroup.
Here's the NeedDataSource code for the report
I do know there's some unnecessary size parameters in the code since the table is docked, I do on the other hand have a hard time getting the row-heights to act as I want to and do appreciate some pointers on this issue as well.
The the current version which I posted here I'm using a DataTable as source for the table. I've also tried converting the datatable into a collection of dynamic (IEnumerable<dynamic>, dynamic = ExpandoObject) without success.
An example on the CSV-string that is used as source:
Now I've tried creating the expected table through the designer and binding this to a mock ObjectDataSource which returns a datatable consisting of the same type of data and read throught the generated code in InitializeComponent, but I'm unable to get the same thing working dynamically.
I've attached screenshots of the table created through the designer and also screenshots from the client and generated reports (the ones that work).
Here's the code that I've used as DataSource while designing the table
Hopefully someone can help me out with these issues (both to get the table working and to get the row heights shrunk)
Best regards,
Tobias
The "pages" of the application which the user is to be able to print consist of charts, custom controls and datagrids. The way I send this to the report generator is by exporting the charts and usercontrols as byte arrays and the datagrids as csv and then store this data (including a GUID for the client) serverside. The GUID is sent as parameter to the report generator which fetches the data and re-creates the images and generates the table.
Now I'm doing this dynamically to be able to use the same report for all the pages and also because the datagrids are themselves dynamic and can consist of anything between 3-40 columns (selectable by the user).
One of the issue I've encountered is when I try to get a row grouping created for one of the tables and keep getting "Object reference not set to an instance of an object". The table works just fine if I stick to ColumnGroups and a detail RowGroup.
Here's the NeedDataSource code for the report
private void PrintReportNeedDataSource(object sender, EventArgs e){ if(((IList) ReportParameters["GUID"].Value).Count == 0) return; var guidAsString = ((IList) ReportParameters["GUID"].Value)[0].ToString(); var reportData = new ReportServices().FetchReportDataForGUID(guidAsString); var report = (Telerik.Reporting.Processing.Report) sender; report.DataSource = reportData; var availableWidth = report.PageSettings.PaperSize.Width - (report.PageSettings.Margins.Left + report.PageSettings.Margins.Right); // panel1 (used when only one type of chart is to be displayed) var panel1 = new Panel { Location = new PointU( new Unit(0, UnitType.Cm), new Unit(0, UnitType.Cm)), Size = new SizeU( availableWidth, new Unit(35, UnitType.Mm)), Dock = DockStyle.Top, KeepTogether = true }; // panel2 (used with panel1 when multiple chart types are to be displayed on top of each other var panel2 = new Panel { Location = new PointU( new Unit(0, UnitType.Cm), new Unit(0, UnitType.Cm)), Size = new SizeU( availableWidth, new Unit(40, UnitType.Mm)), Dock = DockStyle.Top, KeepTogether = true }; panel2.Style.Padding.Top = new Unit(10, UnitType.Mm); // Report title titleField.Value = reportData.ReportTitle; #region Create charts switch (reportData.ReportType) { case Enumerators.PrintReportType.Consumption: case Enumerators.PrintReportType.ComparedConsumption: case Enumerators.PrintReportType.Prognose: foreach (var chartData in reportData.Charts) { var chartImage = Image.FromStream(new MemoryStream(chartData.Data)); var perCentageToChange = panel1.Width / Unit.FromPixels(chartImage.Width, UnitType.Mm); var pBox = new PictureBox { Name = string.Format("CD{0}", reportData.Charts.IndexOf(chartData)), Location = new PointU(new Unit(0), new Unit(0)), Sizing = ImageSizeMode.ScaleProportional, Width = availableWidth, Height = Unit.FromPixels(chartImage.Height, UnitType.Mm) * perCentageToChange, Value = chartImage, Dock = DockStyle.Top }; panel1.Items.AddRange(new ReportItemBase[] {pBox}); } if (panel1.Items.Count != 0) detail.Items.AddRange(new ReportItemBase[] { panel1 }); break; case Enumerators.PrintReportType.StackedConsumption: foreach (var chartData in reportData.MiscPictures) { // Calculate how wide a single image can be to fill the width of the page var totalWidthInPx = reportData.MiscPictures.Sum(pic => pic.Width); var totalWidthInMm = Unit.FromPixels(totalWidthInPx, UnitType.Mm); var perCentageToChange = availableWidth.Value / totalWidthInMm.Value; var widthPerImage = totalWidthInPx / reportData.MiscPictures.Count * perCentageToChange; var chartImage = Image.FromStream(new MemoryStream(chartData.Data)); var pBox = new PictureBox { Name = string.Format("GCD{0}", reportData.MiscPictures.IndexOf(chartData)), Width = Unit.FromPixels(widthPerImage, UnitType.Mm), Value = chartImage, Sizing = ImageSizeMode.ScaleProportional, Dock = DockStyle.Left, Location = new PointU(new Unit(0), new Unit(0)) }; pBox.Style.Padding.Bottom = Unit.Mm(5); panel1.Items.AddRange(new ReportItemBase[] { pBox }); } if (panel1.Items.Count != 0) detail.Items.AddRange(new ReportItemBase[] { panel1 }); foreach (var chartData in reportData.Charts) { var chartImage = Image.FromStream(new MemoryStream(chartData.Data)); var chartWidth = Unit.FromPixels(chartImage.Width, UnitType.Mm); var perCentageChange = availableWidth / chartWidth; var pBox = new PictureBox { Name = string.Format("CD{0}", reportData.Charts.IndexOf(chartData)), Location = new PointU(new Unit(0), new Unit(0)), Sizing = ImageSizeMode.ScaleProportional, Width = availableWidth, Height = Unit.FromPixels(chartImage.Height, UnitType.Mm)*perCentageChange, Value = chartImage, Dock = DockStyle.Top }; panel2.Items.AddRange(new ReportItemBase[] { pBox }); } if(panel2.Items.Count != 0) detail.Items.AddRange(new ReportItemBase[] { panel2 }); // Stacked chart doesn't have any table data and can return return; default: throw new ArgumentOutOfRangeException(); } #endregion #region Create padding between charts and table var paddingPanel = new Panel { Size = new SizeU( availableWidth, Unit.Cm(1.5)), Dock = DockStyle.Top }; detail.Items.AddRange(new ReportItemBase[] { paddingPanel }); #endregion #region Create table data using (var parser = new GenericParserAdapter(new StringReader(reportData.TableDataCsv))) { parser.FirstRowHasHeader = true; reportData.DataTable = parser.GetDataTable(); } // Creating and configuring the ObjectDataSource component: var objectDataSource = new ObjectDataSource { Name = "TableDataSource", DataSource = reportData.DataTable }; //var tableDataAsCollectionOfDynamics = reportData.DataTable.ToDynamic(); report.DataSource = objectDataSource; var dataTable = new Table { Name = "DataTable", DataSource = objectDataSource, Location = new PointU(new Unit(0), new Unit(0)), Size = new SizeU( availableWidth, new Unit(40, UnitType.Mm)), Dock = DockStyle.Fill, ColumnHeadersPrintOnEveryPage = true }; detail.Items.Add(dataTable); //create two HtmlTextBox items (one for header and one for data) which would be added to the items collection of the table TextBox textBoxHeader; TextBox textBoxContent; var i = 0; dataTable.ColumnHeadersPrintOnEveryPage = true; dataTable.Body.Rows.Add(new TableBodyRow(new Unit(0.1, UnitType.Cm))); // Create Padding- and DetailGroup (RowGrops) var paddingGroup = new TableGroup { Name = "PaddingGroup" }; var detailGroup = new TableGroup { Name = "DetailGroup" }; detailGroup.Groupings.Add(new Grouping("")); #region If the reporttype is prognosis, create grouping by PrognoseType switch (reportData.ReportType) { case Enumerators.PrintReportType.Consumption: case Enumerators.PrintReportType.StackedConsumption: case Enumerators.PrintReportType.ComparedConsumption: dataTable.RowGroups.Add(detailGroup); break; case Enumerators.PrintReportType.Prognose: // Add an additional row to the body (padding) dataTable.Body.Rows.Add(new TableBodyRow(new Unit(0.1, UnitType.Cm))); var prognoseTypeHeader = new TextBox { Value = "Prognos", Name = "PrognosHeader", Size = new SizeU( Unit.Cm(0.1), Unit.Cm(0.1)), Multiline = true, TextWrap = true, CanGrow = false, CanShrink = true }; dataTable.Corner.SetCellContent(0, 0, prognoseTypeHeader); var prognoseTypeContent = new TextBox { Name = "PrognosContent", Multiline = false, CanGrow = false, CanShrink = true, Size = new SizeU( Unit.Cm(1.1), Unit.Mm(0)) }; prognoseTypeContent.Style.Padding.Top = Unit.Mm(0); prognoseTypeContent.Style.Padding.Bottom = Unit.Mm(0); prognoseTypeContent.Style.Color = Color.DimGray; prognoseTypeContent.Style.VerticalAlign = VerticalAlign.Middle; prognoseTypeContent.Style.Font.Size = Unit.Point(GetFontSize(reportData.DataTable.Columns.Count)); prognoseTypeContent.Style.BorderColor.Default = Color.AliceBlue; prognoseTypeContent.Style.BorderStyle.Default = BorderType.Solid; prognoseTypeContent.Style.BorderWidth.Default = Unit.Point(0.5); prognoseTypeContent.Value = "=Fields.Prognostyp"; // Add the header and content to the table dataTable.Items.AddRange( new ReportItemBase[] { prognoseTypeHeader, prognoseTypeContent }); // Create RowGroup to group by PrognoseType var rowGroupingGroup = new TableGroup { Name = "PrognoseGrouping" }; rowGroupingGroup.Groupings.Add(new Grouping("=Fields.Prognostyp")); rowGroupingGroup.Sortings.Add(new Sorting("=Fields.Prognostyp", SortDirection.Asc)); rowGroupingGroup.ChildGroups.Add(detailGroup); rowGroupingGroup.ChildGroups.Add(paddingGroup); rowGroupingGroup.ReportItem = prognoseTypeContent; dataTable.RowGroups.Add(rowGroupingGroup); break; default: throw new ArgumentOutOfRangeException(); } #endregion foreach (DataColumn dc in reportData.DataTable.Columns) { // The prognosetype column is already handled if (reportData.ReportType == Enumerators.PrintReportType.Prognose && dc.ColumnName == "Prognostyp") continue; // Encode the column name to make spacing work with the table dc.ColumnName = TelerikUtil.EncodeIdentifier(dc.ColumnName); // Create the colgroup var tableColumnGroup = new TableGroup { Name = string.Format("ColGroup{0}", reportData.DataTable.Columns.IndexOf(dc)) }; dataTable.ColumnGroups.Add(tableColumnGroup); dataTable.Body.Columns.Add( new TableBodyColumn(i == 0 ? Unit.Cm(0.5) : Unit.Cm(1))); #region Create header for the column textBoxHeader = new TextBox { Name = string.Format("Header{0}", dc.ColumnName), Value = TelerikUtil.DecodeIdentifier(dc.ColumnName), Size = new SizeU( Unit.Cm(0.1), Unit.Cm(0.1)), Multiline = true, TextWrap = true, CanGrow = false, CanShrink = true }; textBoxHeader.Style.VerticalAlign = VerticalAlign.Middle; textBoxHeader.Style.TextAlign = HorizontalAlign.Center; textBoxHeader.Style.Font.Size = Unit.Point(GetFontSize(reportData.DataTable.Columns.Count)); textBoxHeader.Style.Font.Bold = true; textBoxHeader.Style.BackgroundColor = Color.FromName("ControlLight"); textBoxHeader.Style.BorderColor.Default = Color.AliceBlue; textBoxHeader.Style.BorderStyle.Default = BorderType.Solid; textBoxHeader.Style.BorderWidth.Default = Unit.Point(0.5); // Bind the headern as ReportItem for the colGroup tableColumnGroup.ReportItem = textBoxHeader; #endregion #region Create content for the column textBoxContent = new TextBox { Name = string.Format("Content{0}", dc.ColumnName), Multiline = false, CanGrow = false, CanShrink = true, Size = new SizeU( Unit.Cm(1.1), Unit.Mm(0)) }; textBoxContent.Style.Padding.Top = Unit.Mm(0); textBoxContent.Style.Padding.Bottom = Unit.Mm(0); textBoxContent.Style.Color = Color.DimGray; textBoxContent.Style.VerticalAlign = VerticalAlign.Middle; textBoxContent.Style.Font.Size = Unit.Point(GetFontSize(reportData.DataTable.Columns.Count)); textBoxContent.Style.BorderColor.Default = Color.AliceBlue; textBoxContent.Style.BorderStyle.Default = BorderType.Solid; textBoxContent.Style.BorderWidth.Default = Unit.Point(0.5); #region Content value binding & formatting if (textBoxHeader.Value.Contains("Procent") || textBoxHeader.Value.Contains("%")) { var ltZeroFormat = new FormattingRule(); var gtZeroFormat = new FormattingRule(); ltZeroFormat.Filters.AddRange( new[] { new Filter( string.Format("=CDbl(Substr(Fields.{0}, 0, Len(Fields.{0})-1))",dc.ColumnName), FilterOperator.LessThan, "0") }); ltZeroFormat.Style.Color = Color.Green; gtZeroFormat.Filters.AddRange( new[] { new Filter( string.Format("=CDbl(Substr(Fields.{0}, 0, Len(Fields.{0})-1))",dc.ColumnName), FilterOperator.GreaterThan, "0") }); gtZeroFormat.Style.Color = Color.Red; textBoxContent.ConditionalFormatting.AddRange( new[] { ltZeroFormat, gtZeroFormat }); textBoxContent.Value = string.Format("=(CDbl(Substr(Fields.{0}, 0, Len(Fields.{0})-1))/100)", dc.ColumnName); textBoxContent.Format = "{0:P2}"; } else if ( !textBoxHeader.Value.Contains("Tidpunkt") && !textBoxHeader.Value.Contains("Kvartal")) textBoxContent.Value = string.Format("=CDbl(Fields.{0})", dc.ColumnName); else textBoxContent.Value = string.Format("=Fields.{0}", dc.ColumnName); #endregion #endregion if(reportData.ReportType == Enumerators.PrintReportType.Prognose) { var dummyPadding = new TextBox { Name = string.Format("Padding{0}", dc.ColumnName), Size = new SizeU( Unit.Cm(1), Unit.Mm(3)) }; dataTable.Body.SetCellContent(0, i, dummyPadding); dataTable.Body.SetCellContent(1, i++, textBoxContent); // Add the dummy padding item to the table dataTable.Items.Add(dummyPadding); } else { dataTable.Body.SetCellContent(0, i++, textBoxContent); } // Add header and content to the table dataTable.Items.AddRange( new ReportItemBase[] { textBoxHeader, textBoxContent }); } #endregion}I do know there's some unnecessary size parameters in the code since the table is docked, I do on the other hand have a hard time getting the row-heights to act as I want to and do appreciate some pointers on this issue as well.
The the current version which I posted here I'm using a DataTable as source for the table. I've also tried converting the datatable into a collection of dynamic (IEnumerable<dynamic>, dynamic = ExpandoObject) without success.
An example on the CSV-string that is used as source:
"\"Prognostyp\",\"Kvartal\",\"Utfall\",\"Prognos\",\"Kvartalssumma\",\"Referens\",\"Procent\"\r\n\"Budget\",\"Q1\",\"203 442\",\"0\",\"203 442\",\"227 045\",\"-10,40 %\"\r\n\"Budget\",\"Q2\",\"85 173\",\"31 463\",\"116 636\",\"117 133\",\"-0,42 %\"\r\n\"Budget\",\"Q3\",\"0\",\"107 558\",\"107 558\",\"107 558\",\"0,00 %\"\r\n\"Budget\",\"Q4\",\"0\",\"200 592\",\"200 592\",\"200 592\",\"0,00 %\"\r\n\"P1\",\"Q1\",\"203 442\",\"0\",\"203 442\",\"227 045\",\"-10,40 %\"\r\n\"P1\",\"Q2\",\"85 173\",\"28 317\",\"113 490\",\"105 420\",\"7,66 %\"\r\n\"P1\",\"Q3\",\"0\",\"107 558\",\"107 558\",\"107 558\",\"0,00 %\"\r\n\"P1\",\"Q4\",\"0\",\"200 592\",\"200 592\",\"200 592\",\"0,00 %\"\r\n"Now I've tried creating the expected table through the designer and binding this to a mock ObjectDataSource which returns a datatable consisting of the same type of data and read throught the generated code in InitializeComponent, but I'm unable to get the same thing working dynamically.
I've attached screenshots of the table created through the designer and also screenshots from the client and generated reports (the ones that work).
Here's the code that I've used as DataSource while designing the table
[DataObjectMethod(DataObjectMethodType.Select)]public DataTable PrognoseTableData(){ DataTable dt; using (var parser = new GenericParserAdapter(new StringReader("\"Prognostyp\",\"Kvartal\",\"Utfall\",\"Prognos\",\"Kvartalssumma\",\"Referens\",\"Procent\"\n\"Budget\",\"Q1\",\"203 442\",\"0\",\"203 442\",\"227 045\",\"-10,40 %\"\n\"Budget\",\"Q2\",\"85 173\",\"31 463\",\"116 636\",\"117 133\",\"-0,42 %\"\n\"Budget\",\"Q3\",\"0\",\"107 558\",\"107 558\",\"107 558\",\"0,00 %\"\n\"Budget\",\"Q4\",\"0\",\"200 592\",\"200 592\",\"200 592\",\"0,00 %\""))) { parser.FirstRowHasHeader = true; dt = parser.GetDataTable(); } return dt;}Hopefully someone can help me out with these issues (both to get the table working and to get the row heights shrunk)
Best regards,
Tobias