Search code examples
delphiexcelcsvdelphi-2007virtualtreeview

Export a VirtualStringTree to excel, csv?


Is it possible to export a VirtualStringTree to Excel or CSV?

I am using Delphi 2007 and trying to save my VirtualStringTree data records as Excel or CSV format.


Solution

  • We have an ExcelWriter helper class, that can dump all kinds of things to Excel, e.g.

    • TADOQuery
    • TListView
    • TVirtualStringTree

    In this case the fast way (and easy to copy-paste here) is the overload that can dump a variant array into Excel:

    class function TExcelWriter.ExportToExcelVariantArray(const VArray: Variant; const Title, SubTitle: WideString): Boolean;
    var
        xl: OleVariant;
        workbook: OleVariant;
        worksheet: OleVariant;
        range: OleVariant;
        Rowcount, ColumnCount: Integer;
        HeaderRowIndex: Integer;
        s: WideString;
    begin
        Result := False;
    
        if not VarIsArray(VArray) then
            raise EExcelWriterException.Create('ExportToExcelVariantArray: Supplied variant is not an array');
    
        if VarArrayDimCount(VArray) <> 2 then
            raise EExcelWriterException.Create('ExportToExcelEVariantArray: Supplied variant array does not have 2 dimensions ('+IntToStr(VarArrayDimCount(VArray))+')');
    
        ColumnCount := VarArrayHighBound(VArray, 2) - VarArrayLowBound(VArray, 2); //2 for "leftmost dimension"
        rowCount := VarArrayHighBound(VArray, 1) - VarArrayLowBound(VArray, 1); //1 for "leftmost dimension"
    
        try
            xl := CreateOleObject('Excel.Application');
        except
            on E:Exception do
            begin
                if (E is EOleSysError) then
                begin
                    if EOleSysError(E).ErrorCode = CO_E_CLASSSTRING then
                        raise EExcelWriterException.Create('Excel is not installed.'+CRLF+
                                'Could not load "Excel.Application" object (Co_E_CLASSSTRING)')
                    else
                        raise;
                end
                else
                    raise;
            end;
        end;
        try
            xl.ScreenUpdating := False;
            xl.DisplayAlerts := False;  // Don't display dialogs such as "save changes to workbook".
    
    
            workbook := xl.Workbooks.Add;
            try
                Worksheet := Workbook.Worksheets[1];
                try
                    Worksheet.Activate;
                    Worksheet.Cells.WrapText := False;
    
                    HeaderRowIndex := 1; //Rows&Columns in Excel start at one.
                    range := TExcelWriter.GetRange(worksheet, HeaderRowIndex, 1, HeaderRowIndex+RowCount, ColumnCount);
                    range.Value := VArray;
    
                    //Bold the header row
                    Worksheet.Rows[HeaderRowIndex].Font.Bold := True;
                    Worksheet.Rows[HeaderRowIndex].Font.Underline := True;
                    Worksheet.Columns.AutoFit;
    
                    //Set printed header&footer
                    if Copy(Title, 1, 2) = '@@' then
                        s := Copy(Title, 3, MaxInt)
                    else
                        s := Title;
                    if SubTitle <> '' then
                    begin
                        if s <> '' then s := s+#13;
                        s := s + SubTitle;
                    end;
    
                    TExcelWriter.SetHeaderAndFooters(Worksheet,
                            s, '', '',
                            '&D &T', '', 'Page &P of &N');
                finally
                    Worksheet := Unassigned;
                end;
            finally
                Workbook := Unassigned;
            end;
    
            //When all done
            xl.ScreenUpdating := True;
            xl.Visible := True;
            xl.UserControl := True; // Very important, prevents Excel from going
                      // away when we nil out our reference to it below.
        finally
            xl := Unassigned;
        end;
    
        Result := True;
    end;
    

    We have a TVirtualListView descendant of TVirtualStringTree that made it easy to transition to VirtualTrees (it has TVirtualListItem, etc). It then has a helper method ContentToVariantArray, which is similar to ContentToHtml and ContentToRtf:

    function TVirtualListView.ContentToVariantArray: Variant;
    var
        Columns: TColumnsArray;
        VArray: Variant;
        Node: PVirtualNode;
        ColumnCount: Integer;
        RowCount: Integer;
        nRow: Integer;
        i: Integer;
    begin
        Columns := Self.Columns.GetVisibleColumns;
    
        ColumnCount := Length(Columns);
        RowCount := Self.Items.Count+1; //+1 for the heaader
    
        VArray := VarArrayCreate([0, RowCount-1, 0, ColumnCount-1], varOleStr); //Docs say cannot use varString, must be varOleStr (which is a BSTR i.e. WideString)
    
        nRow := 0;
    
        for i := 0 to ColumnCount-1 do
        begin
            VArray[nRow, i] := Self.Columns.Items[Columns[i].Index].Text;
        end;
    
        Node := Self.GetFirst;
        while Assigned(Node) do
        begin
            Inc(nRow);
            for i := 0 to ColumnCount-1 do
            begin
                VArray[nRow, i] := Self.Text[Node, Columns[i].Index];
            end;
    
            Node := Self.GetNextSibling(Node);
        end;
    
        Result := VArray;
    end;
    

    The main downside here is that we're automating Excel in order to use it. This means your customer/server will need Excel installed.

    The above code shows the user Excel (having to save a file to the hard drive just to look at it is wasteful), rather than creating an export file. But wouldn't be hard to call .Save or whatever the API is.