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