This is a migrated thread and some comments may be shown as answers.

Issues with dynamic printing report for Silverlight

2 Answers 203 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
Tobias Rodewi
Top achievements
Rank 1
Tobias Rodewi asked on 06 Dec 2011, 10:24 AM
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

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

2 Answers, 1 is accepted

Sort by
0
Accepted
Hadib Ahmabi
Top achievements
Rank 1
answered on 06 Dec 2011, 01:21 PM
I once stumbled upon the same problem (actually the project was almost 1:1 with yours). I was dynamically creating a table from a Grid with grouping (on many levels). My problem was, that I was setting width 0 to the cells in one of the columns in the table(the one that I grouped on). It seems that the report marks the items non-existing if the dimensions are "0". 
Another thing I don't quite get is the hierarchy of the row-groups you want to achieve. If you don't have grouping you need just one group - the detail with Grouping = "". If you need additional grouping you can add more groups or nest them. For example:

GroupByPrognostyp (nested)
-------->DetailGroup

would be different than

GroupByPrognostyp, DetailGroup (adjacent)

in the first case, you need only 1 table row(detail is in the bottom of the hierarchy), in the second you need 2 rows.
Try to debug and see that the table is created in a way that it is supposed to be (comparing to one made in designer is best), don't forget to check the dimensions.
Check this article, showing how to serialize report to XML and try serializing yours with the one made in the designer and see the differences. 

About the row heights I think it's better not to use docking but rather absolute positioning and sizes (if using absolute positioning, sizes must always be greater than zero), then you will have everything look exactly how you want it to (I myself tried with docking - not good). You can then also adjust the CanGrow and CanShrink properties. 
0
Tobias Rodewi
Top achievements
Rank 1
answered on 07 Dec 2011, 01:02 PM
Thanks Squiggles, you were so right. I had mistakenly set the height property to 0 on one of the textboxes which led to the error. Changing the dock-type to top instead of fill also sorted out my issue with the row height.

Best regards
Tags
General Discussions
Asked by
Tobias Rodewi
Top achievements
Rank 1
Answers by
Hadib Ahmabi
Top achievements
Rank 1
Tobias Rodewi
Top achievements
Rank 1
Share this question
or