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.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • [Pass DataTable to ReportViewer](http://stackoverflow.com/a/34512521/3110834) – Reza Aghaei Nov 01 '16 at 15:19
  • let me try the solution in c# and get back to this chain. – Ganesh Kamath - 'Code Frenzy' Nov 01 '16 at 15:27
  • *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](https://stackoverflow.com/questions/37479511/display-datasource-from-datagridview-in-reportviewer) or [this one](http://stackoverflow.com/a/39713817/3110834). – Reza Aghaei Nov 01 '16 at 15:34
  • 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. – Ganesh Kamath - 'Code Frenzy' Nov 01 '16 at 15:41
  • If there is a way to avoid RDLC or HTML files altogether, that would be even better :) – Ganesh Kamath - 'Code Frenzy' Nov 01 '16 at 15:44
  • As another option you can Print the `DataGridView` control using its `DrawToBitmap` method. – Reza Aghaei Nov 01 '16 at 15:47
  • Creating a dynamic rdlc report at run-time would not be easy. – Reza Aghaei Nov 01 '16 at 15:49
  • Also take a look at [this post](https://stackoverflow.com/questions/40010705/printing-datagridview-from-right-to-left) 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. – Reza Aghaei Nov 01 '16 at 15:53
  • @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. – Ganesh Kamath - 'Code Frenzy' Nov 01 '16 at 16:23
  • If you mean the [last link](https://stackoverflow.com/questions/40010705/printing-datagridview-from-right-to-left) you can download C# version [here](https://code.msdn.microsoft.com/windowsdesktop/VBNet-Printing-Example-bc3b0176/file/138098/2/VB.Net%20Printing%20Example.zip) – Reza Aghaei Nov 01 '16 at 17:01
  • 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... – Ganesh Kamath - 'Code Frenzy' Nov 01 '16 at 17:12
  • You can use the Idea which I used to generate html. It's really elegant solution. Use the idea to create an RDLC report. – Reza Aghaei Nov 01 '16 at 18:00
  • 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. – Reza Aghaei Nov 01 '16 at 19:20

1 Answers1

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:

Community
  • 1
  • 1
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • 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. – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 04:55
  • Can we set different grid widths to different columns in this type of pre-processor template for data we have on the grid? – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 05:07
  • the .tt file shown above seems to be XML, could you show a HTML implementation of the same please. . . – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 06:44
  • I need help with the following lines
    f.ReportColumns = this.dgrData.Columns.Cast() .Select(x => new ReportColumn(x.DataPropertyName) { Title = x.HeaderText, Width = x.Width }).ToList();
    – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 09:08
  • can you please convert this to code without lambda expression please – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 09:09
  • 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. – Reza Aghaei Nov 02 '16 at 09:21
  • 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. – Reza Aghaei Nov 02 '16 at 09:26
  • 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. – Reza Aghaei Nov 02 '16 at 09:32
  • oh wonderful. I also figured out how the lambda function works. For my own code, i changed the block above to: List lstRepCmn = new List(); 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; – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 09:37
  • 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(); foreach(DataGridViewRow x in dataGridView1.Rows){ list.Add(new ReportColumn(x.DataPropertyName) { Title = x.HeaderText, Width = x.Width });}` Then `f.ReportColumns = list;`. – Reza Aghaei Nov 02 '16 at 09:37
  • Yes, great. Linq is really useful. – Reza Aghaei Nov 02 '16 at 09:40
  • 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. – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 09:42
  • 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? – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 09:46
  • 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. – Reza Aghaei Nov 02 '16 at 09:47
  • 1
    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! – Reza Aghaei Nov 02 '16 at 09:48
  • Also the `HTML` answer which I [posted here](http://stackoverflow.com/a/39713817/3110834) 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. – Reza Aghaei Nov 02 '16 at 09:56
  • Thanks for the note on reportContent variable. I will go through the HTML part as well. Thank you for your guidance. – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 11:07
  • Let me know if you had any question about the linked post or if you find it useful :) – Reza Aghaei Nov 02 '16 at 11:14
  • 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? – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 13:07
  • 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 );` – Reza Aghaei Nov 02 '16 at 13:17
  • 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 this – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 13:29
  • I think if you paste all the comments above into one comprehensive learning answer, it will weight as much as the answer itself (^_^) – Ganesh Kamath - 'Code Frenzy' Nov 02 '16 at 13:30
  • 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. – Reza Aghaei Nov 02 '16 at 14:21
  • I have posted a separate question to seek answers regarding RDL report generation in this link: http://stackoverflow.com/questions/40395159/c-sharp-winform-how-to-generate-real-time-reports-by-passing-3-variables-to-re . – Ganesh Kamath - 'Code Frenzy' Nov 03 '16 at 06:54
  • 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 [here](https://stackoverflow.com/questions/58373578/how-to-use-run-time-text-templates-to-do-report-grouping-and-sorting) – Hannington Mambo Oct 14 '19 at 09:16