在中提到要支持报表模块,由于项目组这期任务需要报表功能,于是这几天把这个功能加进来了。因为没有时间重新设计开发一个C#版的报表引擎,所以现在所实现的报表模块是基于在中介绍过的我几年前写的一个delphi下的报表引擎。
本篇介绍一下在下的报表模块实现以及使用。
使用ReportModule
下面为框架以前对查询窗体QueryObject的支持,如下面代码所示生成下图:
[DefaultObject( " 3AEF18F3-50F3-4120-A0AB-0330A74FB084 " , Catalog = " 指标管理 " , ModuleType = ModuleType.Query, Index = 600 ), Label( " 技术经济指标模块 " )] [NavigateQueryType( typeof (ProjectIndicatorNavigateCriteria), Header = " 选择项目PBS " )] [QueryObject( typeof (ProjectPBSProperty))] //工程属性 [QueryObject( typeof (ProjectCostIndicator))] [ ... ] public class ProjectIndicatorQueryObject: BaseQueryObject { } 为了与查询窗体集成,编写代码方式与之前类似,如果想让【工程属性】显示为报表样式,UI如下所示,则需要更改代码使用ReportObject:
[DefaultObject( " 3AEF18F3-50F3-4120-A0AB-0330A74FB084 " , Catalog = " 指标管理 " , ModuleType = ModuleType.Query, Index = 600 ), Label( " 技术经济指标模块 " )] [NavigateQueryType( typeof (ProjectIndicatorNavigateCriteria), Header = " 选择项目PBS " )] [NotAllowEdit, NotAllowNew, NotAllowRemove] [QueryObject( typeof (ProjectPBSProperty))][QueryObject( typeof (ProjectPBSPropertyReportObject))]//工程属性 [...... ] public class ProjectIndicatorQueryObject: BaseQueryObject { } //定义包括的业务对象,如果报表包含多个业务对象,可以通过多个ReportObject来指定业务对象[ReportObject( typeof (ProjectPBSProperty))] [DefaultObject( " B9C1AB3C-CF1E-4f29-985A-9758BF125CAD " , ShowInModule = false , Index = 700 ), Label( " 工程属性报表 " )] [NotAllowEdit, NotAllowNew, NotAllowRemove] public class ProjectPBSPropertyReportObject : ReportObject { } OpenModule之ReportModule
- OpenExpressApp是一个基于对象的应用框架,所以需要考虑如何如何通过对象的方式来实现报表功能
- 对于数据来源,基于业务对象是一种方式,而以前一直使用记录Record来作为报表数据源,这个也需要提供支持
- 实现是需要重用框架的View和类库的概念,与OpenExpressApp框架进行较好的集成
- 考虑到报表模块不是框架必须的,并且现在报表模块实现中使用到的报表引擎不是开源产品,所以需要考虑在框架实现中不能影响现在框架的应用,所以报表模块将作为OpenModule中的一个模块来发布,而不是内置在OpenExpressApp框架内部。
基于以上一些目标,现在已经实现了报表模块,下面我将对实现方案进行简要描述。(注:读者需要对OpenExpressApp的查询业务对象部分有所了解。)
新增加了一个OpenModule目录,同之前示例代码一样,模块的编写一般会有一个类库,一个是与界面相关的项目,ReportModule同样需要这两个项目:
- OpenExpressApp.ReportModule.Library:报表模块相关类库,如ReportObject
- OpenExpressApp.ReportModule.WPF:报表模块UI相关,如ReportObjectView
OpenExpressApp.ReportModule.Library
- ReportObject:报表业务对象所有报表业务对象都需要从ReportObject继承下来,如下面的示例代码片段:
public class ProjectPBSPropertyReportObject : ReportObject { } - ReportObjectAttribute:报表对象的数据来源属性标签,为了便于定义业务对象数据来源,提供类库属性定义
//数据来源业务对象,约定通过业务对象的GetList方法获取数据[ReportObject(typeof(ProjectPBSProperty))] public class ProjectPBSPropertyReportObject : ReportObject { } - 重用查询业务对象QueryObject,使用ReportObject对象
[QueryObject(typeof(ProjectPBSProperty))][QueryObject(typeof(ProjectPBSPropertyReportObject))]//工程属性public class ProjectIndicatorQueryObject: BaseQueryObject { }[......] - OpenJsonObject:从SQL获取对象数据,并生成Json格式数据串由于需要与Delphi的报表引擎交互,而以前的报表引擎是基于数据集的,所以业务对象的数据进入报表时采纳了json串来进行交互。而支持SQL获取数据,也需要进行交互,所以也采纳了Json进行交互,格式定义如下: Name:表名称 Schemas: {fld1:X, fld2:X}, //X为GSPDataType Records=[{fld1:XX,fld2:xxx,fld3:xxx}, {fld1:XX,fld2:xxx,fld3:xxx}] fdtString = 0; fdtBoolean = 1; fdtDouble = 2; fdtInt = 3; fdtDateTime = 4;
- ReportDataStore:报表数据,支持业务对象数据源和SQL获取数据源
OpenExpressApp.ReportModule.WPF
- ReportObjectView:从WPFObjectView继承() ,生成ReportFram报表控件,Data绑定ReportDataStore,模块内部支持报表格式设计并自动保存(设计功能后期将作为一个Command实现,这样可以进行功能权限设定)
代码 /* ****************************************************** * * 作者:周金根 * 创建时间:20100408 * 说明:文件描述 * 版本号:1.0.0 * 报表View,指定设计样式MetaData和数据源ReportDataStore后可以Open报表 * 历史记录: * 创建文件 周金根 20100408 * ****************************************************** */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using AxReportFram; using System.Windows.Forms.Integration; using System.Reflection; using OpenExpressApp.ReportModule.Library; using System.Collections; using System.Windows.Forms; using OpenExpressApp.Module.WPF; using System.Diagnostics; namespace OpenExpressApp.ReportModule.WPF{ public class ReportObjectView : WPFObjectView { public ReportObjectView(Type type) : base (type) { this ._metaDataId = ApplicationModel.GetBusinessObjectInfo(type).Id;; } internal Guid _metaDataId; internal ReportObjectMetaData ReportObjectMetaData { get ; set ; } private ReportObjectMetaData roMetaData; public ReportObjectView() : base ( typeof (ReportObject)) { } private ReportDataStore _reportDataStore; public new ReportDataStore Data { get { return _reportDataStore; } set { Debug.Assert(value is ReportDataStore, " ReportObjectView.Data必须是ReportDataStore " ); _reportDataStore = value; ClearDataSource(); BuildData(); } } private void BuildData() { foreach (DataSourceInfo item in _reportDataStore.Datasources) { string jsonCustomers = BuildCustomersJson(item.Objects, item.Type); AddDataSource(jsonCustomers); } foreach (OpenJsonObject item in _reportDataStore.JsonDatas) { AddDataSource(item.JsonData); } } public override void RefreshCurrentObject() {} public override object CurrentObject { get { return null ; // 报表没有当前行 } set {} } private AxReportFramX _reportFram; protected override object CreateControl() { WindowsFormsHost reportHost = new WindowsFormsHost(); _reportFram = new AxReportFram.AxReportFramX(); _reportFram.OnSaveMetaData += new IReportFramXEvents_OnSaveMetaDataEventHandler(_reportFram_OnSaveMetaData); reportHost.Child = _reportFram; return reportHost; } protected virtual void OnMetaDataChanged() { // 保存MetaData到OpenExpressApp数据库 MetaData = _reportFram.XML; if ( this .MetaDataChanged != null ) { this .MetaDataChanged( this , EventArgs.Empty); } } public event EventHandler MetaDataChanged; void _reportFram_OnSaveMetaData( object sender, IReportFramXEvents_OnSaveMetaDataEvent e) { OnMetaDataChanged(); } #region 根据对象类别生成Json字符串 // 设计:转换json字符串到GSPTable 2010.03.22 // 形式如: Name:表名称 // Schemas: {fld1:X, fld2:X}, // X为GSPDataType // Records=[{fld1:XX,fld2:xxx,fld3:xxx}, {fld1:XX,fld2:xxx,fld3:xxx}] // fdtString = 0; fdtBoolean = 1; fdtDouble = 2; fdtInt = 3; fdtDateTime = 4; private string BuildCustomersJson(IList list, Type type) { StringBuilder sbJson = new StringBuilder( "" ); // 开始 sbJson.Append( " { " ); // 添加表名 sbJson.Append(String.Format( @" Name:""{0}"", " , type.Name)); // 添加字段Schema sbJson.Append( " Schemas: { " ); PropertyInfo[] propInfos = type.GetProperties(); foreach (PropertyInfo propInfo in propInfos) { sbJson.Append(String.Format( " {0}:{1}, " , propInfo.Name, PropertyTypeToDataType(propInfo))); } sbJson.Append( " }, " ); // 添加记录 sbJson.Append( " Records: [ " ); foreach (var item in list) { sbJson.Append( " { " ); foreach (PropertyInfo propInfo in propInfos) { object value = propInfo.GetValue(item, null ); if (value == null ) continue ; string strValue = "" ; if (( typeof (String) == propInfo.PropertyType) || ( typeof (Guid) == propInfo.PropertyType)) strValue = " \ "" + value.ToString() + " \ "" ; else if ( typeof (Boolean) == propInfo.PropertyType) strValue = Convert.ToInt16(value).ToString(); else strValue = value.ToString(); sbJson.Append(String.Format( " {0}:{1}, " , propInfo.Name, strValue)); } sbJson.Append( " }, " ); } sbJson.Append( " ] " ); // 末尾 sbJson.Append( " } " ); return sbJson.ToString(); } // fdtString = 0; fdtBoolean = 1; fdtDouble = 2; fdtInt = 3; fdtDateTime = 4; private int PropertyTypeToDataType(PropertyInfo propInfo) { if ( typeof (String) == propInfo.PropertyType) return 0 ; else if ( typeof (Boolean) == propInfo.PropertyType) return 1 ; else if ( typeof (Double) == propInfo.PropertyType) return 2 ; else if ( typeof ( int ) == propInfo.PropertyType) return 3 ; else if ( typeof (DateTime) == propInfo.PropertyType) return 4 ; else return 0 ; } #endregion #region 封装报表控件 public string MetaData { get { return ReportObjectMetaData.MetaData; } set { ReportObjectMetaData.MetaData = value; ReportObjectMetaData.Save(); } } public void AddDataSource( string json) { _reportFram.AddDataSource(json); } public void OpenReport() { if ( ! String.IsNullOrEmpty(MetaData)) _reportFram.XML = MetaData; _reportFram.OpenReport( new Guid(), false ); } public void ClearDataSource() { _reportFram.ClearDataSource(); } #endregion }} ReportObjectMetaData是一个内置的保存报表视图设计格式的一个业务对象,在数据库OpenExpressApp中对应表ReportObjectMetaData,其中自动Id为ReportObject业务对象的对象Id,MetaData为报表设计样式的XML格式字符串。
- 与OpenExpressApp的QueryForm模板窗口集成
在QueryFormController.cs中根据QueryObjectAttribute来生成相应的Tab页签,通过以下代码红色部分内容,调用业务对象类型默认生成的视图生成器来生成ReportObjectView
private void CreateTabItem(QueryObjectAttribute queryObjInfo) { Type type = queryObjInfo.ObjectType; // 生成View和Controller WPFObjectView view = null ; if (ViewType.DetailView == queryObjInfo.ViewType) { // 生成DetailView } else { // 根据对象类型自动生成View view = DefaultViewCreator.Create(type); if (view == null ) { // 生成ListView } } DefaultViewCreator是OpenExpressApp框架内部的一个全局注册类,通过 Register方法可以注册特定离诶性能过的业务对象视图生成器
代码 namespace OpenExpressApp.Module.WPF{ public static class DefaultViewCreator { /// <summary> /// 第一个参数Type:业务对象类型 /// 第二个参数Type:注册生成WPFObjectView类型 /// </summary> private static Dictionary < Type, ICreateDefaultView > _creatorMap = new Dictionary < Type, ICreateDefaultView > (); public static void Register(Type boType, ICreateDefaultView creatorType) { _creatorMap.Add(boType, creatorType); } public static WPFObjectView Create(Type type) { foreach (var item in _creatorMap) { if (item.Key.IsAssignableFrom(type)) return item.Value.CreateView(type); } return null ; } } public interface ICreateDefaultView { WPFObjectView CreateView(Type boType); }} 在ReportModule模块装载代码中,加入注册ReportObject与ReportObjectView的生成对应
代码 public class ReportWPFModule : AdaptCommandModule { public override void Initialize() { base .Initialize();DefaultViewCreator.Register( typeof(ReportObject), new CreateDefaultReportView()); } } 代码 internal class CreateDefaultReportView : ICreateDefaultView { #region ICreateDefaultView Members public WPFObjectView CreateView(Type boType) { // 生成DetailView var view = new ReportObjectView(boType); view.DataLoader = new ReportObjectViewController(view); return view; } #endregion } 其中用到了ReportObjectViewController,这是一个获取数据打开报表的一个视图控制类,与OpenExpressApp的ViewController功能类似
代码 namespace OpenExpressApp.ReportModule.WPF{ internal class ReportObjectViewController : ViewDataLoaderBase, IControlWrapper { public ReportObjectViewController(ReportObjectView view) : base (view) { } public new ReportObjectView View { get { return base .View as ReportObjectView; } } protected override string FactoryMethod { get { return " GetList " ; } } private Type GetQueryType(Type entityType) { var assembly = entityType.Assembly; var typeName = entityType.FullName; return assembly.GetType(typeName + " List " ) ?? assembly.GetType(typeName + " s " ); } public override void AsyncGetObject( string getListMethod, params Object[] getListParam) { ReportDataStore rds = new ReportDataStore(); ReportObject ro = Activator.CreateInstance(View.BOType) as ReportObject; // 添加业务对象数据源 foreach (var bo in ro.BusinessObjects) { using ( this ._dataProvider.DeferRefresh()) { this ._dataProvider.IsAsynchronous = false ; this ._dataProvider.ObjectType = this .GetQueryType(bo); this ._dataProvider.FactoryMethod = getListMethod; this ._dataProvider.FactoryParameters.Clear(); foreach (var item in getListParam) { this ._dataProvider.FactoryParameters.Add(item); } } rds.AddDataSource(_dataProvider.Data as IList, bo); } // 添加业Sql数据源(现在为分批获取,以后改为打包获取数据,减少网络交互次数) foreach (var bo in ro.SqlObjects) { string sql = bo.Value; // todo:替换参数,根据过滤条件生成最终Sql rds.AddJsonData(OpenJsonObject.GetBySql(sql, bo.Key, null )); } // 延迟获取元数据,在这里装载MetaData if (View.ReportObjectMetaData == null ) { if (ReportObjectMetaData.Exists(View._metaDataId)) { View.ReportObjectMetaData = ReportObjectMetaData.Get(View._metaDataId); } else { View.ReportObjectMetaData = ReportObjectMetaData.New(); View.ReportObjectMetaData.Id = View._metaDataId; } } if ( ! (View.Control as FrameworkElement).IsLoaded) (View.Control as FrameworkElement).Loaded += delegate ( object sender, RoutedEventArgs e) { View.Data = rds; View.OpenReport(); }; else { View.Data = rds; View.OpenReport(); } } #region IControlWrapper Members public object Control { get { return View.Control; } } #endregion protected override Type FindQueryType() { throw new NotImplementedException(); } }}
更多内容:
欢迎转载,转载请注明:转载自 [ ]