前不久接到北方区一个个案, 比较有个性, 总结了一下大家以后碰到就有参考拉
一.开发需求
规格如下:
程序编号 : CrystalReport.exe
程序名称 : 义利报表
画面:(备注:应用单选按钮)
页签一:销售报表 | 页签二:财务报表 | 页签三 ……(根据目录动态取)
销售额对比表 服务中心销售对比表 表3 ...... (根据报表文件动态取)
……. ……. …….
……. ……. …….
……. ……. …….
查询 取消
信息处理:
1.
通过CrystalReport.ini文件去配置报表路径、服务器IP、数据库用户名、密码和数据库名称。
2.
查询:根据单选按钮选择的报表,调用对应的水晶报表查询。
3.
取消:退出查询界面。
|
二.解决过程
1.
沟通:
看了之后我比较茫然, 只知道要实现exe调用水晶报表, 说实话当时连水晶报表是啥东东长什么模样都不知道.后来与北方区规格撰写人曹铃铃多次电话沟通后才得知, 客户的需求是:
该客户(义利)一直在使用水晶报表制作相关报表, 也许是水晶报表价格忒贵, 所以只有一个用户, 这样的话看报表的所有人都权限一样(最大权限), 都可以随意编辑保存报表, 并且要看一张报表还要打开水晶报表主程序, 比较麻烦. 因此产生一个问题, 报表文件的管理难, 做这个个案就是给浏览报表的人一个报表入口, 可以浏览报表而不能编辑.
知道这个需求后, 我联系了易拓负责水晶报表的朋友, 了解到水晶报表其实是一个个的RPT后缀的报表文件, 报表逻辑全封装在里面, 要想脱离水晶报表环境预览它并不容易, 并且什么它的数据源连接方式也还不知道, 采用”推”模式还是”拉”模式也不知道, 具体相关的水晶报表元件是什么他也不了解……不过他告诉我用.NET开发兼容性会更好, 听的我一头雾水啊……
2.
知识储备:
既然如此, 还是让曹铃铃把水晶报表安装程序发过来安装玩玩吧, 于是花了几个小时的时间从北方区传来安装程序并安装了, 并让她帮忙做一个简单的明细表给我, 方便我这边了解及测试. 看到水晶报表的真面目后发现它其实和SQL 2005的ReportService里的报表非常类似: 建一个报表前要建一个数据库连接并创建数据源并绑定当前报表, 然后往报表界面添加相关对象, 保存后是一个*.rpt文件. 哦, 于是上网搜索了一番总算有所得, delphi也是可以调用水晶报表的, 可惜找了很长时间都没有找到delphi5调用Crystal Repots XI的, 可能是delphi版本太老了, Crystal Repots XI又太新了, 郁闷拉. 不过一想, 这个个案和易飞没有关系, 和delphi也谈不上关系啊, 只要能调起Crystal Repots XI无论咋实现都行啊, 搜啊搜, 找到好多vs2005的调用方法, 不过太乱了, 毕竟Crystal Repots的版本太多了, 不同的版本调用方法也不尽一样, 下面整理了vs2005调用Crystal Repots XI的
J
水晶报表在应用时分两种方法,分别是拉模式(PULL)、推模式(PUSH)。
拉模式:在水晶报表生成时的数据源是从水晶报表文件中的SQL语句从数据库中提取的,在编程时不用重写SQL语句,但要加上登录信息(具体方法,后面介绍)。推模式:在水晶报表生成时的数据源,是用编程时重写水晶报表中SQL语句而生成的dataset对像。也就是说,推模式是用dataset组装水晶报表。水晶报表组件介绍。水晶报表在VS2005中有两种组件,在WEB项目是分别是CrystalReportSource,CrystalReportViewer。在FORM项目里是分别是crystalReport,CrystalReportViewer。CrystalReportSource,crystalReport是水晶报表的数据提供者;CrystalReportViewer是水晶报表的浏览器。
不论是推模式还是拉模式都应使用ReportDocument和ReportViewer两个控件完成报表的数据加载和数据展现。
这样可以将表示层和基础业务逻辑分开, ReportDocument解决报表的封装,CrystalReportView解决报表的展现。
还有一种做法是使用CrystalReportViewer和CrystalReportSource控件, CrystalReportViewer的ReportSourceID属性指定数据的来源, CrystalReportSource控件指定rpt模板,如果模板中设置了过滤参数,可以用ControlID和界面中的数据选择控件关联,这种方法和sqldatasource很相似,但是这种使用方式会丧失灵活性,逻辑层次也不明确,和对sqldatasource一样,不推荐使用。
方式一:拉
在拉模式中如要在水晶报表中的SQL语句加上条件参数时要用{?参数名}方式给出。例:“SELECT T1, T2, T3 FROM T Where T1='{?parm}'” parm就是参数名, 以下例子中所用到的水晶报表文件中使用的SQL语句是“SELECT T1, T2, T3 FROM T Where T1='{?parm}'” parm就是参数名。
【1.WEB方式下】
using
CrystalDecisions.Shared;
using
CrystalDecisions.CrystalReports.Engine;
protected
void
Button_pull_Click(
object
sender, EventArgs e)

{
//连接报表文件
CrystalReportSource1.ReportDocument.Load(Server.MapPath("CrystalReport.rpt"));

//设置登陆信息
CrystalReportSource1.ReportDocument.SetDatabaseLogon("name", "password", “ServerName”, “DbName”);

//传递参数
CrystalReportSource1.ReportDocument.SetParameterValue("Title", "测试报表");
CrystalReportSource1.ReportDocument.SetParameterValue("Parm", "1");
//绑定水晶报表数据源。
CrystalReportSource1.DataBind();

//给该浏览器赋上对像
CrystalReportViewer1.ReportSource = CrystalReportSource1;
CrystalReportViewer1.DataBind();
}
【2.FORM方式下】
注意:在FORM方式下代码同WEB方式,但用crystalReport控件换掉了CrystalReportSource;用crystalReportViewer换掉了CrystalReportViewer;这两个控件都可以在工具箱里找到。同时在编程时去掉DataBind()方法。
private
void
Form1_Load(
object
sender, EventArgs e)

{
//连接报表文件
ReportDocument.Load(Application.StartupPath + "CrystalReport.rpt");

//设置登陆信息
ReportDocument.SetDatabaseLogon("name", "password", “ServerName”, “DbName”)

//传递参数
ReportDocument.SetParameterValue("Title", "测试报表");
ReportDocument.SetParameterValue("Parm", "1");

//下面是给该水晶报表浏览器赋上对像
CrystalReportViewer.ReportSource =ReportDocument;
}
方式二:推
在推模式中编程组装的Dataset里的SQL语句中的字段要与水晶报表里的SQL语句字段一致。简单的说,推模式中的水晶报表是个模板,把在设计器里报表的格式设好后,再组装DataSet就可以生成报表了。
方法二里报表的数据来源已经不是报表模板使用拉模式获得了,是推模式的一种实现
。
这样实现的好处是这里的sql是由页面输入条件动态构造出的, 可以实现按条件的动态查询。不使用强类型dataset,用datatable非常方便。容易实现的优点从安全性上看就变成了缺点,页面输入拼装sql,安全性不高啊。如果涉及多个表,sql很长构造麻烦的情况,使用第二种可能不是很方便。
【1.WEB方式下】
using
CrystalDecisions.Shared;
using
CrystalDecisions.CrystalReports.Engine;
using
System.Data.SqlClient;
protected
void
Button_push_Click(
object
sender, EventArgs e)

{
string sql = "SELECT T1, T2, T3 FROM T where T1='a'";
string DBConfig_sql =@"Data Source=chengjiea;Initial Catalog= ERP;UserID=sa;Password=518518";
DataSet ds = new DataSet();
SqlConnection sqlCon = new SqlConnection(DBConfig_sql);
SqlCommand sqlCmd = new SqlCommand(sql, sqlCon);
SqlDataAdapter sqlAd = new SqlDataAdapter();
sqlAd.SelectCommand = sqlCmd;
sqlAd.Fill(ds, "sql");
CrystalReportSource1.ReportDocument.Load(Server.MapPath("CrystalReport.rpt"));
//注意此处必需指明Dataset中的表的名称,否则会提示“您请求的报表需要更多信息.”
CrystalReportSource1.ReportDocument.SetDataSource(ds.Tables["sql"]);
//{?}中的参数可以不用赋值,即使赋了值也不起作用。
//CrystalReportSource1.ReportDocument.ParameterFields["Parm"].CurrentValues.AddValue("123");
CrystalReportSource1.ReportDocument.ParameterFields["Title"].CurrentValues.AddValue("报表样例!");
CrystalReportSource1.DataBind();
CrystalReportViewer1.ReportSource = CrystalReportSource1;
CrystalReportViewer1.DataBind();
}
【2.FORM方式下】
private
void
Form1_Load(
object
sender, EventArgs e)

{
string sql = "SELECT T1, T2, T3 FROM T where T1='a'";
string DBConfig_sql = @"Data Source=chengjiea;Initial Catalog= ERP;UserID=sa;Password=518518";
DataSet ds = new DataSet();
SqlConnection sqlCon = new SqlConnection(DBConfig_sql);
SqlCommand sqlCmd = new SqlCommand(sql, sqlCon);
SqlDataAdapter sqlAd = new SqlDataAdapter();
sqlAd.SelectCommand = sqlCmd;
sqlAd.Fill(ds, "sql");
crystalReport1.Load(Application.StartupPath + "CrystalReport.rpt");
crystalReport1.SetDataSource(ds.Tables["sql"]);
//{?}中的参数可以不用赋值,即使赋了值也不起作用。
//CrystalReportSource1.ReportDocument.ParameterFields["Parm"].CurrentValues.AddValue("123");
crystalReport1.ParameterFields["Title"].CurrentValues.AddValue("这时推模式的报表样例!");
crystalReportViewer1.ReportSource = crystalReport1;
}
3.
开发
有了这些储备, 相信问题很快久能解决了吧. 于是针对个案需求选用vs2005下C#来做”拉”模式的”Form”下实现, 至于C#基本语法以及C#下怎样遍历目录, 遍历文件, 开启新窗体, 等等等 都可以在MSDN上查到相关资料来参考, 下面一同附上代码, 万一大家以后遇到可以参考:
J
using
System;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.Data;
using
System.Drawing;
using
System.Text;
using
System.Windows.Forms;
using
System.Collections;
using
System.IO;
using
System.Runtime.InteropServices;

namespace
CrystalReport

{
public partial class Main : Form

{
public Main()

{
InitializeComponent();
}

//当前选中的报表路径
public string Path_Str = "";

//TabPage序列
public int FI = 0;

//RadioButton序列
public int FISUB = 0;

//RadioButton宽度
public int FWidth = 0;

//RadioButton高度
public int FHeight = 0;

//RadioButton行数
public int FLines = 0;

//当前应用程序执行路径
public string FExePath = "";

//创建TabPage
public TabPage[] mTabPage = new TabPage[500];

//创建GroupBox
public GroupBox[] mGroupBox = new GroupBox[500];

//创建RadioButton
public RadioButton[] mRadioButton = new RadioButton[500];

public void CreateAll(DirectoryInfo source)

{
//判断rpt路径是否存在
if (Directory.Exists(source.FullName) == false)

{
MessageBox.Show(source.FullName + " 不存在", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}

//遍历目录
foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())

{
//MessageBox.Show(diSourceSubDir.Name, "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

//实例TabPage
mTabPage[FI] = new TabPage();
mTabPage[FI].Parent = tabControl1;
mTabPage[FI].Text = diSourceSubDir.Name;
mTabPage[FI].TabIndex = FI;

//实例GroupBox
mGroupBox[FI] = new GroupBox();
mGroupBox[FI].Parent = mTabPage[FI];
mGroupBox[FI].Dock = DockStyle.Fill;
mGroupBox[FI].Text = "选择报表";

//行次
int MI = 0;

int x = mGroupBox[FI].Location.X;
int y = mGroupBox[FI].Location.Y;

//遍历文件
foreach (FileInfo fi in diSourceSubDir.GetFiles())

{
//换列显示
if (MI % FLines == 0)

{
if (MI != 0)

{
x = x + FWidth;
y = 0;
MI = 0;
}
}

//实例RadioButton
mRadioButton[FISUB] = new RadioButton();
mRadioButton[FISUB].Parent = mGroupBox[FI];
mRadioButton[FISUB].Location = new System.Drawing.Point(x + 25, y + 15 + MI * FHeight);
mRadioButton[FISUB].Text = fi.Name;
mRadioButton[FISUB].AutoSize = true;

//默认选择第一个
if (mRadioButton[FISUB].Location.Y == 15)
if (mRadioButton[FISUB].Location.X == 25)
mRadioButton[FISUB].Checked = true;

//行次累加
MI = MI + 1;

//当前的RadioButton总Id累加
FISUB = FISUB + 1;
}

//当前的TabPage总Id累加
FI = FI + 1;

//递归
CreateAll(diSourceSubDir);
}
}

//调用kernel32.dll读写ini文件信息
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);

//写ini
public void IniWriteValue(string Section, string Key, string Value, string filepath)//对ini文件进行写操作的函数

{
WritePrivateProfileString(Section, Key, Value, filepath);
}

//读ini
public string IniReadValue(string Section, string Key, string filepath) //对ini文件进行读操作的函数

{
StringBuilder temp = new StringBuilder(255);
int i = GetPrivateProfileString(Section, Key, "无法读取对应的值", temp, 255, filepath);
return temp.ToString();

}

//字符串转换为整型
public static int IntParse(string objValue, int defaultValue)

{
int returnValue = defaultValue;
if (!string.IsNullOrEmpty(objValue))

{
try

{
returnValue = int.Parse(objValue);
}
catch

{
returnValue = defaultValue;
}
}

return returnValue;
}

private void Main_Load(object sender, EventArgs e)

{

}

private void Main_Shown(object sender, EventArgs e)

{
//取得当前路径
FExePath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).Replace("file:\\", "").Replace("\\", @"\");

//从ini读取RPT存放路径
Path_Str = IniReadValue("PathInfo", "Path", FExePath + "\\CrystalReport.ini");

//窗体定位
Main.ActiveForm.Width = IntParse(IniReadValue("FormInfo", "Width", FExePath + "\\CrystalReport.ini"), 540); ;
Main.ActiveForm.Height = IntParse(IniReadValue("FormInfo", "Height", FExePath + "\\CrystalReport.ini"), 310);
Main.ActiveForm.Left = IntParse(IniReadValue("FormInfo", "Left", FExePath + "\\CrystalReport.ini"), 310);
Main.ActiveForm.Top = IntParse(IniReadValue("FormInfo", "Top", FExePath + "\\CrystalReport.ini"), 310);

//从ini读取RPT显示信息
FWidth = IntParse(IniReadValue("ViewInfo", "Width", FExePath + "\\CrystalReport.ini"), 160);
FHeight = IntParse(IniReadValue("ViewInfo", "Height", FExePath + "\\CrystalReport.ini"), 25);
FLines = IntParse(IniReadValue("ViewInfo", "Lines", FExePath + "\\CrystalReport.ini"), 8);

//取不出路径则报错退出应用程序
if (Path_Str.Length == 0)

{
MessageBox.Show("CrystalReport.ini中节PathInfo下的Path路径设定错误", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
//退出
}

//遍历RPT路径并动态新增页签及radio报表名称
DirectoryInfo diSource = new DirectoryInfo(Path_Str);
CreateAll(diSource);
}

private void button1_Click(object sender, EventArgs e)

{
//获取当前页签及当前页签选中的报表
string mClass = tabControl1.SelectedTab.Text;
string mRpt = "";

for (int MI = 0; MI <= FISUB - 1; MI++)

{
if (mRadioButton[MI].Parent.Parent.Text == mClass)

{
if (mRadioButton[MI].Checked == true)

{
mRpt = mRadioButton[MI].Text;
break;
}
}
}

//获得RPT文件路径
string mRTPPath = Path_Str + "\\" + mClass + "\\" + mRpt;
//MessageBox.Show(mRTPPath, "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

//新建实例
Report_View RPT = new Report_View();

//预览窗口显示
RPT.Text = "预览_" + mRpt;
RPT.Show();

RPT.RDT1.Load(mRTPPath);
RPT.RDT1.SetDatabaseLogon(IniReadValue("DBInfo", "User", FExePath + "\\CrystalReport.ini"),
IniReadValue("DBInfo", "Pass", FExePath + "\\CrystalReport.ini"),
IniReadValue("DBInfo", "Host", FExePath + "\\CrystalReport.ini"),
IniReadValue("DBInfo", "Dbname", FExePath + "\\CrystalReport.ini")
);
//绑定RPT报表
RPT.RPT1.ReportSource = RPT.RDT1;
}

private void button2_Click(object sender, EventArgs e)

{
this.Close();//关闭
}

private void Main_FormClosed(object sender, FormClosedEventArgs e)

{
//保存当前窗口宽高
string mWidth = Main.ActiveForm.Width.ToString();
string mHeight = Main.ActiveForm.Height.ToString();
string mLeft = Main.ActiveForm.Left.ToString();
string mTop = Main.ActiveForm.Top.ToString();

IniWriteValue("FormInfo", "Width", mWidth, FExePath + "\\CrystalReport.ini");
IniWriteValue("FormInfo", "Height", mHeight, FExePath + "\\CrystalReport.ini");
IniWriteValue("FormInfo", "Left", mLeft, FExePath + "\\CrystalReport.ini");
IniWriteValue("FormInfo", "Top", mTop, FExePath + "\\CrystalReport.ini");
}

}
}
4.
测试部署
编译后配置好ini后, 在我本地测试exe, 通过.
然而事情还没结束, 我们知道虽然它也是exe, 但是.net程序这个冬冬的运行环境和delphi可不一样, 它与java(须配置java虚拟机)类似, 也就是说要想在一台干净的电脑上运行.net程序还必须要安装.net的运行环境----dotnetframwork2.0, 所以我便在测试机器上安装了2.0环境, 本以为应该没问题了, 然而又让我 surprise了, 报了一堆错误, 肯定又是环境问题, 那该怎么搞, 在vs2005里找了一圈, 哦, 用vs2005自带的安装程序制作工具试试, 果然在做安装程序的时候vs2005检测到一堆”依赖项”, 并且这些依赖项都与Crystal Reports有关, 心里大悦啊, 这次总算可以了吧, 编辑后在测试机器上安装, 然后执行, 又报一个文件找不到的错误, 名字是 CrystalDecisions.Web.dll, 于是在回到安装程序制作里, 在应用程序文件夹里添加上这个文件, 再打包测试, 成功.:) (此安装程序发给曹铃铃测试ok)
虽然当前的问题解决了, 但是还遗留了一个问题, 就是上面说的测试通过的机器上有BO-BI的客户端环境, 或者有vs2005的开发环境, 或者有水晶报表的开发环境(仅仅XI版本), 另外我测试了一下, 在除.net外啥环境都没有的情况下会报一个错, 如下图: