zoukankan      html  css  js  c++  java
  • 信息系统开发平台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  

    • 总体目标
    1. OpenExpressApp是一个基于对象的应用框架,所以需要考虑如何如何通过对象的方式来实现报表功能
    2. 对于数据来源,基于业务对象是一种方式,而以前一直使用记录Record来作为报表数据源,这个也需要提供支持
    3. 实现是需要重用框架的View和类库的概念,与OpenExpressApp框架进行较好的集成
    4. 考虑到报表模块不是框架必须的,并且现在报表模块实现中使用到的报表引擎不是开源产品,所以需要考虑在框架实现中不能影响现在框架的应用,所以报表模块将作为OpenModule中的一个模块来发布,而不是内置在OpenExpressApp框架内部。  

    基于以上一些目标,现在已经实现了报表模块,下面我将对实现方案进行简要描述。(注:读者需要对OpenExpressApp的查询业务对象部分有所了解。

    • Solution结构以及主要类库介绍

    新增加了一个OpenModule目录,同之前示例代码一样,模块的编写一般会有一个类库,一个是与界面相关的项目,ReportModule同样需要这两个项目:

    1. OpenExpressApp.ReportModule.Library:报表模块相关类库,如ReportObject
    2. 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/ ]

  • 相关阅读:
    Django_05_模板
    Django_04_视图
    Django_03_后台管理
    Django_02_创建模型
    Django_01_创建图书管理项目
    Djang简介
    day_03比特币转账的运行原理
    day_02比特币的转账机制及其7个名词
    day01_人类社会货币的演变
    Socket问题
  • 原文地址:https://www.cnblogs.com/zhoujg/p/1737146.html
Copyright © 2011-2022 走看看