How can I display Crystal XI reports inside a Delphi 2007 application?

The most recent Crystal XI component for Delphi was released for Delphi 7. That VCL component compiles in D2007, but gives me errors at runtime. What is the best way to display a database-connected Crystal Report in a Delphi 2007 application?


This is the solution I've found, using ActiveX:

First, register the Active X control like this:

In Delphi, choose Component -> Import Component

Click on "Type Library", click Next

Choose "Crystal ActiveX Report Viewer Library 11.5"

Pick whatever Palette Page you want (I went with "Data Access")

Choose an import location

Exit out of the wizard

Add the location you chose to your project Search Path

Now this code should work:

...
uses
  CrystalActiveXReportViewerLib11_5_TLB, OleAuto;
...

procedure TForm1.Button1Click(Sender: TObject);
var
  cry : TCrystalActiveXReportViewer;
  oRpt, oApp : variant;
  i : integer;
  frm : TForm;
begin
  cry := TCrystalActiveXReportViewer.Create(Self);
  oApp := CreateOleObject('CrystalRuntime.Application');
  oRpt := oApp.OpenReport('c:\my_report.rpt',1);
  for i := 1 to oRpt.Database.Tables.Count do begin
    oRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] := 'username';
    oRpt.Database.Tables[i].ConnectionProperties.Item['Password'] := 'password';
  end;

  frm := TForm.Create(Self);
  try
    cry.Parent := frm;
    cry.Align := alClient;
    cry.ReportSource := oRpt;
    cry.ViewReport;
    frm.Position := poOwnerFormCenter;
    frm.ShowModal;
  finally
    FreeAndNil(frm);
  end;  //try-finally
end;

procedure TForm1.btnExportClick(Sender: TObject);
var
  cry : TCrystalActiveXReportViewer;
  oRpt, oApp : variant;
  i : integer;
begin
  //Export the report to a file
  cry := TCrystalActiveXReportViewer.Create(Self);
  oApp := CreateOleObject('CrystalRuntime.Application');
  oRpt := oApp.OpenReport(c_DBRpt,1);
  for i := 1 to oRpt.Database.Tables.Count do begin
    oRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] := 'username';
    oRpt.Database.Tables[i].ConnectionProperties.Item['Password'] := 'password';
  end;

  oRpt.ExportOptions.FormatType := 29;  //excel 8
  oRpt.ExportOptions.DiskFileName := 'c:\output.xls';
  oRpt.ExportOptions.DestinationType := 1;  //file destination
  //Export(False) => do NOT prompt.
  //Export(True) will give runtime prompts for export options.
  oRpt.Export(False);
end;

If you use this method, then this (rather dense) reference will be helpful, especially since Intellisense doesn't work on Ole objects like these.

Edit: The original link to the reference broke, so I changed it to point to a new one (valid as of Dec 15 2009). If that new one breaks, then Google should be able to find it.


I know it's not your question and it might not be an acceptable answer at all in your situation, but I have found FastReports to be clearly superior to Crystal for my purposes. It's lighter weight, includes a real scripting language, incorporates event handling, can make calls into your native code for information and updates and does not require an ActiveX connection. I can export my reports into sharp looking PDF files or Excel spreadsheets and several other formats. The quality of the output adds to the overall experience users get from my application. I could go on, but if it's off topic for you, it won't be helpful.


For the sake of anyone else who can use it, here is a complete class that gives a pleasant wrapper around these vile Crystal interactions. It works for me about 80% of the time, but I suspect a lot of this stuff is very dependent on the specific platform on which it runs. I'll post improvements as I make them.

Somebody at Business Objects should really take a hard look at this API. It sucks pretty badly.

{
Class to facilitate the display of Crystal 11 Reports.

The Crystal 11 VCL component does not seem to work with Delphi 2007.
As a result, we have to use ActiveX objects, which make deployment messy.

This class is similar to CrystalReporter, but it works for Crystal 11.
However, it lacks some of the features of the old CrystalReporter.

Refer to the crystal reports activex technical reference to duplicate the
missing functionality.

Example usage is at the bottom of this unit.
//}
unit CrystalReporter11;

interface

uses
  CrystalActiveXReportViewerLib11_5_TLB, OleAuto, Classes, Controls;

type
  TCryExportFormat = (
                      XLS
                     ,PDF
                     );

type
  TCrystalReporter11 = class
  private
    FCryRpt : TCrystalActiveXReportViewer;
    FRpt, FApp : variant;
    FReportFile, FUsername, FPassword, FServer, FFilters : string;
    FOwner : TComponent;
    procedure SetLoginInfo(const username, password, server : string);
    function GetFilterConds: string;
    procedure SetFilterConds(const Value: string);
  public
    property FilterConditions : string read GetFilterConds write SetFilterConds;

    procedure ExportToFile(ExportFileName : string;
      FileExportFmt : TCryExportFormat; PromptForOptions : boolean);
    procedure Display;

    constructor Create(AOwner : TComponent; ReportFile : string); overload;
    constructor Create(AOwner : TComponent; ReportFile,
      Username, Password, Server : string); overload;
  end;

implementation

uses
  SysUtils, Forms;

const
  //these are taken from pgs 246 and 247 of the technical reference
  c_FmtCode_Excel = 29;
  c_FmtCode_PDF = 31;

constructor TCrystalReporter11.Create(AOwner: TComponent; ReportFile: string);
begin
  inherited Create;
  try
    FReportFile := ReportFile;
    if FileExists(FReportFile) then begin
      FOwner := AOwner;
      FCryRpt := TCrystalActiveXReportViewer.Create(AOwner);
      FApp := CreateOleObject('CrystalRuntime.Application');
      FRpt := FApp.OpenReport(FReportFile,1);
      FFilters := FRpt.RecordSelectionFormula;
    end
    else begin
      raise Exception.Create('Report file ' + ReportFile + ' not found!');
    end;
  except on e : exception do
    raise;
  end;  //try-except
end;

constructor TCrystalReporter11.Create(AOwner: TComponent; ReportFile, Username,
  Password, Server: string);
begin
  Create(AOwner,ReportFile);
  FUsername := Username;
  FPassword := Password;
  FServer := Server;
  SetLoginInfo(FUsername,FPassword,FServer);
end;

procedure TCrystalReporter11.Display;
var
  rptForm : TForm;
begin
  SetLoginInfo(FUsername,FPassword,FServer);
  FCryRpt.ReportSource := FRpt;
  rptForm := TForm.Create(FOwner);
  try
    FCryRpt.Parent := rptForm;
    FCryRpt.Align := alClient;
    FCryRpt.ViewReport;
    rptForm.Position := poOwnerFormCenter;
    rptForm.WindowState := wsMaximized;
    rptForm.Caption := ExtractFileName(FReportFile);
    rptForm.ShowModal;
  finally
    FreeAndNil(rptForm);
  end;  //try-finally
end;

procedure TCrystalReporter11.ExportToFile(ExportFileName : string;
  FileExportFmt : TCryExportFormat; PromptForOptions : boolean);
begin
  case FileExportFmt of
    XLS : FRpt.ExportOptions.FormatType := c_FmtCode_Excel;
    PDF : FRpt.ExportOptions.FormatType := c_FmtCode_PDF;
  end;  //case

  FRpt.ExportOptions.DiskFileName := ExportFileName;
  FRpt.ExportOptions.DestinationType := 1;  //file destination
  FCryRpt.ReportSource := FRpt;  
  FRpt.Export(PromptForOptions);
end;

function TCrystalReporter11.GetFilterConds: string;
begin
  Result := FFilters;
end;

procedure TCrystalReporter11.SetFilterConds(const Value: string);
begin
  FFilters := Value;
  if 0 < Length(Trim(FFilters)) then begin
    FRpt.RecordSelectionFormula := Value;
  end;
end;

procedure TCrystalReporter11.SetLoginInfo(const username, password,
  server : string);
var
  i : integer;
begin
  //set user name and password
  //crystal only accepts these values if they are CONST params
  for i := 1 to FRpt.Database.Tables.Count do begin
    FRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] := username;
    FRpt.Database.Tables[i].ConnectionProperties.Item['Password'] := password;
    try
      {
      Some reports use direct connections, and others use an ODBC Data Source.
      Crystal XI uses a different label to refer to the database name in each
      method.
      I don't know how to determine in advance which method is being used, so:
          First, we try the direct connection.
          If that fails, we try the "data source" method.

      Reference: "Crystal Reports XI Technical Reference", pages 41 thru 46;
                 "Common ConnectionProperties"
      }
      FRpt.Database.Tables[i].ConnectionProperties.Item['Server'] := server;
    except on E: Exception do
      FRpt.Database.Tables[i].ConnectionProperties.Item['Data Source'] := server;
    end;
  end;
end;
{
Example usage:

procedure TForm1.btnShowRptDBClick(Sender: TObject);
var
  cry : TCrystalReporter11;
begin
  cry := TCrystalReporter11.Create(Self,'c:\my_report.rpt','username',
    'password','server.domain.com');
  try
    cry.Display;
  finally
    FreeAndNil(cry);
  end;
end;
}
end.

I too have been disappointed with the lack of effort by Crystal Reports with respect to application integration. I use the RDC, and from what I understand this is being deprecated and emphasis is being placed on .Net.

My application has these files in the uses clause: CRRDC, CRAXDRT_TLB,

It works ok. The because drawback is parameter passing. In my option the parameter dialog boxes which come with the viewer are terrible. So I use my own Delphi application to prompt for parameters and pass them to the report.