Create RDLC report dynamically at run-time from a DataGridView
Asked Answered
T

1

7

I have a form AdvancedSearchForm with a DataGridView control dgrData and a button Report in C# Winform. On click of the button Report, I wish that a form with a ReportView control be shown with the same columns as in the DataGridView with the same column Headers.

Form with DataGridView and Button

enter image description here

Output expected on clicking Button “Report”:

enter image description here

My DatagridView (dgrData) Control is associated with

  1. SQL
“Select Id, c_Name from Country”
  1. ConnectionString
server=localhost;User Id=root;password=root;Persist Security Info=True;database=country_state

To load data to the grid at runtime, I prepare the following DataAdapter:

DataAdapter dataAdapter = DataAdapter.Current;
// I am passing the SQL statement and the table name to my database which knows the ConnectionString within the LoadData function

DataTable dt0 = dataAdapter.LoadData("select Id, c_Name from `country`", "country");
if (dt0 != null) {
   dgrData.DataSource = dt0;
}

Is it possible to call a child Form containing default reportviewer control, which shows report with a table containing columns corresponding to the datagridview (dgrData) along with data dynamically at runtime?

Output expectation in detail:

  1. On click of button, the reportviewer on the target form should get
    associated with the dataSource from the values in the DataGridView. So, the ReportViewer control does not know anything about the data in the report till user clicks on the Report Button at run-time.
  2. I wish the the solution does not require creation of a separate RDLC file, because It causes external dependency, to stop the current flow and create a report file in a report file designer which can be over-whelming to users.
  3. I know nothing about RDLC designer and associating data source (I am willing to learn (^_^), but I cannot force this learning requirement on my team) and binding the data to the report. I would appreciate working coding examples, if your help contains theory.
  4. I know ReportViewer has been around for quite a long time now. Wish that an example solution for 1-1 data mapping between data-grid and ReportViewer was easier to find for someone in the future on SO.

Note: Please let me know if any additional data is required from my side in the comments. To show the current solution, I had to create and RDLC file where I had to put both the connection string and SQL at design time which I wish to avoid in the solution I am looking for. I wish to find a solution where the RDLC file is generated through some modular code that can be used on other solutions as well, rather than having to design it for each form where I have DataGrids.

Tonsure answered 1/11, 2016 at 15:15 Comment(13)
Pass DataTable to ReportViewerMackoff
let me try the solution in c# and get back to this chain.Asteroid
I wish the the solution does not require creation of a separate RDLC. The solution which I posted is based on an existing report. It doesn't create columns at run-time. If you need to create report at run-time, you can use this idea or this one.Mackoff
Thank you @rezaAghaei. I want to know if you can help me with creating a rdlc file at run time instead of HTML like you have shown in the other chain. I had seen the solutions you have provided above, but somehow I think they are complicated and I feel things need not be that complex.Asteroid
If there is a way to avoid RDLC or HTML files altogether, that would be even better :)Asteroid
As another option you can Print the DataGridView control using its DrawToBitmap method.Mackoff
Creating a dynamic rdlc report at run-time would not be easy.Mackoff
Also take a look at this post and use the C# example from MSDN. If you needed righ to left, apply the fixes which I posted in answer to the C# code, otherwise the C# example for left to right is OK.Mackoff
@rezaAghaei, I wanted to know if you can show that solution in C#, I find VB a little difficult to read and translate. I have not formally coded in that language yet.Asteroid
If you mean the last link you can download C# version hereMackoff
I tried out the example, it shows print preview after calculating grid. Is there a way to use this logic to get it to generate RDL file at runtime. I will really appreciate if that can be done...Asteroid
You can use the Idea which I used to generate html. It's really elegant solution. Use the idea to create an RDLC report.Mackoff
I posted an answer based on the same idea of using t4 template. Using is is really simple and it supports adding dolumns dynamically at run-time.Mackoff
M
7

As an option to create RDLC report dynamically at at run-time, you can use Run-Time Text Templates.

In the below example, I've created a simple grid report that can be used to create report dynamically at run-time. You can dynamically add columns to report and set title, width, header back color for column.

In the example, I've filled the template using a DataGridView. But you can use this technique dependent from any kind of contrlol or even use it in web forms.

Sample Usage - Create and Show Dynamic Report

To create and show dynamic report it's enough to add some columns to the ReportForm and then set data and show the form.

var f = new ReportForm();
f.ReportColumns = this.dataGridView1.Columns.Cast<DataGridViewColumn>()
                      .Select(x => new ReportColumn(x.DataPropertyName)
                      { Title = x.HeaderText, Width = x.Width }).ToList();
f.ReportData = this.dataGridView1.DataSource;
f.ShowDialog();

enter image description here

Path to solution

It's enough to add ReportColumn and DynamicReport.tt and ReportForm to your application or even in a reusable library once and then simply use like above example. Follow below steps to create a dynamic report template.

Report Column Model

Create a report column model that contains properties for title, expression, color and so on. We will use this to add columns to report.

using System;
using System.Drawing;
public class ReportColumn
{
    public ReportColumn(string name)
    {
        Name = name;
        Title = name;
        Type = typeof(System.String);
        Width = GetPixelFromInch(1);
        Expression = string.Format("=Fields!{0}.Value", name);
        HeaderBackColor = Color.LightGray;
    }
    public string Name { get; set; }
    public string Title { get; set; }
    public Type Type { get; set; }
    public int Width { get; set; }
    public float WidthInInch
    {
        get { return GetInchFromPixel(Width); }
    }
    public string Expression { get; set; }
    public Color HeaderBackColor { get; set; }
    public string HeaderBackColorInHtml
    {
        get { return ColorTranslator.ToHtml(HeaderBackColor); }
    }
    private int GetPixelFromInch(float inch)
    {
        using (var g = Graphics.FromHwnd(IntPtr.Zero))
            return (int)(g.DpiY * inch);
    }
    private float GetInchFromPixel(int pixel)
    {
        using (var g = Graphics.FromHwnd(IntPtr.Zero))
            return (float)pixel / g.DpiY;
    }
}

Report Template

Add a Run-time Template (Also known as Preprocessed template) to the project and name it DynamicReport.tt and copy this content to the file:

<#@ template language="C#" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ parameter name="Model" type="System.Collections.Generic.List<ReportColumn>"#>
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition">
  <DataSources>
    <DataSource Name="DataSource1">
      <ConnectionProperties>
        <DataProvider>System.Data.DataSet</DataProvider>
        <ConnectString>/* Local Connection */</ConnectString>
      </ConnectionProperties>
      <rd:DataSourceID>e9784bb0-a630-49cc-b7f9-8495aca23a6c</rd:DataSourceID>
    </DataSource>
  </DataSources>
  <DataSets>
    <DataSet Name="DataSet1">
      <Fields>
<#    foreach(ReportColumn column in Model){#>
        <Field Name="<#=column.Name#>">
          <DataField><#=column.Name#></DataField>
          <rd:TypeName><#=column.Type.Name#></rd:TypeName>
        </Field>
<#    }#>
      </Fields>
      <Query>
        <DataSourceName>DataSource1</DataSourceName>
        <CommandText>/* Local Query */</CommandText>
      </Query>
      <rd:DataSetInfo>
        <rd:DataSetName />
        <rd:TableName />
        <rd:ObjectDataSourceType />
      </rd:DataSetInfo>
    </DataSet>
  </DataSets>
  <Body>
    <ReportItems>
      <Tablix Name="Tablix1">
        <TablixBody>
          <TablixColumns>
<#    foreach(ReportColumn column in Model){#>
            <TablixColumn>
              <Width><#=column.WidthInInch#>in</Width>
            </TablixColumn>
<#    }#>
          </TablixColumns>
          <TablixRows>
            <TablixRow>
              <Height>0.25in</Height>
              <TablixCells>
<#    foreach(ReportColumn column in Model){#>
                <TablixCell>
                  <CellContents>
                    <Textbox Name="<#=column.Name#>TextBox">
                      <CanGrow>true</CanGrow>
                      <KeepTogether>true</KeepTogether>
                      <Paragraphs>
                        <Paragraph>
                          <TextRuns>
                            <TextRun>
                              <Value><#=column.Title#></Value>
                              <Style />
                            </TextRun>
                          </TextRuns>
                          <Style />
                        </Paragraph>
                      </Paragraphs>
                      <rd:DefaultName><#=column.Name#>TextBox</rd:DefaultName>
                      <Style>
                        <Border>
                          <Color>LightGrey</Color>
                          <Style>Solid</Style>
                        </Border>
                        <BackgroundColor><#=column.HeaderBackColorInHtml#></BackgroundColor>
                        <PaddingLeft>2pt</PaddingLeft>
                        <PaddingRight>2pt</PaddingRight>
                        <PaddingTop>2pt</PaddingTop>
                        <PaddingBottom>2pt</PaddingBottom>
                      </Style>
                    </Textbox>
                  </CellContents>
                </TablixCell>
<#    }#>
              </TablixCells>
            </TablixRow>
            <TablixRow>
              <Height>0.25in</Height>
              <TablixCells>
<#    foreach(ReportColumn column in Model){#>
                <TablixCell>
                  <CellContents>
                    <Textbox Name="<#=column.Name#>">
                      <CanGrow>true</CanGrow>
                      <KeepTogether>true</KeepTogether>
                      <Paragraphs>
                        <Paragraph>
                          <TextRuns>
                            <TextRun>
                              <Value><#=column.Expression#></Value>
                              <Style />
                            </TextRun>
                          </TextRuns>
                          <Style />
                        </Paragraph>
                      </Paragraphs>
                      <rd:DefaultName><#=column.Name#></rd:DefaultName>
                      <Style>
                        <Border>
                          <Color>LightGrey</Color>
                          <Style>Solid</Style>
                        </Border>
                        <PaddingLeft>2pt</PaddingLeft>
                        <PaddingRight>2pt</PaddingRight>
                        <PaddingTop>2pt</PaddingTop>
                        <PaddingBottom>2pt</PaddingBottom>
                      </Style>
                    </Textbox>
                  </CellContents>
                </TablixCell>
<#    }#>
              </TablixCells>
            </TablixRow>
          </TablixRows>
        </TablixBody>
        <TablixColumnHierarchy>
          <TablixMembers>
<#    foreach(ReportColumn column in Model){#>
            <TablixMember />
<#    }#>
          </TablixMembers>
        </TablixColumnHierarchy>
        <TablixRowHierarchy>
          <TablixMembers>
            <TablixMember>
              <KeepWithGroup>After</KeepWithGroup>
            </TablixMember>
            <TablixMember>
              <Group Name="Details" />
            </TablixMember>
          </TablixMembers>
        </TablixRowHierarchy>
        <DataSetName>DataSet1</DataSetName>
        <Top>0.15625in</Top>
        <Left>0.125in</Left>
        <Height>0.5in</Height>
        <Width>2in</Width>
        <Style>
          <Border>
            <Style>None</Style>
          </Border>
        </Style>
      </Tablix>
    </ReportItems>
    <Height>0.82292in</Height>
    <Style />
  </Body>
  <Width>6.5in</Width>
  <Page>
    <LeftMargin>1in</LeftMargin>
    <RightMargin>1in</RightMargin>
    <TopMargin>1in</TopMargin>
    <BottomMargin>1in</BottomMargin>
    <Style />
  </Page>
  <rd:ReportID>60987c40-62b1-463b-b670-f3fa81914e33</rd:ReportID>
  <rd:ReportUnitType>Inch</rd:ReportUnitType>
</Report>

Report Form

Add a Form to project and add a ReportViewer control to the form and put this code in the class:

public partial class ReportForm : Form
{
    public ReportForm()
    {
        InitializeComponent();
        ReportColumns  = new List<ReportColumn>();
        this.Load+=new EventHandler(ReportForm_Load);
    }

    public List<ReportColumn> ReportColumns { get; set; }
    public Object ReportData { get; set; }

    private void ReportForm_Load(object sender, EventArgs e)
    {
        var report = new DynamicReport();
        report.Session = new Dictionary<string, object>();
        report.Session["Model"] = this.ReportColumns;
        report.Initialize();
        var rds = new Microsoft.Reporting.WinForms.ReportDataSource("DataSet1", this.ReportData);
        this.reportViewer1.LocalReport.DataSources.Clear();
        this.reportViewer1.LocalReport.DataSources.Add(rds);
        var reportContent = System.Text.Encoding.UTF8.GetBytes(report.TransformText());
        using (var stream = new System.IO.MemoryStream(reportContent))
        {
            this.reportViewer1.LocalReport.LoadReportDefinition(stream);
        }
        this.reportViewer1.RefreshReport();
    }
}

Note

You can simply extend the ReportColumn model and also DynamicReport.tt. I've created the template using an exiting report, I just used some t4 code tags to make it dynamic.

Example

You can clone or download a working example:

Mackoff answered 1/11, 2016 at 19:16 Comment(25)
thank you, this seems to mimic the DataGrid exactly and solves the problem for now. I wanted to know if there is flexibility in the Pre-processor template to make it appear in a more custom format. Specifically in cases where we need to merge rows and columns as per our reporting needs.Asteroid
Can we set different grid widths to different columns in this type of pre-processor template for data we have on the grid?Asteroid
the .tt file shown above seems to be XML, could you show a HTML implementation of the same please. . .Asteroid
I need help with the following lines <br/> f.ReportColumns = this.dgrData.Columns.Cast<DataGridViewColumn>() .Select(x => new ReportColumn(x.DataPropertyName) { Title = x.HeaderText, Width = x.Width }).ToList();Asteroid
can you please convert this to code without lambda expression pleaseAsteroid
This is the way that t4 works: The tt file works as a template. It converts all lines to Writer.Write("...") methods and mixes the C# code which we used in tt. Then as a result it creates a C# class which has some methods like TransformText which creates the string output for us. Somehow it works like ASP.NET pages which you mix texts and C# code blocks or like Razor. The C# class is generated under tt file in solution and should not be touched. Make any changes in tt file then the C# class will changes. To learn more about T4 templates see the linked document from Microsoft.Mackoff
RDLC report is in fact a XML file that used by Report Engine to render. It doesn't have anything to do with HTML. The RDLC which you see in the T4 file, is not a magical one, I created a simple table report and then used its XML in T4 file. Then instead of having columns hard-coded, I created them using those foreach loops which you see in the T4 file. To add other elements like a title or a footer or some labels for date time and so on, yo should use the same Idea and create a report and convert it to such T4 template.Mackoff
You can add columns with different widths to the ReportColumns. The can be different from DataGridView columns. But you should provide data for that column. For example you can use it this way: f.ReportColumns.Add(new ReportColumn("A"));f.ReportColumns.Add(new ReportColumn("B")); then you can pass a List which its elements contains A and B properties. For example a DataTable which is created this way: var dt = new DataTable(); dt.Columns.Add("A"); dt.Columns.Add("B"); dt.Rows.Add("1");dt.Rows.Add("2"); then if you set f.ReportData = dt it shows the report with these data.Mackoff
oh wonderful. I also figured out how the lambda function works. For my own code, i changed the block above to: List<ReportColumn> lstRepCmn = new List<ReportColumn>(); foreach (DataGridViewColumn c in dgrData.Columns) { if (c.Visible) { ReportColumn r = new ReportColumn(c.DataPropertyName) { Title = c.HeaderText, Width = c.Width }; lstRepCmn.Add(r); } }f.ReportColumns = lstRepCmn;Asteroid
Using Linq is optional but it;s essential and you should not avoid using linq. The code which I used is equivalent to a simple foreach loop. var list = new List<ReportColumn>(); foreach(DataGridViewRow x in dataGridView1.Rows){ list.Add(new ReportColumn(x.DataPropertyName) { Title = x.HeaderText, Width = x.Width });} Then f.ReportColumns = list;.Mackoff
Yes, great. Linq is really useful.Mackoff
Thank you @RezaAghaei, I will contact you further on this if i come across any more doubts. Thank you very much for being patient with this problem and helping me with reportViewer control.Asteroid
I can see from the code that this ultimately creates RDL file syntax at run time that can be loaded to a report viewer. Is there a way I can see the generated RDL syntax code?Asteroid
the reportContent variable is the string which contains RDLC format. Save it to a *.rdlc file then you can simply open it in Visual Studio.Mackoff
Creating dynamic reports at run-time is not easy and in a real world environment/project I believe my answer here is just a start point. But a really elegant and useful solution. If you get the idea, it will be useful for you for many cases which you need to create templates, for example an email template which contains the products which the customer has bought. The idea is brilliant IMO!Mackoff
Also the HTML answer which I posted here is really good start point for those who wants to create HTML output, but most users doesn't have any idea about Run-time T4 Templates and don't know how it works and since the answer is somehow long the answer is neglected; but it's really useful indeed.Mackoff
Thanks for the note on reportContent variable. I will go through the HTML part as well. Thank you for your guidance.Asteroid
Let me know if you had any question about the linked post or if you find it useful :)Mackoff
can we get the RDL Skeleton from this? I have got the xml generated from the report content using the code string RDLCCode = System.Text.Encoding.UTF8.GetString(reportContent); but can get the design schema?Asteroid
You should first feed the template with model and (ReportColumns) and initialize it then you can write var reportContent = System.Text.Encoding.UTF8.GetBytes(report.TransformText()); then System.IO.File.WriteAllText(@"d:\report1.rdlc", reportContent );Mackoff
Can you show me what changes I need to do. To me it looks like the model assignment and reportContent serialization happend in the same file - ReportForm.cs. However, I dont know how to get the RDL skeleton from thisAsteroid
I think if you paste all the comments above into one comprehensive learning answer, it will weight as much as the answer itself (^_^)Asteroid
You're right. But the answer is long enough and it makes the answer too much long, most users don't read the answer currently if I make it more longer probably it decreases the chance of the answer to be read. Insteaf you can vote the comments which you think are more useful to bring them up.Mackoff
I have posted a separate question to seek answers regarding RDL report generation in this link: #40395659 .Asteroid
I found this post really useful! I just wondered if Reza Agaei could help me to include grouping and sorting? I have posted a new related question hereCopepod

© 2022 - 2024 — McMap. All rights reserved.