Creating your Own Export Format


Introduction

Creating your own Export Format is easy if you subclass TCustomExportListView or one of it's descendants. You only need to override a few methods and you have your very own ListView Export Object that you can use to export the contents of a ListView to the format you need

Subclassing TCustomExportListView

Actually, we're not going to subclass TCustomExportListView. That's a bit advanced, if you want to you should look at the source code to see how that works. (If you don't have the source code version, you need to register first).

What we are going to do, however, is subclass TCustomShellViewExportListView which is itself a direct descendant of TCustomExportListView. What this class does is it will automatically launch the associated Shell Viewer Application for the file type defined in the GetExtension method you override (more on that later).

Here's an example class definition:

type
    TMyExportListView = class(TCustomShellViewExportListView)
    protected
        function GetExtension: String; override;

        procedure WriteHeader(var Columns: TStringList); override;
        procedure WriteEntry(var Items: TStringList; Entry: Integer);  override;
        procedure WriteFooter;  override;

        procedure HandleError(Exception: TObject);  override;


    public
        constructor Create(aListView: TListView; aExportFile, aTitle: String; aShowProgress: Boolean);  override;
        destructor Destroy;  override;

    end;

The methods in blue are optional, you must however override the GetExtension, WriteHeader and WriteEntry methods.

Overriding GetExtension, it must be done!

You must override the GetExtension function so that the Windows can figure out which application to launch to view your file (if no export file was specified, then it must be viewed on the screen). It's easy enough, just look at the following example:

function TMyExportListView.GetExtension: String;
begin
    Result := 'xls'; // If we were creating an Excel Export Object for instance
end;

If the shell view fails, then it is assumed that there is no viewer application associated with that file (ie. Microsoft Excel not installed) and in that case the export object automatically gives a generic error message. You can customize it however by modifiying the ShellViewErrorMsg property at any time before the object is destroyed (the constructor is an especially good place). Here's an example:

ShellViewErrorMsg := 'Microsoft Excel not installed. Please install it.';

Properties that you can reference in your code:

property Footer: String; // The Footer Text
property Header: String; // The Header Text
property OutputFile: String; // The File To Export
property Title: String; // The Export Document Title
property ExportSuccessful: Boolean; // If True, Export was a success.

Isn't there a difference between Exporting to the Screen and Exporting to a File?

Lucky for you there isn't! Not from the programmers perspective anyways. Ok, I take that back Not from the programmer who is subclassing TCustomShellViewExportListView. If you subclass TCustomExportListView, you need to handle it differently but you only need to subclass TCustomExportListView directly if you're going to export to an OLE application (like Microsoft Word & Excel) for instance. And that's beyond the scope of this document (but it's quite easy actually, it's a case of "the source code says a 1000 words". For that you need to register and get source code version though.)

So, really, TCustomShellViewExportListView takes care of everything for you. You just write to the OutputFile and TCustomShellViewExportListView will display it using Window's registered Shell Viewer for that file type which is why it is critical that you override the GetExtension method. Also remember that exporting to the screen or file is determined by whether or not an Export File is specified in the constructor. If you just pass a blank string (' ') then it will export to the screen.

Overriding the Constructor & Destructor (optional)

It might be tempting to put your creation code into the WriteHeader method since you have to override that anyways. Don't do it! Constructors are designed so that if an exception occurs, the Destructor will be called to clean up the mess. If you put your create code in WriteHeader instead, it won't clean up so nicely. An example is creating an OLE Automation object (like Microsoft Word), you would create the object in the overriden Create method and destroy it in the overriden Destroy method.

Remember if you override the constructor, the statement 'inherited;' should be the first line of code in the constructor method. If you override the destructor, 'inherited;' should be the last line of code in the destructor method.

If you override the destructor, you can reference the ExportSuccessful variable to see if the export was a success.

Overriding WriteHeader, it must be done!

You can write a Title if you want into your header code, just reference the Title property (it is set in the constructor). This is also a good spot for writing the current date & time, although you can also do that in the WriteFooter method.

Columns is a TStringList that holds the captions of each column of the ListView. Just iterate through and write each column header.

You should access the Header property here.

Overriding WriteEntry, it must be done!

Items is a TStringList that holds the values for each item for the current entry (row). (ie. Items[0] is the first column's data).

Overriding WriteFooter (optional)

You can write your footer code here. You should access the Footer property here.

Overriding Handle Error (optional)

If you override the HandleError method, no error message will be shown if something goes awry when you call DoExport (DoExport is the method you call when you want to actually perform the export). Instead your HandleError method will be call and you can do what you want (usually display your own error message).

Important: Don't put the word 'inherited' into your code for HandleError. If you do, the generic error message will be displayed.

Subclassing TCustomTextExportListView

This is the easiest thing to subclass! Just look at the example below.

Everything is taken care of with this method. The catch is that it's only good for Text files, you'll have to use TCustomShellViewExportListView for anything else. 

Writing to the text file is simple just reference the TextFile variable F using Write or WriteLn.

An Example: Tab-Delimited Text Exporting

TTabExportListView = class(TCustomTextExportListView)
protected
    procedure WriteHeader(var Columns: TStringList); override;
    procedure WriteEntry(var Items: TStringList; Entry: Integer); override;
end;

// *** TTabExportListView Methods ***

procedure TTabExportListView.WriteHeader(var Columns: TStringList);
const
    cTab = Chr($09);
var
    S: String;
    i: Integer;
begin
    // Write the Columns Row
    S := '';
    for i := 0 to Columns.Count - 1 do
       S := S + Columns[i] + cTab;

    WriteLn(F, S);
end;

procedure TTabExportListView.WriteEntry(var Items: TStringList; Entry: Integer);
const
    cTab = Chr($09);
var
    S: String;
    i: Integer;
begin
    S := '';
    for i := 0 to Items.Count - 1 do
       S := S + Items[i] + cTab;

    WriteLn(F, S);
end;

How do I Actually Export ???

It's easy but... there's a catch. Not really a big catch, just something you should be aware of.... you want to Free the object as soon as possible after you call DoExport because the destructor is where the Shell Viewer Application gets called from.

Here's an example where we are Exporting MyListView to the Screen (because no file is specified. ie. ' ') with a document title of My Document Title and we are showing the progress (that's what the True is for).

with TClipboardExportListView.Create(MyListView, '', 'My Document Title', True) do
try
    Footer := 'Some Footer Text';
    Header := 'Some Header Text;
    Result := DoExport; // Export the List View (returns false on failure)

finally
    Free; // Free the Export Object
end;

Note: looking at this code, I just realized something else. If you want a silent abort, that is if your export fails but you don't want to display a message to the user, just override HandleError and leave the inside blank, ie. don't put any code there (especially not the word 'inherited'.

Some Final Words

Please send me the Export Objects you've created so I can include them in further releases of TExportListView. Any suggestions of course are also welcome. You can reach me at ycomp@hotpop.com


Please click here to register.