Skip to content

Latest commit

 

History

History
254 lines (211 loc) · 9.3 KB

打印功能设计说明.md

File metadata and controls

254 lines (211 loc) · 9.3 KB

打印功能说明

服务端

先看打印服务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;

Todo

有些Web应用使用第3方打印伺服器,比如Lodop。后续我们将在客户端添加一个HTTP服务来实现此功能。