I’ve spent much of my ‘free’ time during the last month been pouring over error reports received during the past year. Unfortunately most of the reports are vague and completely untraceable as to their cause. Focusing on the most commonly reported errors I’ve tried to deduce their causes, but I’m likely to be completely off base.
TfrmMain, TfrmPrint: EConvertError
‘Not a valid floating point value’. All of these error reports came from version .18. This was fixed in version .19 by changing all calls to StrToFloat to TryStrToFloat. Come on folks, updates are free, please keep your copies up-to-date.
TfrmMain: Range check error
There were several Range errors which I believe originate in the Topaz database engine (no trace available). I re-compiled Topaz with Range checking turned off, hopefully that will tone down the frequency of these error reports.
TfrmPrint: Multiple errors
Multiple errors were reported originating in the TfrmPrint class. Procedures flagged include: SaveConsult, SaveLevels, SavePatient, ValidPatData, GraphPrep, and ReDrawSerumLevelPlot. Hopefully recompiling Topaz will fix most of the Save errors. But, to add another layer of safety, I’ve added try..try..except..finally blocks to each procedure and changed the SerumLevel array from fixed to dynamic.
TfrmMonitor: No default printer selected
This was reported twice. I cannot imagine how or why someone would not have a default printer selected in Windows. Regardless, I’ve added this tiny bit of code to hopefully catch that condition (unfortunately I have no way of testing this):
if (Printer.PrinterIndex > 0)then
begin
// Print the consult
end;
TfrmPKPD: Division by Zero
The function AUC was flagged in one report. I cannot imagine how this procedure gets called without valid parameters, but, once again, I added try..try..except..finally block that will prevent the program from crashing.
TfrmLevels; EConvertError
‘is not a valid integer value.’ Sounding like a broken record, I simply cannot imagine how this procedure gets called without valid parameters, regardless I changed StrToInt to TryStrToInt in hopes of fixing this impossible error.
TfrmMain: Pack date invalid
This error came up twice, with no traceable cause. Because the user is able to change date settings in Windows at will, I assumed an incompatible date format is causing the issue. I added some code to force the short date format into the standard USA: ‘MM/dd/yyyy’. Originally I had declared the OutputBuffer within the var block of the function, but that was causing a memory leak on exit, even after adding a StrDispose in the cleanup (finally) block:
procedure TfrmMain.SavePackDate();
var
APKini : TIniFile;
strDateFormat : string;
strTemp : string;
OutputBuffer: PChar;
SelectedLCID: LCID;
begin
try
// Determine local short date format
OutputBuffer := StrAlloc(255);
SelectedLCID := GetUserDefaultLCID;
GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255);
strTemp := string(OutputBuffer);
// if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy'
if Copy(strTemp, 1, 1) = 'd' then
strDateFormat := 'dd/MM/yyyy'
else
strDateFormat := 'MM/dd/yyyy';
try
APKini := TIniFile.Create(PathName + 'apk.ini');
try
APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now));
finally
APKini.Free;
end;
except
on E:Exception do begin
MessageDlg('Error saving pack date',
E.Message,
mtError, [mbOK], 690, dckActiveForm);
end;
end;
finally
// Free up the string list memory
StrDispose(OutputBuffer);
end;
end;
After I moved the code into an inline function there were no more memory leaks:
procedure TfrmMain.SavePackDate();
var
APKini : TIniFile;
strDateFormat : string;
strTemp : string;
function ShortDateFormat: string;
var
OutputBuffer: array[0..255] of Char;
SelectedLCID: LCID;
begin
SelectedLCID := GetUserDefaultLCID;
GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255);
Result := OutputBuffer;
end;
begin
try
// Determine local short date format
strTemp := ShortDateFormat;
// if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy'
if Copy(strTemp, 1, 1) = 'd' then
strDateFormat := 'dd/MM/yyyy'
else
strDateFormat := 'MM/dd/yyyy';
APKini := TIniFile.Create(PathName + 'apk.ini');
try
APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now));
finally
APKini.Free;
end;
except
on E:Exception do begin
MessageDlg('Error saving pack date',
E.Message,
mtError, [mbOK], 690, dckActiveForm);
end;
end;
end;
Population analysis improvements
I did find some time to make several improvements in this version of APK. The main focus of improvements this go round is the population analysis function. Probably the biggest change is removal of the 500 record limit, it now uses dynamic arrays so the number of records that may be analyzed is virtually unlimited. I added text to display the current model for comparison to the population analysis and a regression line of the current Kel or CL calculation.
APK Population Analysis Report
Finally, I added a median Vd calculation, which is surprisingly complex:
function MedianVd: double;
var
n,i,middle : integer;
TempDouble : double;
TempArray : array of Double;
begin
// Set Length of TempArray
SetLength(TempArray,TotalPatients);
// Copy first column of RegressTable array
for i := 0 to TotalForArray do begin
TempArray[i] := RegressTable[i,0];
end;
// use truncated selection sort to find median
middle := (TotalForArray+1) div 2;
for i := 0 to middle do begin
for n := 1 to TotalForArray-i do begin
if TempArray[n] > TempArray[n-1] then
begin
TempDouble := TempArray[n];
TempArray[n] := TempArray[n-1];
TempArray[n-1] := TempDouble
end
end
end;
// find median
try
if odd(high(TempArray)) then
// when high(TempArray) is odd, there are an even number of elements in array.
// define median as average of two middle values.
result := (TempArray[middle] + TempArray[middle-1]) / 2
else
// when high(TempArray) is even, there are an odd number of elements in array.
// median is the middle value.
result := TempArray[middle];
finally
// Free memory used for TempArray
SetLength(TempArray,0);
end;
end;
As soon as I finish testing I will release this version into the wild. Enjoy.
And here’s my current favorite song (based on a true story), “Who’s the judge to decide how much this world should punish”: