我们知道,很多控件可以通过右键菜单“属性”调出自定义属性编辑器(F4调出的是控件的属性浏览器)。其实,只要为类AMoneyDesigner指定[Editor(typeof(CustomEditor), typeof(ComponentEditor))]属性,并通过重写CustomEditor类(继承自 System.ComponentModel.ComponentEditor类)的EditComponent方法调用窗体AMoneyProperties就可以了,窗体AMoneyProperties是实际上的与报表设计者进行交互的自定义属性编辑器。篇幅原因,请参考本文后面提供的示例下载中的文件CustomEditor.cs和AMoneyProperties.cs。本文示例的自定义属性编辑器的界面如图8所示。
![](https://i-blog.csdnimg.cn/blog_migrate/5524eb5703bbd9aec3635d01da8ab555.png)
图8 设计时组件的自定义属性编辑器
需要指出的是,在自定义属性编辑器中,我们设置自定义报表项的属性时,可以调用在报表设计环境中随处可见的表达式编辑器(如图9所示)。代码7展示了对图8中的“金额字段”下拉列表的取值调用表达式编辑器进行编辑的处理。
代码7:使用表达式编辑器编辑运行时组件的属性
private void cmbMoneyValue_SelectedIndexChanged(object sender, EventArgs e)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
//用户选择了下拉列表中的“<表达式
>”并且是否加在表达式编辑器为true
if (this.cmbMoneyValue.SelectedIndex == 0 && this.m_launchEditor)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
this.m_launchEditor = false;
ExpressionEditor&nb sp;editor = new ExpressionEditor();
string newValue = (string)editor.EditValue(null, this.designerComponent.Site, this.m_oldComboValue);
this.cmbMoneyValue.Items[0] = newValue;
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/4cfb04a3e6437ebae4ea7d99b061244f.png)
图9 调用“表达式编辑器”对图8中的“金额字段”的取值进行编辑(点击小图看大图)
在上面我们提到过,自定义报表项可以“延伸现有控件的功能来扩展现有的 RDL”,还提到过“设计时组件继承自 Microsoft.ReportDesigner.CustomReportItemDesigner类”,那么既然设计时组件无法从现有RDL支持的报表项进行继承,又怎么实现“延伸现有控件的功能”呢?其实,这是Microsoft在玩的一个文字游戏,其实际含义是在现有报表项和自定义报表项之间进行“转换” 。我们知道在Access的窗体设计器中,假如我们选定一个文本框控件的话,我们可以使用它的右键菜单将控件“更改”为标签、列表框或组合框这三种控件中的任何一种(如图10所示)。自定义报表项“延伸现有控件的功能”与此类似:你开发了一个自定义报表项(比如本文的会计金额显示),该自定义报表项的功能和某一现有的标准报表项(比如文本框)的功能相似,那么如果你提供了在报表设计器中将现有的标准报表项转换为自定义报表项的功能,就可以说该自定义报表项延伸了该标准报表项的功能。无论是自定义报表项还是现有的标准报表项在RDL中都表现为一个XML片断,报表项之间的转换意味着两个XML片断(事实上程序处理过程中面对的不止是片断,而是一个完整的XML文件)之间的转换,这时我们可以借助XSLT来实现这种转换操作。由于本文的会计金额显示报表项与文本框控件的功能相似,所以需要允许报表设计者将报表中的一个文本框控件转换为一个会计金额显示控件,需要用到代码8所示的XSLT文件。
![](https://i-blog.csdnimg.cn/blog_migrate/3e36a956bb1ec769f17831904f2c8bd2.png)
图10 Access窗体编辑器中控件的转换
代码8:TextboxConverter.xslt
<?xml version="1.0" encoding="utf- 16" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition"
xmlns:rdl="http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition">
<xsl:output omit-xml-declaration="no"/>
<xsl:template match="/rdl:Textbox">
<CustomReportItem>
<Type>AMoney</Type>
<Top><xsl:value-of select="rdl:Top"/></Top>
<Left><xsl:value-of select="rdl:Left"/></Left>
<Height><xsl:value-of select="rdl:Height"/></Height>
<Width><xsl:value-of select="rdl:Width"/></Width>
<CustomProperties>
<CustomProperty>
<Name>AMoney:FractionColor</Name>
<Value>255</Value>
</CustomProperty>
<CustomProperty>
<Name>AMoney:KiloColor</Name>
<Value>32768</Value>
</CustomProperty>
<CustomProperty>
<Name>AMoney:NormalColor</Name>
<Value>13882323</Value>
</CustomProperty>
<CustomProperty>
<Name>AMoney:LineBorderWidth</Name>
<Value>1</Value>
</CustomProperty>
<CustomProperty>
<Name>AMoney:LineSpacing</Name>
<Value>14</Value>
</CustomProperty>
<CustomProperty>
<Name>AMoney:MoneyValue</Name>
<Value><xsl:value- of select="rdl:Value"/></Value>
</CustomProperty>
</CustomProperties>
</CustomReportItem>
</xsl:template>
</xsl:stylesheet>
这个XSLT文件是比较简单的,主要功能是取得文本框控件中可以用到的属性值并在合适的位置指定给会计金额显示控件,需要将TextboxConverte.xslt文件的生成操作设置为“嵌入的资源”。有了XSLT文件,我们还需要一个用于实际转换操作的类 AMoneyConverter,该类从接口Microsoft.ReportDesigner.Design.IReportItemConverter继承,并需要实现Convert()方法,具体代码限于篇幅原因请参考本文提供的下载中的文件AMoneyConverter.cs。
好了,一个功能完备的自定义报表项的设计时组件的实现就介绍完了,图11展示了设计时组件的架构。
![](https://i-blog.csdnimg.cn/blog_migrate/48b1026212ebd7895487a05f1f86da61.png)
图11 设计时组件架构(改自http://msdn2.microsoft.com/en-us/library/ms345219.aspx)
二、运行时组件
好了,目前已经可以使用这个设计时组件进行报表的设计了,一个结构完备的基于XML的RDL文件将交由报表处理器进行处理,但是报表处理器只能从报表定义中获取到自定义报表项的相关属性,但无法从中获取向用户呈现报表的方法。自定义报表项的呈现工作应当是由自定义报表项的运行时组件来完成的。当报表处理引擎在报表定义中发现了一个带有特定<Type>标记的 <CustomReportItem>元素时,就需要加载该CustomReportItem的运行时组件并向其传递一个CustomReportItem对象,然后由运行时组件完成呈现报表的操作,图12可以帮助理解这一过程。我们在上面(图2)提到的“Report Processing Extension”其实指的就是自定义报表项的运行时组件。
![](https://i-blog.csdnimg.cn/blog_migrate/2f749f1023695d1b69d77f7ade13efa4.png)
图12 运行时组件架构(翻译自http://msdn2.microsoft.com/en-us/library/ms345219.aspx)
与本文示例会计金额显示的设计时组件对应的运行时组件的代码如下:
代码9:运行时组件
![](http://writeblog.csdn.net/Images/OutliningIndicators/ContractedBlock.gif)
using 指令#region using 指令
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.ReportingServices.ReportRendering;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections.Specialized;
#endregion
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
namespace Waxdoll.ReportingServices
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ContractedBlock.gif)
{
public class AMoneyCRI : ICustomReportItem
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
private CustomReportItem m_CustomReportItem;
private Microsoft.ReportingServices.ReportRendering.Image m_AMoneyImage;
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
//存储由报表引擎传递过来的自定义报表项的属性值
public CustomReportItem CustomItem
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
set
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
m_CustomReportItem = value;
}
}
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
//SQL Server 2005中并无实现,返回null
public Action Action
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
get
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
return null;
}
}
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
//返回已呈现的自定义报表项
public ReportItem RenderItem
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
get
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (m_AMoneyImage == null)
Process();
return m_AMoneyImage;
}
}
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
/// 向用户呈现报表的主要处理,访问设计时组件生成的报表定义,创建由RenderItem属性返回的自定义报表项
/// </summary>
/// <returns>返回ChangeType.None</returns>
public ChangeType Process()
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
//如果将要进行的处理使用的是绝对长度,请指定和自己设备相对应的DPI值
//1dpi=1像素点/1英寸
int dpi = 96;
int imageWidth = (int)(this.m_CustomReportItem.Width.ToInches() * dpi);
int imageHeight = (int)(this.m_CustomReportItem.Height.ToInches() * dpi);
Bitmap image = new Bitmap(imageWidth, imageHeight);
image.SetResolution(dpi, dpi);
Graphics graphics = Graphics.FromImage(image);
graphics.Clear(Color.White);
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
//自定义属性的获取与处理
Color clrFraction = ColorTranslator.FromWin32(Convert.ToInt32(LookupCustomProperty(this.m_CustomReportItem.CustomProperties, "AMoney:FractionColor", "255")));
Color clrKilo = ColorTranslator.FromWin32(Convert.ToInt32(LookupCustomProperty(this.m_CustomReportItem.CustomProperties, "AMoney:KiloColor", "32768")));
Color clrNormal = ColorTranslator.FromWin32(Convert.ToInt32(LookupCustomProperty(this.m_CustomReportItem.CustomProperties, "AMoney:NormalColor", "13882323")));
int intLineBorderWidth = Convert.ToInt32(LookupCustomProperty(this.m_CustomReportItem.CustomProperties, "AMoney:LineBorderWidth", 1));
int intLineSpacing = Convert.ToInt32(LookupCustomProperty(this.m_CustomReportItem.CustomProperties, "AMoney:LineSpacing", 14));
string strMoney = LookupCustomProperty(this.m_CustomReportItem.CustomProperties, "AMoney:MoneyValue", "亿千百十万千百十元角分").ToString();
if (strMoney != "亿千百十万千百十元角分")
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
decimal decMoney = Convert.ToDecimal(strMoney);
decMoney = Math.Round(2);
strMoney = decMoney.ToString("#.00");
strMoney = strMoney.Replace(".", string.Empty);
strMoney = "¥" + strMoney;
}
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
int intLoop = 1;
float fltFontSize = 9.0f;
while (imageWidth - intLoop * (intLineSpacing + intLineBorderWidth) > 0)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
//输出strMoney的值
if (intLoop <= strMoney.Length)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
using (SolidBrush sBrush = new SolidBrush(Color.Black))
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
using (Font sFont = new Font("宋体", fltFontSize))
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
graphics.DrawString(strMoney.Substring(strMoney.Length - intLoop, 1),sFont, sBrush, (float)(imageWidth - (intLoop * intLineBorderWidth + (intLoop - 0.5) * intLineSpacing + fltFontSize / 2)), (float)(imageHeight - fltFontSize) / 2);
}
}
}
//画线
if (intLoop == 1)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
using (Pen lPen = new Pen(clrNormal, intLineBorderWidth))
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
graphics.DrawLine(lPen, imageWidth - intLoop * (intLineSpacing + intLineBorderWidth), 0, imageWidth - intLoop * (intLineSpacing + intLineBorderWidth), imageHeight);
}
}
else if (intLoop == 2)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
using (Pen lPen = new Pen(clrFraction, intLineBorderWidth))
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
graphics.DrawLine(lPen, imageWidth - intLoop * (intLineSpacing + intLineBorderWidth), 0, imageWidth - intLoop * (intLineSpacing + intLineBorderWidth), imageHeight);
}
}
else
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if ((intLoop - 2) % 3 == 0)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
using (Pen lPen = new Pen(clrKilo, intLineBorderWidth))
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
graphics.DrawLine(lPen, imageWidth - intLoop * (intLineSpacing + intLineBorderWidth), 0, imageWidth - intLoop * (intLineSpacing + intLineBorderWidth), imageHeight);
}
}
else
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
using (Pen lPen = new Pen(clrNormal, intLineBorderWidth))
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
graphics.DrawLine(lPen, imageWidth - intLoop * (intLineSpacing + intLineBorderWidth), 0, imageWidth - intLoop * (intLineSpacing + intLineBorderWidth), imageHeight);
}
}
}
intLoop++;
}
//最外面的矩形框
using (Pen rPen = new Pen(clrNormal, intLineBorderWidth))
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
graphics.DrawRectangle(rPen, 0, 0, imageWidth - intLineBorderWidth, imageHeight - intLineBorderWidth);
}
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
//将呈现后的图片复制给Microsoft.ReportingServices.ReportRendering.Image类型
this.m_AMoneyImage = new Microsoft.ReportingServices.ReportRendering.Image(this.m_CustomReportItem.Name, this.m_CustomReportItem.ID);
System.IO.MemoryStream stream = new System.IO.MemoryStream();
image.Save(stream, ImageFormat.Png);
this.m_AMoneyImage.ImageData = new byte[stream.Length];
stream.Seek(0, System.IO.SeekOrigin.Begin);
stream.Read(this.m_AMoneyImage.ImageData, 0, (int)stream.Length);
this.m_AMoneyImage.MIMEType = "image/png";
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
//SQL Server 2005中必须返回ChangeType.None。
return ChangeType.None;
}
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
/// 获取自定义报表项的自定义属性
/// </summary>
/// <param name="properties">this.m_CustomReportItem.CustomProperties</param>
/// <param name="name">自定义属性名称</param>
/// <param name="defaultvalue">如果自定义属性不存在或返回为空时的默认值</param>
/// <returns></returns>
private object LookupCustomProperty(CustomPropertyCollection properties, string name, object defaultvalue)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
if (properties == null)
return defaultvalue;
CustomProperty property = properties[name];
if (property == null)
return defaultvalue;
if ((property.Value.GetType() == typeof(String)) && (string)property.Value == "")
return defaultvalue;
return property.Value;
}
}
}
在代码9中,我们看到,运行时组件从Microsoft.ReportingServices.ReportRendering.ICustomReportItem接口继承。如果正在运行的报表中被发现存在一个<CusteomReportItem>标记,与该自定义报表项对应的运行时组件的一个进程就会被创建,紧接着运行时组件的CustomItem属性将被设置,Process()方法将被调用,Process()方法使用GDI+将在设计时组件中设置好的自定义报表项的属性呈现为一个图片,紧接着将其转换为一个Microsoft.ReportingServices.ReportRendering.Image类型,并将其通过可以返回一个已经呈现好的自定义报表项的运行时组件的ReportItem属性交给报表处理引擎,至此,报表处理引擎解决了一个自定义报表项的呈现,唱着欢快的歌儿奔向远方了……
本文示例自定义报表项下载
如果你下载了上面链接中的示例,你可能会发现:首先,压缩包里有2个.bat批处理文件和3个.config配置文件,其次,解决方案中有两个孤立的项目,项目AmoneyDesigner中有几个类也是相互独立的。呵呵,不用奇怪,其实本文还没有结束,解决方案生成的只是两个dll而已,Visual Studio和SSRS并不知道如何使用这两个dll进行自定义报表项的处理。下面结合本文的示例讲一下自定义报表项的配置和部署,配置和部署步骤如下:
1、双击AMoneyCRI.sln打开解决方案,Ctrl+Shift+B生成解决方案(有趣的是/bin/debug文件夹里竟然有 DundasWebChart.dll、DundasWinChart.dll等文件,Microsoft和Dundas的合作还真是紧密,呵呵……);
2、用记事本打开X:/Program Files/Microsoft SQL Server/MSSQL.3/Reporting Services/ReportServer/rsreportserver.config,复制文件开始出<Dsn>标签中的字符串;打开本例中的rsreportserver.config文件,粘贴字符串到<Dsn>标签内;
3、打开本例中的文件rssrvpolicy.config,搜索并替换“C:/Program Files/Microsoft SQL Server/MSSQL.3 /Reporting Services/ReportServer/bin/AMoneyCRI.dll”为“X:/Program Files/Microsoft SQL Server/MSSQL.3/Reporting Services/ReportServer/bin/AMoneyCRI.dll”;
4、用记事本打开批处理文件Configure.bat,搜索并替换“E:/Program Files/Microsoft Visual Studio 8/Common7 /IDE/PrivateAssemblies”为“Y:/Program Files/Microsoft Visual Studio 8/Common7/IDE/PrivateAssemblies”;搜索并替换 “C:/Program Files/Microsoft SQL Server/MSSQL.3/Reporting Services/ReportServer”为“X:/Program Files/Microsoft SQL Server/MSSQL.3/Reporting Services/ReportServer”;
5、用记事本打开批处理文件Deploy.bat,搜索并替换“E:/Program Files/Microsoft Visual Studio 8/Common7 /IDE/PrivateAssemblies”为“Y:/Program Files/Microsoft Visual Studio 8/Common7/IDE/PrivateAssemblies”;搜索并替换 “C:/Program Files/Microsoft SQL Server/MSSQL.3/Reporting Services/ReportServer/bin”为“X:/Program Files/Microsoft SQL Server/MSSQL.3/Reporting Services/ReportServer/bin”;
6、双击执行Configure.bat和Deploy.bat这两个批处理文件;
7、如果需要在BIDS中报表设计器的工具栏上看到“会计金额”工具,请在工具栏上“选择项...”,定位到 Y:/Program Files/Microsoft Visual Studio 8/Common7/IDE/PrivateAssemblies并选择AMoneyDesigner.dll即可。
(注意:“X:”代表SSRS的安装驱动器的盘符,“Y:”代表Visual Studio的安装驱动器的盘符。)
重要:请严格按照以上步骤部署本文的示例!
那么,为什么要这样配置和部署呢?
步骤2中,rsreportserver.config文件是报表服务器的配置文件,由于<Dsn>标签内存储了基于安全理由加密的连接字符串,故而应该更改成自己机器上的字符串,否则报表管理器和报表服务器运行不起来不要找我,呵呵,实在不行还是可以找我的;由于我们需要告知报表服务器如何处理自定义报表项,所以我们在报表服务器中加入了以下配置信息:
<
ReportItems
>
<
ReportItem
Name
="AMoney"
Type
="Waxdoll.ReportingServices.AMoneyCRI, AMoneyCRI"
/>
</
ReportItems
>
步骤3中,rssrvpolicy.config是报表服务器的安全配置文件,为了使报表服务器具有访问自定义报表项的运行时组件,我们需要在其中添加以下配置信息:
<
CodeGroup
class
="UnionCodeGroup"
version
="1"
PermissionSetName
="FullTrust"
Description
="This code group grants AMoneyCRI.dll FUllTrust permission. "
>
<
IMembershipCondition
class
="UrlMembershipCondition"
version
="1"
Url
="C:/Program Files/Microsoft SQL Server/MSSQL.3 /Reporting Services/ReportServer/bin/AMoneyCRI.dll"
/>
</
CodeGroup
>
另外,示例中的另外一个配置文件RSReportDesigner.config用于存储有关可用于报表设计器的扩展插件的设置,我们在其中添加了如下配置信息分别用于告知报表设计器我们添加了何种自定义报表项,该报表项使用何种设计器以及报表设计器中文本框控件通过什么向该自定义报表项进行转换。
<
ReportItems
>
<
ReportItem
Name
="AMoney"
Type
="Waxdoll.ReportingServices.AMoneyCRI, AMoneyCRI"
/>
</
ReportItems
>
<
ReportItemDesigner
>
<
ReportItem
Name
="AMoney"
Type
="Waxdoll.ReportingServices.AMoneyDesigner, AMoneyDesigner"
/>
</
ReportItemDesigner
>
<
ReportItemConverter
>
<
Converter
Source
="Textbox"
Target
="AMoney"
Type
="Waxdoll.ReportingServices.AMoneyConverter, AMoneyDesigner"
/>
</
ReportItemConverter
>
步骤4、5中的两个批处理文件分别用于复制修改后的配置文件以及解决方案生成的dll文件和pdb文件到指定的目录中。
好了,配置部署完毕,来看一下,自定义报表项的运行情况(测试Demo)。
![](https://i-blog.csdnimg.cn/blog_migrate/ecff14c86ca91dda591cac158a305256.png)
图13 包含自定义报表项的报表的运行情况
好了,到目前为止,一切都结束了。这篇超长随笔的目的之一是为了让大家更清楚地了解SSRS的功能,其实功能是无限扩展的,这是任何轻量级报表工具所无法比拟的;另外一个目的无非是抛砖引玉,我相信,随着对报表设计的需要,一定会有非常好的报表扩展插件和自定义报表项被我们中国人所开发出来,因为中国的报表难啊!