Alex
Alex

Reputation: 35881

RDLC with repeated row headings and row wrap

I'm building an RDLC subreport in Visual Studio's report builder. It needs to display the data horizontally, repeat the row headers, and wrap when it runs out of space (10 items per row). There can be n number of items (Id's). Also note the Length has to be shown between the items. Also note that the row headings repeat for each row. Here's a sample:

enter image description here

So for one subreport, it might be 30 "items" (as in above image) while another can have 52, 20, 14, etc.

The data is stored on SQL Server like this:

(I didn't design the table; would have gone with one-to-many rather than storing data as a CSV in a column.)

I found this example: Show data Horizontally in rdlc report, but it's not showing the row headers, repeating them, or wrapping the items.

How do you create an RDLC that displays data as in the above sample? Thank you.

Upvotes: 3

Views: 1724

Answers (1)

Alex
Alex

Reputation: 35881

The following serves as one, among many, solutions; it's only one approach. But it finally fixed the report. Wrapping the items became a no-go; it's not possible, as far as I know. So I decided to just have max of 10 items per row in the subreport.

I started by working on the data, which as is indicated above, came in as CSV values from the db. Not ideal, but workable.

// Split the CSV data into lists.
var weightList = dataModel.Weight.Split(',')
    // Add thousands separator to weight
    .Select(x => int.Parse(x).ToString("##,###"))   
    .ToList();
var lengthList = dataModel.Length.Split(',').ToList();
reportData.RowModelList = new List<RowModel>();

// Loop for 10 at a time
for (var i = 0; i < weightList.Count; i=i+10)
{
    var weightListTen = new List<string>();
    // Get an array of 10 weight items
    var weightArrayItems = weightList.Skip(i).Take(10).ToArray();

    // Important: we need to add 10 elements per row,
    // even if the item is blank string. This makes life
    // easier in RDLC as we won't have to tangle with 
    // out-of-bound array errors. The below issue can
    // be encountered on the last row.
    for (var k = 0; k < 10; k++)
    {
        weightListTen.Add(weightArrayItems.Length - 1 < k ? "" : weightArrayItems[k]);
    }

    var lengthListTen = new List<string>();
    // Get an array of 9 length items
    var lengthArrayItems = lengthList.Skip(i).Take(9).ToArray();
    // Same as weight: loop and add fully 10 items
    // per row (i variable), so to avoid array out-of-bound
    // errors in VBA.
    for (var l = 0; l < 10; l++)
    {
        lengthListTen.Add(lengthArrayItems.Length - 1 < l ? "" : FeetAndInches(int.Parse(lengthArrayItems[l])));
    }

    // Similar issue as above being addressed here, but
    // with a slightly different approach. We need the 
    // Id property array for this row and we want to
    // ensure we have 10 records, even if the item 
    // only has a blank string. 
    var idList = new List<string>();
    var diff = (i + 10) - weightList.Count;
    for (var j = i+1; j <= weightList.Count + diff ; j++) 
    {
        idList.Add(j <= weightList.Count ? j.ToString() : "");
    }

    // Instantiate a new row.
    var rowModel = new RowModel
    {
        Weight = weightListTen.ToArray(), 
        Length = lengthListTen.ToArray(),
        Id = idList.Select(x => x.ToString()).ToArray()
    };
    reportData.RowModelList.Add(rowModel);
}

// Helper for getting feet/inches
private static string FeetAndInches(int inches)
{
    return inches / 12 + "' " + inches%12 + "\"";
}

Here's the Renderer class that handles the RDLC rendering:

public class Renderer
{
    private readonly int _id;
    private ReportDataSource _dataSourceMain;
    private ReportDataSource _dataSourceRows;

    public Renderer(int id)
    {
        _id = id;
    }

    public async Task<byte[]> RenderPdfAsync()
    {
        var report = await DataService.GetReportDataAsync(_id);

        Warning[] warnings;
        string[] streamIds;
        var mimeType = string.Empty;
        var encoding = string.Empty;
        var extension = string.Empty;

        using (var report = new LocalReport())
        {
            report.ShowDetailedSubreportMessages = true;
            report.EnableExternalImages = true;

            report.ReportEmbeddedResource = "Renderer.Templates.Pdf.rdlc";
            var dsMain = new List<DataModel> { report };
            _dataSourceMain = new ReportDataSource("dsMain", dsMain);
            _dataSourceRows = new ReportDataSource("dsRows", permit.RowModelList);
            report.DataSources.Add(_dataSourceMain);
            report.DataSources.Add(_dataSourceRows);
            report.SubreportProcessing += SetSubDataSource;
            report.Refresh();
            var result = report.Render("PDF", null, out mimeType, out encoding, out extension, out streamIds, out warnings);

            if (warnings.Any())
            {
                Logger.Log(LogLevel.Error, null, "Report Processing Messages: " + string.Join(", *", warnings.Select(x => new
                                                         {x.Message, x.Code, x.ObjectName, x.ObjectType, x.Severity})));
            }

            return result;
        }
    }

    private void SetSubDataSource(object sender, SubreportProcessingEventArgs e)
    {
        e.DataSources.Clear();
        e.DataSources.Add(_dataSourceMain);
        e.DataSources.Add(_dataSourceRows);
    }
}

The subreport RDLC uses a Tablix bound to the dsRows and then references the array elements across ID, Weight, and Length rows. The Tablix has its header row deleted and has a column on the left with the static values of those 3 row headers entered into textbox controls.

Then there are 10 columns to the right of that header information column; these display the array elements for that row. In each cell, an expression fetches the respective array value: =Fields!Weight.Value(0) in the first Weight column, and =Fields!Weight.Value(9) in the last one.

There are a lot of better ways to assemble the data in the for loop earlier (for example, merging the multiple loops into one); but it's working and we'll visit it as time allows to refactor and gain efficiencies.

Hope this helps someone else.

Upvotes: 2

Related Questions