先看打印服务HisService_Report_Impl.pas,只展示重要代码:
type
THisService_Report = class(TRORemoteDataModule, IHisService_Report)
Reporter: TfrxReport;
private
protected
{ IHisService_Report methods }
function PrintReport(const ReportName: string;
const ParamNames: StringArray;
const ParamValues: StringArray;
const Flag: Integer): Binary;
end;
THisFunction = class(TfsRTTIModule)
private
function CallMethod(Instance: TObject; ClassType: TClass;
const MethodName: string; Caller: TfsMethodHelper): Variant;
public
constructor Create(AScript: TfsScript); override;
end;
implementation
procedure Create_HisService_Report(out anInstance : IUnknown);
begin
anInstance := THisService_Report.Create(nil);
end;
{ HisService_Report }
function THisService_Report.PrintReport(const ReportName: string;
const ParamNames: StringArray; const ParamValues: StringArray;
const Flag: Integer): Binary;
var
I: Integer;
ReportFile: string;
begin
ReportFile := FastReport.ReportFilePath + ReportName;
Check(not FileExists(ReportFile), '打印模版文件找不到:%s.', [ReportName]);
Check(ParamNames.Count <> ParamValues.Count, '打印参数名和值个数不一致');
try
with Reporter do
begin
Variables.Clear;
LoadFromFile(ReportFile);
for I := 0 to ParamNames.Count - 1 do
//参数值好像用引擎来执行,字符串必须要括起来
Variables[ParamNames.Items[I]] := QuotedStr(ParamValues.Items[I]);
PrepareReport;
Result := Binary.Create();
// SaveToStream(Result);
PreviewPages.SaveToStream(Result);
end;
except
on E: Exception do
raise Exception.Create('打印出错|' + ReportName + '|' + E.Message);
end;
end;
{ THisFunction }
constructor THisFunction.Create(AScript: TfsScript);
var
I: Integer;
begin
inherited;
with AScript do
begin
//todo: 添加更多HIS打印功能
// 添加ADO连接字符串常量, 用于FastReport
with RemoteServer.ConnectionManager.Connections do
for I := 0 to Count - 1 do
begin
AddConst('DB_' + Connections[I].Name, 'string',
DAString2ADOString(Connections[I].ConnectionString));
//AppCore.Logger.Write(DAString2ADOString(Connections[I].ConnectionString));
end;
end;
end;
var
fClassFactory: IROClassFactory;
initialization
fsRTTIModules.Add(THisFunction);
fClassFactory := TROPooledClassFactory.Create('HisService_Report',
{$IFDEF FPC}@{$ENDIF}Create_HisService_Report, THisService_Report_Invoker, 10);
finalization
UnRegisterClassFactory(fClassFactory);
fClassFactory := nil;
-
PrintReport函数参数:模板名称,参数名称列表,参数值列表,标志。返回binary,即生成的内容。参数一般是业务数据的主键,通过这些参数去数据库取数据。目前DA和FastReport没有结合组件,我们还是靠FastReport通过ADO去连数据库完成打印,那么ADO连接字符串不需要重复配置,看下面的说明。
-
fsRTTIModules.Add(THisFunction)为打印添加扩展函数和全局对象,这里主要把DA数据库连接定义转化为打印模板可以直接使用的全局常量字符串,名称为DB_+DA数据库名称,最后我们的打印模板在初始化时重新为frxADODatabase.DatabaseName赋值这些常量。
-
** TfrxReport不是线程安全类型,为什么没有作保护? ** 这是由TROPooledClassFactory服务调用机制决定的,请求来的时候TROPooledClassFactory会从池中选一个服务对象来完成这次请求。如果池中没有可用的服务对象,则会等待。意思就是由TROPooledClassFactory服务工厂管理的单个服务对象不会同时服务多个请求,线程安全问题就不存在。RO的服务工厂还有:TROClassFactory/TROSingletonClassFactory/TROSynchronizedSingletonClassFactory/TROPerClientClassFactory,使用的时候根据情况和需要进行选择。打印服务可以选择单例同步工厂TROSynchronizedSingletonClassFactory,效果是一样的。缺点是如果打印耗时长,并发数量大,一个请求会阻塞后面的请求。如果选择单例工厂TROSingletonClassFactory,代码则需要改造,在调用PrintReport时创建TfrxReport。通用工厂TROClassFactory为每次请求创建一个新的服务对象,服务完成后释放对象,普通场景没有问题,高并发不太合适。为了尽量减少对象创建和释放过程,防止数据库过载,我建议大多数情况下使用TROPooledClassFactory,池的大小建议小于等于CPU核的数量。
-
App_FastReport.pas有为打印模板添加更多的扩展函数,比如数字转人民币大写。
-
原先“设计报表”的功能在服务端,但是从Delphi 7升级到Delphi 10的时候发现会卡死,所以现在“设计报表”的功能放到了客户端。
- 基础视图我们这样开始打印功能:
TCustomDataPrintProc = procedure(Sender: TCustomData; ShowPrintDialog: Boolean);
var
CustomDataPrintProc: TCustomDataPrintProc;
procedure TCustomDataView.OnPrintExecute(Sender: TObject);
begin
DataPrint;
end;
procedure TCustomDataView.DataPrint();
begin
if Assigned(CustomDataPrintProc) then
CustomDataPrintProc(ViewData, ShowPrintDialog);
end;
- His客户端HisClient_Classes.pas具体这样实现打印:
procedure PrintCustomData(Sender: TCustomData; ShowPrintDialog: Boolean);
var
LReportNames: TStrings;
LReportName: string;
LPreview: Boolean;
LPrinterName: string;
LAccess: string;
begin
LReportName := '';
LPreview := True;
with Sender do
begin
if ReporterTypeField = '' then
begin
with ReporterDialog do
begin
if ShowPrintDialog then
begin
PrintParamData := HisOrganizer.PrintParamData;
if Selections.Text <> ReporterNames.Text then
begin
Selections.Assign(ReporterNames);
SelectIndex := 0;
end;
if not Execute() then
Abort;
end;
LReportName := ReporterName;
LPreview := PrintPreview;
LPrinterName := PrinterName;
end
end
else
LReportName := ReporterNames.Values[AsString[ReporterTypeField]];
PrintReportFromServer(LReportName, [KeyFieldNames, 'UserID', 'UserName',
'OfficeID', 'OfficeName', 'UnitID', 'UnitName'],
[KeyValue, HisUser.ID, HisUser.Name, HisUser.OfficeID, HisUser.OfficeName,
HisUser.UnitID, HisUser.UnitName], HisOrganizer.PrintParamData, LPreview,
LPrinterName);
end;
end;
initialization
CustomDataPrintProc := PrintCustomData;
-
打印的时候,如果ShowPrintDialog=True则展示打印对话框,用户选择可要打印的模板,填写模板对应的参数以及选择打印机。每个模板可设置哪些参数,在“打印设置”里面配。系统在DAHisClient.ini记录用户为每个打印模板选择的打印机,下次就不用再选。为了效率,有些打印业务不展示打印对话框,“选择打印机”功能专门为这些打印业务配置相应打印机。
-
PrintCustomData函数的参数Sender,类型为TCustomData,是对TDAMemDataTable进行的二次封装。Schema DataTable配置的CustomAttributes将对应到TDAMemDataTable.CustomAttributes,Schema Fields全部属性配置将对应到TDAField。 ** 注意:因为数据和视图是双向绑定,很多功能我们只需要知道是哪个TCustomData,不需要关心视图。 **
-
可以选择的打印模板就是来自于Schema DataTable的CustomAttributes配置,名称为ReporterNames。
-
在调用打印服务的时候,除了对话框里面可见的打印参数,我们额外附加的参数包括业务主键值,用户id,name,科室id,name。
procedure PrintReportFromServer(const ReportName: string;
const ParamNames: array of Variant; const ParamValues: array of Variant;
AParamData: TCustomData = nil; APreview: Boolean = False;
APrinter: string = '');
var
NameArray, ValueArray: StringArray;
Result: Binary;
procedure AppendParamData();
var
ParamValue: string;
begin
if AParamData = nil then
Exit;
AParamData.First;
while not AParamData.Eof do
begin
NameArray.Add(AParamData.AsString['ParamName']);
ParamValue := AParamData.AsString['ParamValue'];
if Pos('Properties=DateEdit', AParamData.AsString['ParamEditor']) > 0 then
ValueArray.Add(StandardizeLocalDate(ParamValue))
else
ValueArray.Add(ParamValue);
AParamData.Next;
end;
end;
begin
try
NameArray := StringArray.Create;
ValueArray := StringArray.Create;
try
AppCore.Logger.Write('正在打印' + ReportName, mtInfo, 0);
CopyArray(ParamNames, NameArray);
CopyArray(ParamValues, ValueArray);
AppendParamData;
Result := (HisConnection.ROService as IHisService_Report)
.PrintReport(ReportName, NameArray, ValueArray, 0);
try
Result.Position := 0;
FastReport.PrintStream(Result, ReportName, APreview, APrinter);
finally
Result.Free;
end;
finally
NameArray.Free;
ValueArray.Free;
end;
except
on E: Exception do
ShowWarning(E.Message);
end;
end;
有些Web应用使用第3方打印伺服器,比如Lodop。后续我们将在客户端添加一个HTTP服务来实现此功能。