信息系统开发平台OpenExpressApp - 报表模块支持ReportObjectView
在信息系统开发平台OpenExpressApp - 框架待完善工作事项中提到要支持报表模块,由于项目组这期任务需要报表功能,于是这几天把这个功能加进来了。因为没有时间重新设计开发一个C#版的报表引擎,所以现在所实现的报表模块是基于在.Net下如何跨语言调用Delphi写的报表引擎中介绍过的我几年前写的一个delphi下的报表引擎。
本篇介绍一下在OpenExpressApp下的报表模块实现以及使用。
使用ReportModule
- 之前的查询窗体的工程属性UI:列表视图
下面为框架以前对查询窗体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的查询业务对象部分有所了解。)
- Solution结构以及主要类库介绍
新增加了一个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继承(理解核心元素ObjectView) ,生成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();
}
}
}
更多内容: 开源信息系统开发平台之OpenExpressApp框架.pdf
欢迎转载,转载请注明:转载自周金根 [ http://zhoujg.cnblogs.com/ ]