Friday, November 26, 2021

Opening Excel from a thread and displaying a file

Three weeks ago, I wrote about programming a thread that would activate Word (in the background, of course) and cause it to display a file that was preprogrammed as an HTML page. I wrote then "the next step is to write similar code that will open Excel in a thread". Well, I wrote the Excel code; this was easy to do once I had the Word example.

Today I came across an example where the simple 'open Excel in a thread' code is not sufficient: it often happens that I want to format certain columns as integers or as dates (although as I documented a year ago, it's better not to format date columns) after the data had been passed to Excel. I would do this with the following two lines:

xlApp.Columns['B:F'].Select; xlApp.Selection.NumberFormat:= XLIntFormat;

I could not do this with the simple thread code as the xlApp variable is not visible to the calling program. The simple solution would be to add an optional parameter to the code that creates the thread; this parameter would be passed down the chain and used after the data had been inserted into Excel. A complication would be that there might be two ranges of columns that need to be formatted. 

Here's a further complication: normally I freeze the top line of an Excel file with the following code:

sheet.Range['A2'].Select; xlApp.ActiveWindow.FreezePanes:= true;

In a certain case, I wanted to freeze the top two lines, meaning that the range would have to be A3 and not A2. Could I do this with another optional parameter? It is my understanding (probably wrong) that there can only be one optional parameter per procedure. This was exacerbated when I thought that I would also have to pass another string parameter denoting which columns should be dates (in the end, I didn't need this). So I decided to use overloaded procedure definitions, a first for me.

I now have the following:

Procedure ThreadExcel (const AFileName: string); overload; Procedure ThreadExcel (const AFileName, intformat: string); overload; Procedure ThreadExcel (const AFileName, intformat: string; freeze: integer); overload; ... Procedure ThreadExcel (const AFileName: string); begin TExcelThread.Create (AFileName, '', 2); end; Procedure ThreadExcel (const AFileName, intformat: string); begin TExcelThread.Create (AFileName, intformat, 2); end; Procedure ThreadExcel (const AFileName, intformat: string; freeze: integer); begin TExcelThread.Create (AFileName, intformat, freeze); end;

I suppose that I also need a variation where there are no columns to be formatted, but a different 'freeze number' is required but I'll cross that bridge when I come to it (actually this could be done by passing afilename, a blank string and the freeze number). The possibility that there might be different types of format passed and/or multiple column selections is handled in the following code:

Procedure DoFormat (const fmt: string; aformat: string); var i: integer; cols: string; begin repeat i:= pos (',', aformat); if i = 0 then begin cols:= aformat; aformat:= ''; end else begin cols:= copy (aformat, 1, i - 1); aformat:= copy (aformat, i + 1, length (aformat)); end; xlApp.Columns[cols].Select; xlApp.Selection.NumberFormat:= fmt; until aformat = ''; end; ... if intformat <> '' then DoFormat (XLIntFormat, intformat);

Intformat could be 'A:B,E:F' in order to format two column ranges. Passing the format string as a parameter enables formats other than XLIntFormat ('#,##0') to be used. This procedure uses what might be termed 'old style' programming; modern day coders would probably use a stringlist in the following manner (this is unchecked code, so it may be wrong):

var i: integer; alist: tstringlist; begin alist:= tstringlist.create; alist.delimiter:= ','; alist.delimitedtext:= aformat; for i:= 0 to alist.count - 1 do begin xlApp.Columns[alist[i]].Select; xlApp.Selection.NumberFormat:= fmt; end; alist.free; end;
I wonder whether I need to add a comma at the end of the list.

No comments: