Tuesday, August 26, 2008

Report Viewer in WPF application

I have started working on a WPF application which has to display some data in a graphical manner. Instead of using the different available charting tools, I decided to use the report facility inbuilt in the Visual Studio to display .
Visual Studio allows for binding objects as the report's data source to create an local report. On trying to achieve my purpose, I came across a few difficulties and on spending some time through various posts and forums I got the basic direction, so thought of consolidating my learning and putting it up.

The problems I came across were as folows:


  • Report viewer is not available as a native control in WPF applications, and therefore it could not be placed in the application.

  • Binding the data source (the object) to the report.

To solve these couple of issues, I went about it in the following way:



  • Since the report viewer is not available on WPF, but on Windows Forms - I used WindowsFormsHost to integrate the report viewer control with the WPF form. WindowsFormsHost is the implementation from Microsoft, which would have other wise required me to derive a class from the HwndHost base class, to add a HWND to my WPF application to host Windows Form control.


  1. I added the following references to my project: Microsoft.ReportViewer.Common, Microsoft.ReportViewer.WinForms, System.Windows.Forms, WindowsFormIntegration.

  2. Create an object of ReportViewer and specify the processing mode as local, the report path (the .rdlc path)

  3. Create an object of the ReportDataSource with the name of the data source and the object which contains the data as its parameters.

  4. Add the data source to the Local report created.

  5. Refresh the report.

  6. Create WindowsFormsHost object which is used for inserting the report to the control on the WPF form.
Sample Code Snippet (Window1.xaml.cs):

//Merchant is the class used to enter new product names and their prices.
Merchant merchant = new Merchant(); List products = new List(); products = merchant.GetProducts(); WindowsFormsHost host = new WindowsFormsHost(); Microsoft.Reporting.WinForms.ReportViewer reportViewer = new Microsoft.Reporting.WinForms.ReportViewer();
//Specifying local processing mode for the ReportViewer. reportViewer.ProcessingMode = ProcessingMode.Local;

//Specifying the location of the report definition file. reportViewer.LocalReport.ReportPath = "..\\..\\ProductReport.rdlc";

//Creating a new ReportDataSource with the name of the DataSource and the object // which is to be used as the DataSource. ReportDataSource ds = new ReportDataSource("ReportViewer_Product", products);

//Adding the ReportDataSource to the DataSoure of the ReportViewer reportViewer.LocalReport.DataSources.Add(ds);

//Causes the current report in the Report Viewer to be processed and rendered. reportViewer.RefreshReport();

//Sets the child control hosted by the WindowsFormsHost element. host.Child = reportViewer;
//Add the WindowsFormsHost element to the Grid in the Window1.xaml reportGrid.Children.Add(host);


The final result will look something like this, other items can also be used instead of a table which has been placed here:



14 comments:

Jelena said...

Thanx man. You are a king!

this two lines saved my day :)

windowsFormsHost1.Child = viewerInstance;
grid0.Children.Add(windowsFormsHost1);

Lawton said...

Awesome! I'd been struggling with this. I can't believe they have declined to add a better way to host SQL reports in WPF.

This was super helpful. Thanks!

Junior.NET said...

It works good but, in order to show WindowsFormsHost in your project, the xaml for Window tag must not have AllowsTransparency="True"

jwjanse said...

Since I work on team projects using central source control I would not like to reference a resource using a path, like

reportViewer.LocalReport.ReportPath = "..\\..\\ProductReport.rdlc";

Since each developer might checkout the sources in a different location in their client, this will not work without code changes.

IMHO it would be better to embed the report, and reference it using ie:
reportViewer.LocalReport.ReportEmbeddedResource = "MyAssembly.ProductReport.rdlc";

inmams said...

Wow!!!! great.... I'd been struggling whole day just to see the data on report.... :)

Great guidance.... :)

Thanks a lot

Nathan said...

This has brought me a long way but I still have some questions. Since I am populating an RDLC report I don't have any data entry. Can you explain what is meant by:

"//Merchant is the class used to enter new product names and their prices."

Thanks much.

Nathan said...

In the above question I am not asking for an explanation of a class. The data I am trying to report is in a recordset and I have not been able to use a recordset object and the GetData() method. Youe example is key to making this work, I just need to understand the data source.

Thanks again

Nathan said...

Below is the code. It produces an empty report.

private void viewReport()
{


string reportPath = "C:\\Dev\\2010\\2010_BoxingAdmin\\BoxingAdmin\\BoxingAdmin\\Report1.rdlc";


BoxingDataSet bds = new BoxingDataSet();

DataTable BoxedBalls = bds.Tables.Add("BoxedBalls");


BoxingDataSetTableAdapters.boxed_BallsTableAdapter bbta = new BoxingDataSetTableAdapters.boxed_BallsTableAdapter();


bbta.Fill(bds.boxed_Balls);

ReportDataSource rds = new ReportDataSource("DataSet1", BoxedBalls);

ReportViewer reportViewer = new ReportViewer();
reportViewer.ProcessingMode = ProcessingMode.Local;
reportViewer.LocalReport.ReportPath = reportPath;
reportViewer.LocalReport.DataSources.Add(rds);
windowsFormsHost1.Child = reportViewer;
reportViewer.RefreshReport();

}

Nathan said...

Ok, the code below implements these instructions using a data set and table adapters.

private void viewReport()
{


string reportPath = "C:\\Dev\\2010\\2010_BoxingAdmin\\BoxingAdmin\\BoxingAdmin\\Report1.rdlc";


BoxingDataSet bds = new BoxingDataSet();


BoxingDataSetTableAdapters.boxed_BallsTableAdapter bbta = new BoxingDataSetTableAdapters.boxed_BallsTableAdapter();


bbta.Fill(bds.boxed_Balls);
DataTable BoxedBalls = bds.boxed_Balls;


ReportDataSource rds = new ReportDataSource("DataSet1", BoxedBalls);

ReportViewer reportViewer = new ReportViewer();
reportViewer.ProcessingMode = ProcessingMode.Local;
reportViewer.LocalReport.ReportPath = reportPath;
reportViewer.LocalReport.DataSources.Add(rds);
windowsFormsHost1.Child = reportViewer;
reportViewer.RefreshReport();

}

Venkatesh said...

Hi,

Can you please share the solution in this blog. I have also created a similar solution but I get only a blank WPF screen. You can also email me the code.
Thanks and regards
Venkatesh. S

Anonymous said...

can someone help me with a working code ... small project will help.

I have written part of the code.

// Define the Business Object "Product" with two public properties
// of simple datatypes.
public class Product
{
private string m_name;
private int m_price;

public Product(string name, int price)
{
m_name = name;
m_price = price;
}

public string Name
{
get
{
return m_name;
}
}

public int Price
{
get
{
return m_price;
}
}
}

// Define Business Object "Merchant" that provides a
// GetProducts method that returns a collection of
// Product objects.

public class Merchant
{
private List m_products;

public Merchant()
{
m_products = new List();
m_products.Add(new Product("Pen", 25));
m_products.Add(new Product("Pencil", 30));
m_products.Add(new Product("Notebook", 15));
}

public List GetProducts()
{
return m_products;
}
}


I know how to add the reportviewer.












Connecting the two is what is driving me crazy....

please suggest

Anonymous said...

Thanx a lot!!! This saved my week!

Anonymous said...

Thank you very much. Im new to WPF and microsoft reports. This helped a lot

Plamen Minchev said...

I have to touch legacy app on VS2008/.NET 3.5 and your advice to create object for reportViewer and then to add in WindowsFormsHost works pretty well.
MS help does work, guess it is for VS2010

Thank you a lot