看图,这是一个人才测评报告,报告中包含多个子部分,部分的个数,内容都是变化的。
所以子报告部分我们采用子报表来实现。
下面讲解一下构建一个这样的报告会遇到的关键问题,并且提供方案方法。
问题一。 如果报告中子报告的数量和报告源都是不确定的,如何呈现?
按照我们一般的思路,就考虑建立一个Tablix表格,绑定一组数据源,然后在里面放置子报告。
不过很遗憾,这样并不能实现, 子报告的报告源必须是定值,不能传递参数或者绑定数据源。。 这下麻烦了,那如果做了?
首先呢,参考这个
地址
http://www.gotreportviewer.com/
里面有 Generate RDLC dynamically - Table 和 Generate RDLC dynamically - Matrix
项目里面,ReportDefinition.cs 文件给我们开拓了思路
通过地址 http://schemas.microsoft.com/sqlserver/
可以找到
然后下载此文件, 在
Visual Studio 2008 命令提示符 执行
xsd /c /n:SampleRDLSchema ReportDefinition.xsd
就可以得到ReportDefinition的实体类了。 方便你动态的创建报告文件。
通过这两个范例, 就有思路了, 我们可以把主报表动态创建出来, 把子报表以代码的方式动态的追加写入主报告文件
创建子报表类
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Web;
namespace
VAP.Modules.CP.ActiveResult.Rdl
{
///
<summary>
///
子报表创建类
///
</summary>
public
class
SubReportRdlGenerator
{
///
<summary>
///
创建一个子报表
///
</summary>
///
<param name="strreportname">
子报告名
</param>
///
<param name="strsubreport">
子报表文件路径
</param>
///
<param name="paras">
参数数组
</param>
///
<param name="inttop">
创建位置(高度)
</param>
///
<returns></returns>
public
Rdl.SubreportType CreateSubReport(
string
strreportname,
string
strsubreport,Rdl.ParametersType paras,
double
inttop)
{
Rdl.SubreportType subreport
=
new
Rdl.SubreportType();
subreport.Name
=
strreportname;
//
subreport.Items=;
subreport.Items
=
new
object
[] { strsubreport, paras,
"
8cm
"
,
"
8cm
"
,inttop.ToString()
+
"
cm
"
,
"
0.4cm
"
};
subreport.ItemsElementName
=
new
Rdl.ItemsChoiceType16[]
{
Rdl.ItemsChoiceType16.ReportName,
Rdl.ItemsChoiceType16.Parameters,
Rdl.ItemsChoiceType16.Width,
Rdl.ItemsChoiceType16.Height,
Rdl.ItemsChoiceType16.Top,
Rdl.ItemsChoiceType16.Left
};
return
subreport;
//
return matrix;
}
public
Rdl.ParametersType CreateParameters()
{
Rdl.ParametersType para
=
new
ParametersType();
//
para.Parameter[0].
return
para;
}
}
}
报告创建类, 这个类是我写的,并不通用,请根据自己的情况创建, 这里有大量的针对我的表的参数
using
System;
using
System.Data;
using
System.IO;
using
System.Collections.Generic;
using
System.Text;
using
System.Xml.Serialization;
using
System.Data.SqlClient;
using
Microsoft.ApplicationBlocks.Data;
namespace
VAP.Modules.CP.ActiveResult.Rdl
{
public
class
RdlGenerator
{
private
Rdl.Report _report;
private
List
<
object
>
lstsubobjects
=
new
List
<
object
>
();
private
int
intSubReports
=
0
;
private
List
<
string
>
m_allFields;
private
double
intEndTop
=
0
;
public
List
<
string
>
AllFields
{
get
{
return
m_allFields; }
set
{ m_allFields
=
value; }
}
#region
创建报表
public
Rdl.Report CreateReport()
{
Rdl.DataSetGenerator datasetgenerator
=
new
DataSetGenerator(
"
ExportUserField
"
,
"
LergerDataSet
"
,
"
CP_ExportUserField
"
);
datasetgenerator.AllFields
=
AllFields;
Rdl.Report report
=
new
Rdl.Report();
report.Items
=
new
object
[]
{
CreateDataSources(),
CreateBody(),
datasetgenerator.CreateDataSets(),
"
0in
"
,
};
report.ItemsElementName
=
new
Rdl.ItemsChoiceType80[]
{
Rdl.ItemsChoiceType80.DataSources,
Rdl.ItemsChoiceType80.Body,
Rdl.ItemsChoiceType80.DataSets,
Rdl.ItemsChoiceType80.Width,
};
_report
=
report;
return
report;
}
private
Rdl.DataSourcesType CreateDataSources()
{
Rdl.DataSourcesType dataSources
=
new
Rdl.DataSourcesType();
dataSources.DataSource
=
new
Rdl.DataSourceType[] { CreateDataSource() };
return
dataSources;
}
private
Rdl.DataSourceType CreateDataSource()
{
Rdl.DataSourceType dataSource
=
new
Rdl.DataSourceType();
dataSource.Name
=
"
LergerDataSet
"
;
dataSource.Items
=
new
object
[] { CreateConnectionProperties() };
return
dataSource;
}
private
Rdl.ConnectionPropertiesType CreateConnectionProperties()
{
Rdl.ConnectionPropertiesType connectionProperties
=
new
Rdl.ConnectionPropertiesType();
connectionProperties.Items
=
new
object
[]
{
"
/* Local Connection */
"
,
"
System.Data.DataSet
"
,
};
connectionProperties.ItemsElementName
=
new
Rdl.ItemsChoiceType[]
{
Rdl.ItemsChoiceType.ConnectString,
Rdl.ItemsChoiceType.DataProvider,
};
return
connectionProperties;
}
private
Rdl.BodyType CreateBody()
{
Rdl.BodyType body
=
new
Rdl.BodyType();
body.Items
=
new
object
[]
{
CreateReportItems(),
};
return
body;
}
private
Rdl.ReportItemsType CreateReportItems()
{
Rdl.ReportItemsType reportItems
=
new
Rdl.ReportItemsType();
reportItems.Items
=
lstsubobjects.ToArray();
return
reportItems;
}
#endregion
#region
加载报表
public
Rdl.Report LoadReport(
string
strreportfile,
double
intendtop)
{
XmlSerializer serializer
=
new
XmlSerializer(
typeof
(Rdl.Report));
Stream reader
=
new
FileStream(strreportfile, FileMode.Open);
_report
=
(Rdl.Report)serializer.Deserialize(reader);
reader.Close();
intEndTop
=
intendtop;
return
_report;
}
#endregion
public
void
LoadSubReports()
{
AddReportItems(lstsubobjects);
}
private
void
AddReportItems(List
<
object
>
lstobjects)
{
List
<
object
>
lstmain
=
new
List
<
object
>
();
Rdl.BodyType body
=
_report.Items[
2
]
as
Rdl.BodyType;
Rdl.ReportItemsType reportItems
=
body.Items[
0
]
as
Rdl.ReportItemsType;
for
(
int
i
=
0
; i
<
reportItems.Items.Length; i
++
)
{
lstmain.Add(reportItems.Items[i]);
}
lstmain.AddRange(lstobjects);
reportItems.Items
=
lstmain.ToArray();
}
private
void
AddReportItem(
object
reportitem)
{
}
private
Rdl.PageBreakType CreatePageBreakType()
{
Rdl.PageBreakType pagebreak
=
new
PageBreakType();
pagebreak.Items
=
new
object
[] { PageBreakTypeBreakLocation.Start };
return
pagebreak;
}
public
void
CreateSubreport(
string
strreportname,
string
strsubreport,
string
strBlockID,
string
strBlockName)
{
double
intt
=
intEndTop
+
intSubReports
*
8
;
RectangleType rectype
=
new
RectangleType();
rectype.Name
=
"
rect
"
+
strBlockName;
rectype.Items
=
new
object
[] { CreatePageBreakType(), intt.ToString()
+
"
cm
"
,
"
0.1cm
"
};
rectype.ItemsElementName
=
new
ItemsChoiceType10[] { Rdl.ItemsChoiceType10.PageBreak,Rdl.ItemsChoiceType10.Top,Rdl.ItemsChoiceType10.Height};
lstsubobjects.Add(rectype);
intt
=
intEndTop
+
intSubReports
*
8
;
SubReportRdlGenerator subreportgen
=
new
SubReportRdlGenerator();
Rdl.ParametersType paras
=
new
ParametersType();
Rdl.ParameterType parablockid
=
new
ParameterType();
parablockid.Name
=
"
BlockID
"
;
parablockid.Items
=
new
object
[] { strBlockID };
parablockid.ItemsElementName
=
new
Rdl.ItemsChoiceType5[] { Rdl.ItemsChoiceType5.Value };
Rdl.ParameterType parablockname
=
new
ParameterType();
parablockname.Name
=
"
BlockName
"
;
parablockname.Items
=
new
object
[] { strBlockName };
parablockname.ItemsElementName
=
new
Rdl.ItemsChoiceType5[] { Rdl.ItemsChoiceType5.Value };
paras.Parameter
=
new
ParameterType[] { parablockid ,parablockname};
lstsubobjects.Add(subreportgen.CreateSubReport(strreportname,strsubreport,paras,intt));
intSubReports
++
;
}
public
void
WriteXml(Stream stream)
{
XmlSerializer serializer
=
new
XmlSerializer(
typeof
(Rdl.Report));
serializer.Serialize(stream, _report);
}
}
}
我们知道主报告在使用子报告时,都是在主报告当前目录里找。但是由于主报告是动态创建的,所以不存在主报告当前的路径,
必须通过LoadSubreportDefinition,预加载子报告。
StreamReader reportsub = File.OpenText(@Server.MapPath("~/CP/Report/" + strsourcename + ".rdlc"));
this.reportviewer1.LocalReport.LoadSubreportDefinition(strsourcename, reportsub);
reportsub.Close();
如何使用? 看下面
private
void
LoadSubReports()
{
List
<
BLL.AnswerBlockInfo
>
answerblocks
=
AnswerBlockController.Current.GetByWhereClause(
"
AnswerID='
"
+
AnswerID.ToString()
+
"
'
"
,
""
);
for
(
int
i
=
0
; i
<
answerblocks.Count; i
++
)
{
BlockInfo blockinfo
=
BlockController.Current.Get(answerblocks[i].BlockID);
//
rdlGenerator.CreateTextBox();
rdlGenerator.CreateSubreport(
"
subreport
"
+
blockinfo.BlockName, blockinfo.ReportFile, blockinfo.BlockID.ToString(), blockinfo.BlockName);
}
}
private
void ShowReport()
{
this.reportviewer1.Reset();
this.reportviewer1.LocalReport.LoadReportDefinition(m_rdl);
LoadSubReportDefinition();
this.reportviewer1.LocalReport.DataSources.Add(new ReportDataSource("UserField", m_dataSet.Tables[0]));
}
这样就解决了子报告的问题;
问题二。 主报告的内容都要用编码去设定吗? 会很繁琐吧?
通过http://www.gotreportviewer.com/上面的范例,想必你也看过了,动态创建报告的主要问题就是代码太繁重,每个部分都要编码输出,内容,位置,都要考虑,有没有别的办法呢。
看方法
#region
加载报表
///
<summary>
///
加载报告,
///
</summary>
///
<param name="strreportfile">
报告文件名
</param>
///
<param name="intendtop">
报告总高度(这个值要有,你要知道你的报告有多高,那么下面的你动态创建的控件要在他下面
</param>
///
<returns></returns>
public
Rdl.Report LoadReport(
string
strreportfile,
double
intendtop)
{
XmlSerializer serializer
=
new
XmlSerializer(
typeof
(Rdl.Report));
Stream reader
=
new
FileStream(strreportfile, FileMode.Open);
_report
=
(Rdl.Report)serializer.Deserialize(reader);
reader.Close();
intEndTop
=
intendtop;
return
_report;
}
#endregion
我们可以把固定的内容提前设置好一个报告文件,保存起来, 通过代码把他反序列化为类,然后执行你的操作后,再保存起来.
rdlGenerator.LoadReport(@Server.MapPath(
"
~/CP/Report/Header/人才测评.rdlc
"
),
33
);
问题三。 我是有多个字报告,但是子报告的数据源可以也是不确定的, 我也想配置,有办法吗?
你应该也知道子报告的数据源必须通过
LocalReport.SubreportProcessing +=new SubreportProcessingEventHandler(SubreportProcessingEventHandler);
事件内传递,所以,要传递那些数据源,我们无法提前知道, 但是也是有办法的。
笔者推荐你用.xml描述好并与子报告同名,放置在子报告同一目录下
void
SubreportProcessingEventHandler(
object
sender, SubreportProcessingEventArgs e)
{
//
string strConnection = "server=WB_XXZX_01\\LERGER;database=Lerger108;uid=sa;pwd=disney; ";
string
strblockid
=
e.Parameters[
0
].Values[
0
];
Guid blockid
=
new
Guid(strblockid);
string
strblockname
=
e.Parameters[
1
].Values[
0
];
string
strConnection
=
System.Configuration.ConfigurationManager.ConnectionStrings[
"
SiteSqlServer
"
].ConnectionString;
SqlConnection objConnection
=
new
SqlConnection(strConnection);
XmlDocument xmlDoc
=
new
XmlDocument();
if
(System.IO.File.Exists(Server.MapPath(
"
~/CP/Report/
"
+
e.ReportPath
+
"
.xml
"
))
==
true
)
{
xmlDoc.Load(Server.MapPath(
"
~/CP/Report/
"
+
e.ReportPath
+
"
.xml
"
));
XmlNode xns
=
xmlDoc.SelectSingleNode(
"
report
"
);
XmlNode xn
=
xns.SelectSingleNode(
"
ReportSources
"
);
XmlNodeList xnl
=
xn.ChildNodes;
foreach
(XmlNode xnf
in
xnl)
{
XmlElement xe
=
(XmlElement)xnf;
string
strsourcename
=
xe.GetAttribute(
"
name
"
);
//
显示属性值
string
strsourcevalue
=
xe.GetAttribute(
"
value
"
);
//
显示属性值
SqlDataReader sqldatareader2
=
SqlHelper.ExecuteReader(strConnection, strsourcename, AnswerID, blockid);
ReportDataSource rptDataSource2
=
new
ReportDataSource(strsourcevalue, sqldatareader2);
e.DataSources.Add(rptDataSource2);
}
}
}
参数AnswerID, blockid?? 是这样的,由于笔者的项目参数基本可以锁定了,只是数据源也许不同, 你可以根据自己情况,再把参数配置进去xml
<?
xml version="1.0" encoding="gb2312"
?>
<
report
>
<
ReportSources
>
<
DBSource
name
="CP_ExportResult"
value
="ChartDetail"
>
</
DBSource
>
<
DBSource
name
="CP_ExportClassResult"
value
="ExportClassResult"
>
</
DBSource
>
<
DBSource
name
="CP_ExportOptionVotes"
value
="exportoptionvotes"
>
</
DBSource
>
</
ReportSources
>
<
SubReports
>
<
SubReport
name
="IT部EXCEL培训调查表Sub1"
>
</
SubReport
>
</
SubReports
>
</
report
>
这样就可以了。
问题四。 如果我的子报告中也含有子报告该怎么办?
同样在xml中描述
XmlDocument xmlDoc = new XmlDocument();
if (System.IO.File.Exists(Server.MapPath("~/CP/Report/" + blockinfo.ReportFile + ".xml")) == true)
{
xmlDoc.Load(Server.MapPath("~/CP/Report/" + blockinfo.ReportFile + ".xml"));
XmlNode xns = xmlDoc.SelectSingleNode("report");
XmlNode xn = xns.SelectSingleNode("SubReports");
if (xn != null)
{
XmlNodeList xnl = xn.ChildNodes;
foreach (XmlNode xnf in xnl)
{
XmlElement xe = (XmlElement)xnf;
string strsourcename = xe.GetAttribute("name");//显示属性值
StreamReader reportsub = File.OpenText(@Server.MapPath("~/CP/Report/" + strsourcename + ".rdlc"));
this.reportviewer1.LocalReport.LoadSubreportDefinition(strsourcename, reportsub);
reportsub.Close();
}
}
}
<?xml version="1.0" encoding="gb2312"?>
<report>
<ReportSources>
<DBSource name="CP_ExportResult" value="ChartDetail">
</DBSource>
<DBSource name="CP_ExportClassResult" value="ExportClassResult">
</DBSource>
<DBSource name="CP_ExportOptionVotes" value="exportoptionvotes">
</DBSource>
</ReportSources>
<SubReports>
<SubReport name="IT部EXCEL培训调查表Sub1">
</SubReport>
</SubReports>
</report>
先到这里,20号继续整理, 父亲节快乐各位