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):
1 if (Printer.PrinterIndex > 0)then 2 begin 3 // Print the consult 4 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.
‘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:
01 procedure TfrmMain.SavePackDate(); 02 var 03 APKini : TIniFile; 04 strDateFormat : string; 05 strTemp : string; 06 OutputBuffer: PChar; 07 SelectedLCID: LCID; 08 begin 09 try 10 // Determine local short date format 11 OutputBuffer := StrAlloc(255); 12 SelectedLCID := GetUserDefaultLCID; 13 GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255); 14 strTemp := string(OutputBuffer); 15 // if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy' 16 if Copy(strTemp, 1, 1) = 'd' then 17 strDateFormat := 'dd/MM/yyyy' 18 else 19 strDateFormat := 'MM/dd/yyyy'; 20 try 21 APKini := TIniFile.Create(PathName + 'apk.ini'); 22 try 23 APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now)); 24 finally 25 APKini.Free; 26 end; 27 except 28 on E:Exception do begin 29 MessageDlg('Error saving pack date', 30 E.Message, 31 mtError, [mbOK], 690, dckActiveForm); 32 end; 33 end; 34 finally 35 // Free up the string list memory 36 StrDispose(OutputBuffer); 37 end; 38 end;
After I moved the code into an inline function there were no more memory leaks:
01 procedure TfrmMain.SavePackDate(); 02 var 03 APKini : TIniFile; 04 strDateFormat : string; 05 strTemp : string; 06 function ShortDateFormat: string; 07 var 08 OutputBuffer: array[0..255] of Char; 09 SelectedLCID: LCID; 10 begin 11 SelectedLCID := GetUserDefaultLCID; 12 GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255); 13 Result := OutputBuffer; 14 end; 15 begin 16 try 17 // Determine local short date format 18 strTemp := ShortDateFormat; 19 // if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy' 20 if Copy(strTemp, 1, 1) = 'd' then 21 strDateFormat := 'dd/MM/yyyy' 22 else 23 strDateFormat := 'MM/dd/yyyy'; 24 APKini := TIniFile.Create(PathName + 'apk.ini'); 25 try 26 APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now)); 27 finally 28 APKini.Free; 29 end; 30 except 31 on E:Exception do begin 32 MessageDlg('Error saving pack date', 33 E.Message, 34 mtError, [mbOK], 690, dckActiveForm); 35 end; 36 end; 37 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.
Finally, I added a median Vd calculation, which is surprisingly complex:
01 02 function MedianVd: double; 03 var 04 n,i,middle : integer; 05 TempDouble : double; 06 TempArray : array of Double; 07 begin 08 // Set Length of TempArray 09 SetLength(TempArray,TotalPatients); 10 // Copy first column of RegressTable array 11 for i := 0 to TotalForArray do begin 12 TempArray[i] := RegressTable[i,0]; 13 end; 14 // use truncated selection sort to find median 15 middle := (TotalForArray+1) div 2; 16 for i := 0 to middle do begin 17 for n := 1 to TotalForArray-i do begin 18 if TempArray[n] > TempArray[n-1] then 19 begin 20 TempDouble := TempArray[n]; 21 TempArray[n] := TempArray[n-1]; 22 TempArray[n-1] := TempDouble 23 end 24 end 25 end; 26 // find median 27 try 28 if odd(high(TempArray)) then 29 // when high(TempArray) is odd, there are an even number of elements in array. 30 // define median as average of two middle values. 31 result := (TempArray[middle] + TempArray[middle-1]) / 2 32 else 33 // when high(TempArray) is even, there are an odd number of elements in array. 34 // median is the middle value. 35 result := TempArray[middle]; 36 finally 37 // Free memory used for TempArray 38 SetLength(TempArray,0); 39 end; 40 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”: