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
Output expected on clicking Button “Report”:
My DatagridView (dgrData
) Control is associated with
- SQL
“Select Id, c_Name from Country”
- 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:
- 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.
- 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.
- 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.
- 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.
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();
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:
- r-aghaei/DynamicRdlcReport
- Download Zip