Hello everyone,
I was able to come up with a working solution; however, if anybody has a better one, the rest of us will certainly appreciate to learn about it.
Case:
Populate a rdlc from a xml file which contains 1-n children objects, which have both static and dynamic data.
Structure Sample
<?xml version="1.0" encoding="ISO-8859-1"?>
<SetDTE>
<DTE version="1.0" > <!-- Children {1-n} (node of interest) -->
<Documento ID="MiPE76266617-720">
<Encabezado> <!-- static data = single group -->
...
</Encabezado>
<Detalle> <!-- dynamic data = 0-n groups -->
...
</Detalle>
<Detalle>
...
</Detalle>
</DTE>
<DTE version="1.0" >
<Documento ID="MiPE76266617-2495">
<Encabezado> <!-- static data = single group -->
...
</Encabezado>
<Detalle> <!-- dynamic data = 0-n groups -->
...
</Detalle>
</DTE>
</SetDTE>
At first I tried to populate it using a standard report with the many data tables generated by a dataset loaded from the source xml file:
string xmlSource = System.IO.File.ReadAllText(DTEPath);
DataSet dsXML = new DTE(xmlSource);
Where
DTE is a customized class inheriting from DataSet, which makes use of its ReadXml method to populate the object. I then extract the required tables from the source and add them to the base:
base.Tables.Add(dsXML.Tables["Documento"] == null ? new Documento() : dsXML.Tables["Documento"].Copy());
Where
Documento is a customized class that inherits from datatable, which purpose is to provide a consistent structure shall the source not have the expected node. This approach is valid, but only for xml files with a single node "of interest".
When faced with xml files with multiple nodes "of interest", the standard report does not cut it. I tried to apply a List control or nested matrices, but not only they require a single datasource, but also, detail rows only allow static data.
The workaround I found was to:
a) Merge all static datatables into a single one by means of setting the respective primary keys (method I ignored and was able to implement thanks to
this post).
b) Generate and apply sub-reports to handle the dynamic areas.
Procedure:
1) Generate a base report including a table(tablix), which must be configured to have as many group rows as needed. In this case a main group and a child group; I expanded the single row/column left in the tablix to cover the entire area of the report's body.
2) Added, to this single cell, all required textboxes, rectangles, lines, etc. to allocate the static data; i.e. the data that with unique values per group row. (The rectangles I added are the placeholders for the needed sub-reports)
3) Built the sub-reports (I needed two), and made sure to add the required input parameter, and filter to them. In this case, each sub-report has a data source, and the appropriate table to accommodate its values.
4) Added the sub-reports to the main one; each inside its designated rectangle.
5) The Code:
a) Hosting form (winform); must link the event handler required to process the sub-report(s):
private void FrmMain_Load(object sender, EventArgs e)
{
....
RepVwer.LocalReport.SubreportProcessing += new SubreportProcessingEventHandler(SetSubDataSource);
}
b) The main void: which gets the data, configures the ReportViewer control, and triggers the visualization of the report:
private void LoadXmlDTE(string DTEPath)
{
try
{
DataSet dsXML = new DTE(System.IO.File.ReadAllText(DTEPath));
RepVwer.ProcessingMode = ProcessingMode.Local;
RepVwer.LocalReport.ReportEmbeddedResource = "DTEViewer.rdlc.EncabezadoDTE.rdlc";
RepVwer.LocalReport.DataSources.Clear();
DataTable auxData = GenerateStaticGlobalData(dsXML);
RepVwer.LocalReport.DataSources.Add(new ReportDataSource("Global", auxData));
_detalle = dsXML.Tables[6];
_referencia = dsXML.Tables[7];
this.RepVwer.RefreshReport();
}
catch (Exception ex)
{
MessageBox.Show(this, "...", "...", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
c) The GenerateStaticGlobalData method is the one in charged of merging all static datatables into a single one, which will be the report's core data source:
private DataTable GenerateStaticGlobalData(DataSet dsXML)
{
DataTable auxData = dsXML.Tables[0].Copy();
auxData.PrimaryKey = new DataColumn[] { auxData.Columns["Documento_Id"] };
dsXML.Tables[1].PrimaryKey = new DataColumn[] { dsXML.Tables[1].Columns["Documento_Id"] };
auxData.Merge(dsXML.Tables[1]);
auxData.PrimaryKey = new DataColumn[] { auxData.Columns["Encabezado_Id"] };
for (int i = 2; i < 6; i++)
{
dsXML.Tables[i].PrimaryKey = new DataColumn[] { dsXML.Tables[i].Columns["Encabezado_Id"] };
auxData.Merge(dsXML.Tables[i]);
}
return auxData;
}
Note: I was able to use a
for statement in the above code because my DTE class organized those datatables in such order.
d) Let's not forget the event handler to assign the data source(s) to the sub-report(s):
public void SetSubDataSource(object sender, SubreportProcessingEventArgs e)
{
e.DataSources.Add(new ReportDataSource("Detalle", _detalle));
e.DataSources.Add(new ReportDataSource("Referencias", _referencia));
}
That's it; I now have a report (with two integrated sub-reports), which can be presented on screen as expected by the client; i.e. a document per page, which they can either export or print as desired.
Hopefully, this can be useful for someone else.
Again, thank you all who took the time to read my question, and now my own answer.