Ускоряем создание отчетов: подключение FastReport к СЭД Директум

10 37

Краткое описание возможностей FastReport

FastReport - это набор компонент для построения отчетов, представляет собой сочетание дизайнера, генератора и Preview отчетов. Данное средство примечательно наличием полноценного дизайнера по типу WYSIWYG, что очень сильно упрощает жизнь разработчику, всегда проще накидать отчет в дизайнере нежели описывать его в коде сценария. Так же имеется возможность экспорта сформированных отчетов в популярные форматы документов (rtf, xls, pdf и т.д). С полным перечнем возможностей данного продукта вы можете ознакомиться на официальном сайте компании производителя. Хотелось бы отметить тот факт что стоимость этого продукта вполне адекватна и соответствует заявленному функционалу. Для тех кто хочет сэкономить деньги компании, существует бесплатная версия продукта.

Приступаем к разработке

За основу для нашего будущего решения я взял VCL компоненты (среда разработки Delphi CodeGear (exBorland), так же можно использовать бесплатную IDE TurboGears, под ней тоже возможно скомпилировать проект). На выходе у нас должна получиться ActiveX библиотека. Итак, создаем CoClass FRReport и интерфейс IFReport c описание его методов, нам понадобятся следующие:

  • IFReport.Design() - метод для запуска дизайнера отчетов
  • IFReport.Show() - метод для просмотра отчета
  • IFReport.Load(AFileName : string) - метод для загрузки файла отчета
  • IFReport.SetVariableValue(AName, AValue: string) - метод для инициализации переменной отчета
  • IFReport.GetVariableValue(AName: string) - метод для получения значения переменной отчета

Реализация этого интерфейса достаточно проста, поэтому я пропущу код на паскале, т.к. все перечисленные методы по своей сути являются всего лишь оберткой для VCL класса TfrxReport. Кстати, не забудьте включить в проект модуль frxADOComponents, иначе не будет возможности обращаться к базе данных.

После компиляции мы получим библиотеку FRRepLib.dll, ее необходимо зарегистрировать посредством утилиты regsvr32.exe (regsvr32.exe [lib_folder]\FRRepLib..dll).

Создаем отчеты

Для создания отчетов, нам потребуется сценарий вызова дизайнера:

Report = CreateObject("FRRepLib.FRReport")
Report.Design() 

это самый простой вариант, при желании можно расширить функционал, все зависит от потребностей и фантазии разработчика.

Создать - создали, а что дальше? А дальше можно сохранять файлы в отдельную директорию или "шару" в сети и открывать их оттуда, но мы поступим иначе, мы будем хранить наши документы в самой СЕД Директум, при этом приобретая очень полезную фичу как версионность отчетов. Создаем справочник отчетов, в котором будем хранить наименование отчета и ссылку на файл, так же создаем еще один сценарий для обработки формирования отчета, который впоследствии мы подключим в нужный нам модуль. Текст сценария:

ВнешниеОтчеты = References.ВнешниеОтчеты.GetComponent()
ВнешниеОтчеты.ComponentForm.View.ViewMode = vmSelect
ВнешниеОтчеты.ComponentForm.View.MultiSelection = False
ВнешниеОтчеты.ComponentForm.ShowModal

if ВнешниеОтчеты.ComponentForm.Result = mrOK
  ReportID = ВнешниеОтчеты.View.SelectedRecordsID(0)
  ВнешниеОтчеты.Locate('Код'; ReportID)
  EDocumentID = ВнешниеОтчеты.Requisites('ReportID').Value
  EDocumentFactory = Application.EDocumentFactory
  ReportDocument =  EDocumentFactory.GetObjectByID(EDocumentID)
  LastReportVersion = GetLastEDocumentVersionNum(ReportDocument)
  ReportFileName = DocumentsFolder & '\tmp_report.fr3'
  ReportDocument.Export(LastReportVersion; ReportFileName; FALSE) 
  Report = СоздатьОбъект("FRRepLib.FRReport")
  Report.Load(ReportFileName)
  Report.SetVariableValue("EDocID"; Object.ID)
  Report.Show()
endif

Данный сценарий открывает справочник со списком отчетов в модальном режиме, определяет выбранную пользователем запись и через фабрику EDocumentFactory получет связанный документ самого отчета. Далее происходит извлечение последней версии документа и сохранение ее в пользовательский каталог, с последующем открытием отчета.

Примеры отчетов

Пример отчета по этам согласования договоров

отчет по этам согласования договоров

Пример листа согласования договора

Пример листа согласования договора

Итог

Мы получили возможность не только быстрого создания отчетов в среде FastReport, но и возможность хранить версии всех отчетов не используя внешних систем контроля версий.

10
Авторизуйтесь, чтобы оценить материал.
2
Дмитрий Тарасов

Интересное решение. Только вот я сомневаюсь в целесообразности хранения отчетов в системе. Обычно сформировал отчет, посмотрел или распечатал и забыл про него. Потом при необходимости его можно сформировать снова. А тут как я понял все отчеты будут храниться в системе. Хотя сама идея интересная.

Алексей Зиновьев
Интересное решение. Только вот я сомневаюсь в целесообразности хранения отчетов в системе. Обычно сформировал отчет, посмотрел или распечатал и забыл про него. Потом при необходимости его можно сформировать снова. А тут как я понял все отчеты будут храниться в системе. Хотя сама идея интересная.

Дмитрий, Вы наверное не совсем поняли, сам, так сказать "макет" отчета хранится в базе, а не сформированный отчет. Т.о. изменяя макет Вы всегда можете откатиться если что то пошло не так.

Дмитрий Тарасов

Аааа... ну тогда совсем другое дело. Приношу извинения за невнимательность :)

Дмитрий Тарасов

Было бы неплохо, если бы выложили еще саму готовую ActiveX библиотеку :)

Денис Баранов

Отчеты надо хранить в системе, например, когда требуется подтверждение, что на такую-то дату отчет выглядел так и никак иначе. Сохранить в системе и подписать ЭЦП. На основании отчетных документов могут выпускаться приказы, заключаться договры, выплачиваться премиальные сотрудникам. Такие отчеты лучше всего защищать от изменений.

Кроме того, отчеты могут рассылаться на ознакомление или согласовываться. Тогда их тоже лучше делать документами. Во-первых, отчеты могут долго формироваться. Гораздо дольше, чем открывается документ. Во-вторых, у тех, кто должен ознакомиться с отчетом, может не быть доступа к тем компонентам информационной системы, которые формируют отчет (как к коду, так и к данным).

Дмитрий Тарасов

Денис, я согласен с тобой. Действительно бывают ситуации, описанные тобой, при которых отчет надо хранить в системе. Я лишь хотел сказать, что все подряд хранить не стоит.

Алексей Зиновьев
Отчеты надо хранить в системе, например, когда требуется подтверждение, что на такую-то дату отчет выглядел так и никак иначе...

Согласен, такой функционал имеет право на жизнь, но это уже другая история и при нехитрых доработках это реализуемо, я же говорил именно о макетах отчетов. 

Алексей Зиновьев

ссылка на видео http://www.youtube.com/watch?v=oR16Haw8syk

Алексей Немцев

По поводу хранения отчетов - многичисленные RTF/HTML/XLS, присуствующие в системе являютя динамическими, то есть не обладают ни одним из вышеперечисленных свойств.

При желании отчет fast report можно экспортировать в неизменный формат, подписать и сохранить в системе. Ручками пользователей. А если такая возможность будет часто требоваться, то несложно и автоматизировать эти действия.

Дмитрий Тарасов

Алексей, можете показать реализацию ниже перечисленных методов в delphi?

IFReport.Design() - метод для запуска дизайнера отчетов
IFReport.Show() - метод для просмотра отчета
IFReport.Load(AFileName : string) - метод для загрузки файла отчета
IFReport.SetVariableValue(AName, AValue: string) - метод для инициализации переменной отчета
IFReport.GetVariableValue(AName: string) - метод для получения значения переменной отчета

Алексей Зиновьев

да конечно, смотрите текст исходника ниже, но я не использую стандартную форму превью, т.к. сделал свою форму предпросмотра, так что все упоминания FPreviewForm можете заменить на вызов метода Show у FReport:

 

type
  TFRReport = class(TAutoObject, IConnectionPointContainer, IFRReport)
  private
    { Private declarations }
    FConnectionPoints: TConnectionPoints;
    FConnectionPoint: TConnectionPoint;
    FEvents: IFRReportEvents;
    { note: FEvents maintains a *single* event sink. For access to more
      than one event sink, use FConnectionPoint.SinkList, and iterate
      through the list of sinks. }
    FReport: TfrxReport;
    FDesigner: TfrxDesigner;
    FPreviewForm: TfmUcPreview;
  public
    procedure Initialize; override;
    destructor Destroy; override;
  protected
    function GetVariableValue(const Name: WideString): WideString; safecall;
    procedure Design; safecall;
    procedure Load(const FileName: WideString); safecall;
    procedure SetVariableValue(const Name, Val: WideString); safecall;
    procedure Show(const Title: WideString = ''); safecall;
    function SaveReportAs(const FileName: WideString): WordBool; safecall;
    function Get_FileName: WideString; safecall;
    procedure Set_FileName(const Value: WideString); safecall;
    function ExportReport(ExportTo: ExportEnum; const FileName: WideString; ShowDialog,
          ShowProgress: WordBool): WordBool; safecall;
    procedure PrepareReport; safecall;
    procedure ShowSaved(const FileName: WideString); safecall;
    function Get_Modified: WordBool; safecall;

    { Protected declarations }
    property ConnectionPoints: TConnectionPoints read FConnectionPoints
      implements IConnectionPointContainer;
    procedure EventSinkChanged(const EventSink: IUnknown); override;

  end;

  var
      {Controls}
      // для добавления в report адо и прочих компонентов
      FAdoComponents  : TfrxADOComponents;
      FDialogControls : TfrxDialogControls;
      fBarCodeObject  : TfrxBarCodeObject;
      fRichObject     : TfrxRichObject;
      fCrossObject    : TfrxCrossObject;
      fGradientObject : TfrxGradientObject;
      fChartObject    : TfrxChartObject;

implementation

uses ComServ, SysUtils;

destructor TFRReport.Destroy;
begin
  FreeAndNil(FDesigner);
  FreeAndNil(FReport);
  FreeAndNil(FPreviewForm);
  inherited;
end;

procedure TFRReport.EventSinkChanged(const EventSink: IUnknown);
begin
  FEvents := EventSink as IFRReportEvents;
end;

procedure TFRReport.Initialize;
begin
  inherited Initialize;
  FConnectionPoints := TConnectionPoints.Create(Self);
  if AutoFactory.EventTypeInfo <> nil then
    FConnectionPoint := FConnectionPoints.CreateConnectionPoint(
      AutoFactory.EventIID, ckSingle, EventConnect)
  else FConnectionPoint := nil;
  FReport := TfrxReport.Create(nil);
  FDesigner := TfrxDesigner.Create(nil);
  FPreviewForm := TfmUcPreview.Create(nil);
  FPreviewForm.ParentWindow := GetDesktopWindow;
end;


function TFRReport.GetVariableValue(const Name: WideString): WideString;
begin
  Result := FReport.Variables.Variables[Name];
end;

procedure TFRReport.Design;
begin
  FDesigner.Standalone := True;
  FReport.DesignReport();
end;

procedure TFRReport.Load(const FileName: WideString);
begin
  FReport.Clear;
  FReport.LoadFromFile(FileName, True);
end;

procedure TFRReport.SetVariableValue(const Name, Val: WideString);
begin
  FReport.Variables.Variables[Name] := Val;
end;

procedure TFRReport.Show(const Title: WideString);
begin
  FPreviewForm.ShowMe(FReport, Title);
end;

function TFRReport.SaveReportAs(const FileName: WideString): WordBool;
begin
  FReport.SaveToFile(FileName);
end;

function TFRReport.Get_FileName: WideString;
begin
  Result := FReport.FileName;
end;

procedure TFRReport.Set_FileName(const Value: WideString);
begin
  FReport.FileName := Value;
end;

function TFRReport.ExportReport(ExportTo: ExportEnum; const FileName: WideString;
          ShowDialog, ShowProgress: WordBool): WordBool;
var
  ExportPlugin: TfrxCustomExportFilter;
begin
  ExportPlugin := nil;
  Result := False;
  case ExportTo of
    RTF: ExportPlugin := fExportRTF;
    PDF: ExportPlugin := fExportPDF;
    XLS: ExportPlugin := fExportXLS;
  end;
  if Assigned(ExportPlugin) then
  begin
    ExportPlugin.FileName := FileName;
    ExportPlugin.ShowDialog := ShowDialog;
    ExportPlugin.ShowProgress := ShowProgress;
    Result := FReport.Export(ExportPlugin);
  end;
end;

procedure TFRReport.PrepareReport;
begin
  FReport.PrepareReport(True);
end;


procedure TFRReport.ShowSaved(const FileName: WideString);
begin
  FPreviewForm.ShowMeSaved(FReport, FileName);
end;

function TFRReport.Get_Modified: WordBool;
begin
  Result := FReport.Modified;
end;

initialization
  TAutoObjectFactory.Create(ComServer, TFRReport, Class_FRReport, ciMultiInstance, tmApartment);

  {Controls}
  FAdoComponents  := TfrxADOComponents.Create(nil);
  FDialogControls := TfrxDialogControls.Create(nil);
  fBarCodeObject  := TfrxBarCodeObject.Create(nil);
  fRichObject     := TfrxRichObject.Create(nil);
  fCrossObject    := TfrxCrossObject.Create(nil);
  fGradientObject := TfrxGradientObject.Create(nil);
  fChartObject    := TfrxChartObject.Create(nil);

finalization
  FreeAndNil(FAdoComponents);
  FreeAndNil(FDialogControls);
  FreeAndNil(fBarCodeObject);
  FreeAndNil(fRichObject);
  FreeAndNil(fCrossObject);
  FreeAndNil(fGradientObject);
  FreeAndNil(fChartObject);
end.
Дмитрий Тарасов

При попытке вызова любого метода библиотеки возвращающего WideString, получаю исключение Access Violation. 

Например, вот метод:

function TFRReport.GetVariableValue(const Name: WideString): WideString;
begin
  Result := FReport.Variables.Variables[Name];
end;

Пробовал даже так делать, все равно возникает исключение:

function TFRReport.GetVariableValue(const Name: WideString): WideString;
begin
  Result := WideString('TEST!!!');
end;

Вызываю метод так:

procedure TForm1.Button1Click(Sender: TObject);
var
    Report: OleVariant;
    Rez: WideString;
begin
    Report := CreateOleObject('FRRepLib.FRReport');
    Report.SetVariableValue('Dir','4.6.1');
    Rez := Report.GetVariableValue('Dir');
    ShowMessage(Rez);
end;

Причем, если метод возвращает не WideString, а например Integer, то все работает без проблем.

Кто-нибудь может подсказать в чем проблема?

Дмитрий Тарасов

Забыл добавить, исключение возникает в самой dll и только при попытке вызова методов возвращающих WideString.

Алексей Зиновьев

Очень странно, пришлите исходник архивом - посмотрю что не так. По фотографии лечить сложно....
 

Дмитрий Тарасов

Отправил вам на почту

Алексей Зиновьев

Все понятно... вы немного ошиблись с описанием методов в библиотеке типов,

посмотрите на скрин, вот так должно выглядеть описание метода (Result типа HRESULT, у вас стоит BSTR).

Дмитрий Тарасов

Спасибо огромное, все заработало!

Илья Елдинов

Добрый день. Есть вопрос по теме FastReporta. Отзовитесь.

Алексей Зиновьев

Спрашивайте

Илья Елдинов

Есть FastReport Studio, это COM/ActiveX компонент. В наличии сама dll

Пытаюсь вызвать с отчет и передать туда параметр. Форма отчета открывается, но параметр не передается.

Илья Елдинов

он вызывается с помощью IS-Builder посредством сценария

Код на IS-Builder:
tReport = CreateObject("FastReport.TfrxReport") 
tReport.LoadReportFromFile("c:\XXXX.fr3") 
tReport.SetVariable('Taskid',152)
tReport.ShowReport()

Илья Елдинов

Создание переменной с кода, работает

tReport.AddVariable('NameGroup','NameParam',123); -- 123 value

Если вызвать дизайнер, то видим, переменная создана, а значение не присваивается.

Алексей Зиновьев

К сожалению по студии не готов давать какие-то комментарии, это разработка вендора, Вам лучше к ним обратиться за консультацией. Я могу Вам ответить только в рамках предложенной в статье схемы реализации "отчетника".

Илья Елдинов

Хорошо. Как понял, то человек который скомпилировал под себя проект, по вашему примеру столкнулся с ошибкой при передачи значения. Вот Ваш ответ "Все понятно... вы немного ошиблись с описанием методов в библиотеке типов" В каком это методе?

Илья Елдинов

У меня ошибки не возникает, просто не передается значение в переменную. Может стоит попробовать преобразовать перед передачей в какой то тип?

Алексей Зиновьев

Этот комментарий был связан с корректностью описания параметров в собственном методе внутри библиотеки типов, т.е. внутри самой библиотеки.

Алексей Зиновьев

Попробуйте перед Show вызвать PrepareReport или что-то аналогичное если есть, м.б. это Вам поможет.

Алексей Зиновьев

Илья, я Вам уже ответил относительно студии, Ваш вопрос конечно связан с FR, но никак не связан с моей статьей. Откройте заявку на форуме разработчика в соответствующей теме https://www.fastreport.ru/ru/forum/index.php?s=74c07128ba1b6d55a1e9f30c9378ff3c&showforum=10

 

Илья Елдинов

Да, я с вами соглашусь, но просто вы на основе vcl создали com обьект и использовали стандартные методы, которые такие же как и в студии. Вот и подумал, что ошибка может быть как раз в этом

 

Алексей Зиновьев

23 сентября 2010 в 17:46

Все понятно... вы немного ошиблись с описанием методов в библиотеке типов,

посмотрите на скрин, вот так должно выглядеть описание метода (Result типа HRESULT, у вас стоит BSTR).

Алексей Зиновьев

Когда я создавал COM-объект, я не смотрел на описание методов в FR-студии.

Кстати, как Вы поняли что переменная не присваивается? Судя по ответу на форуме разработчика Вам дали пример кода как достучаться до параметров Query внутри отчета. Хотя на мой взгляд это кривоватый путь. На самом деле, в самом запросе параметр обозначается так: select * from SBTask where id = :ID , этот параметр ID необходимо сопоставить с параметром отчета (это разные параметры - параметр отчета и параметр запроса) в дизайнере либо в коде через Query.ParamByName(...). = Report.Variables(''), как то так... И далее при вызове "отчетника" через ком установить параметры отчета, далее отчет сам отработает входящий параметр согласно реализованной внутри логике.

Алексей Зиновьев: обновлено 03.04.2017 в 16:30
Илья Елдинов

Все усложняется тем, что под IS-Builder с FastReport никто не имел дело. Форум давно если живой, а разработчики прекратили поддержку данного компанента

Алексей Зиновьев

разработчики прекратили поддержку данного компанента

Ну риски о том что это вообще будет работать в перспективе на Вас, м.б. и не нужен Вам именно этот компонент? У них есть поддержка Net и VCL, инструкция как собрать по рецепту отчетник выше в статье.
Илья Елдинов

Хорошо, спасибо. Попробую разобраться по собранию СОМ обьекта.

Илья Елдинов

Добрый день. Что делаю не так, создаю ActiveX билиотеку, добавляю COM обьект, создаю методы, компилирую. DLL создается, регистрируется, но вызвать её не получается. Можете подробней о том как реализовать правильную библиотеку или где почитать?

Пока пробовал проект по ссылке http://www.introligator.org/articles/3/78

Алексей Зиновьев

На втором этапе вам нужно создать AutomationObject а не ComObject

Ваш полученный класс должен имплементировать интерфейс IDispatch, что-то вроде этого

type
  TReportWrapper = class(TAutoObject, IReportWrapper)
....
  end;
 
где   TAutoObject = class(TTypedComObject, IDispatch)
 
также смотрите более подробную информацию на MSDN и в чем отличие IUnknown от IDispatch.
 
 
Илья Елдинов

Спасибо. Мне дали исходные коды FR studio. проект компилируется, DLL регистрируется, шаблон открывается, но значение параметру у меня так и не передается. Может есть спортивный интерес посмотреть на студию изнутри и вынести вердикт:) ? 

Илья Елдинов: обновлено 11.04.2017 в 19:49
Алексей Зиновьев

Пишите на почту zinovievalex [dog] ГМЕЙЛ.КОМ

Авторизуйтесь, чтобы написать комментарий