Validating TEdit input, by comparing it with an integer value - delphi

I have developed an application that randomly creates two integers and then adds them. The sum of the two integers must be worked out by a user and input in TEdit.
When I use if statement to validate the answer(in TEdt) I get an error.
the code snipped:
answer := int1+int2;
UserAnswer:=StrToInt(edtAnswer.Text);
if(UserAnswer=answer)then
lblRemark.Caption:= 'Correct Answer';
When run the application I get an a convertError "Error...Not a valid integer".

When StrToInt meets a string that cannot be converted to an integer, it raises an exception. The documentation says:
If S does not represent a valid number, StrToInt raises an EConvertError exception.
You can catch that exception.
try
Value := StrToInt(Str);
except
on EConvertError do
// handle conversion error
end;
However, this is rather messy code. As a broad rule, exceptions should really be exceptional. They should not be part of the normal flow of your program. Another broad rule is that you should not handle exceptions. Let them propagate upwards to something that can handle them.
So, if we are breaking both of these rules, how can we do better? Well, by using TryStrToInt. This is a function that returns a Boolean to indicate whether or not the conversion succeeded. The code would then go like this:
if TryStrToInt(Str, Value) then
// handle conversion success by using Value
else
// handle conversion error

Assuming Int1 and Int2 are 'reasonable' for a user (eg. positive only) you can also use
Value := StrToIntDef( Str, -1 {or other illegal value});
Makes the code slightly simpler, maybe.

Related

Should variable be initialized before call to function?

When I call Functions to get a value, I usually initialize varible, in case function fails or doesn't return anything and I want to avoid dealing with uninitialized variable. I do the same for string, integer or any other type.
Example for integer variables:
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
IF vPropValue > 0 Then
...
this it the most common how I use it.
I know I could use:
If GetPropValue(vObject,'Height') > 0 Then
...
but with first example I avoid multiple calls to function, if I need result again later in the code.
Same for string (even though i know local strings are initialized to empty string, while integers are not an can hold any value)
vName := '';
vName := GetObjectName(vObject,'ObjectName');
IF Trim(vPropStrValue) <> '' Then
...
I know I could take steps to avoid duplicate value assignment,like making sure Function returns 0 if everything fails. But I have 100s of functions and I can't rely I never made a mistake how functions handle everything and I'm sure some don't return 0, if everything fails.
I'm trying to understand why this is not desirable practice and how to best avoid it.
EDIT
Here is example where function doesn't return proper value or 0:
function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
procedure TForm1.Button1Click(Sender: TObject);
var vValue:integer;
begin
vValue:=GetValue(11);
Button1.Caption:=IntToStr(vValue);
end;
In this case the value returned from function is some random number.
In this case the initialization appears to be valid approach. OR NOT?
EDIT 2:
As David pointed out in his answer, correct, there was a warning
[dcc32 Warning] Unit1.pas(33): W1035 Return value of function 'GetValue' might be undefined
but, I ignored it, for no reason, just didn't look there. As it let me compile it, I thought it was OK. So, I did look for the warning and I 'fixed' quite a few functions that had similar issue, because of all IFs Result might not have been defined.
EDIT 3 & CONCLUSION:
I hope it adds to the scope of question and explanation:
Maybe an example of another twist that I use in most of my Functions, would also explain why I thought my initialization of variable was needed, is that I wasn't sure my Functions would behave correctly all the times, especially in case of nested function. Mots of them still are set like this:
function GetProperty(vType:integer):integer;
begin
Try
if vType = 99 then
Result:=GetDifferentProperty(vType)// <-- Call to another Function, that could return whatever...
else
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
except
end;
end;
Now I'm addressing these Try Except End; but some functions are 10 years old and to expect them to work 100%, based on my experience back then, is not something to rely on.
As the only developer on this project, I assume I should trust my functions (and he rest of my code), but I can't imagine in multiple developers environment that all function are set up properly.
So my conclusion: since I didn't take care of the basics - properly designed Functions, I need to have all these checks (variable initialization, Try Except lines..) and probably some other unneccessary stuff.
Assuming that vPropValue is a local variable then this code
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
is indistinguishable from
vPropValue := GetPropValue(vObject,'Height');
A simpler example might be like so:
i := 0;
i := 1;
What is the point of assigning 0 to i and then immediately assigning 1 to i? So, you would surely never write that. You would write:
i := 1;
In your code, at the top of this answer, you assign twice to the same variable. The value assigned in the first assignment is immediately replaced by the value assigned in the second assignment. Therefore, the first assignment is pointless and should be removed.
The second example is a little more complex. Assuming that your functions are written correctly, and always assign to their return value, and that vName is a local variable, then
vName := '';
vName := GetObjectName(vObject,'ObjectName');
is indistinguishable from
vName := GetObjectName(vObject,'ObjectName');
The reason why I have added an extra proviso relates to a quirk of the implementation of function return values, discussed below. The difference between this case and the case above is the return value type. Here it is a managed type, string, whereas in the first example the type is a simple Integer.
Again, given the proviso about the function always assigning to the return value, the first assignment is pointless because the value is immediately replaced. Remove that first assignment.
Regarding the function in your edit, the compiler will warn you of its erroneous implementation if you enable hints and warnings. The compiler will tell you that not all code paths return a value.
function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
If neither condition is met, then no value is assigned to the result variable. This function should be:
function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200
else
Result:=0;
end;
I cannot stress how important it is that you always return a value from a function. In fact it is a terrible weakness that Delphi even allows your function to be compiled.
The reason that your double assignment sometimes appears useful to you is due to a quirk of of the implementation of function return values in Delphi. Unlike almost all other languages a Delphi function return value for certain more complex types is actually a var parameter. So this function
function foo: string;
is actually, semantically, the same as this:
procedure foo(var result: string);
This is a really odd decision made by the Delphi designers. In most other languages, like C, C++, C#, Java etc., a function return value is like a by-value parameter passed from callee to caller.
This means that you can, if you wish to be perverse, pass values into a function via its return value. For instance, consider this code:
// Note: this code is an example of very bad practice, do not write code like this
function foo: string;
begin
Writeln(Result);
end;
procedure main;
var
s: string;
begin
s := 'bar';
s := foo;
end;
When you call main, it will output bar. This is a rather strange implementation detail. You should not rely on it. Let me repeat myself. You should not rely on it. Do not get in the habit of initializing return values at the call site. That leads to unmaintainable code.
Instead follow the simple rule of ensuring that the function return value is always assigned by the function, and never read before it has been assigned.
More detail on the implementation of function return values is provided by the documentation, with my emphasis:
The following conventions are used for returning function result
values.
Ordinal results are returned, when possible, in a CPU register. Bytes are returned in AL, words are returned in AX, and double-words
are returned in EAX.
Real results are returned in the floating-point coprocessor's top-of-stack register (ST(0)). For function results of type Currency,
the value in ST(0) is scaled by 10000. For example, the Currency value
1.234 is returned in ST(0) as 12340.
For a string, dynamic array, method pointer, or variant result, the effects are the same as if the function result were declared as an
additional var parameter following the declared parameters. In other
words, the caller passes an additional 32-bit pointer that points to a
variable in which to return the function result.
Int64 is returned in EDX:EAX.
Pointer, class, class-reference, and procedure-pointer results are returned in EAX.
For static-array, record, and set results, if the value occupies one byte it is returned in AL; if the value occupies two bytes it is
returned in AX; and if the value occupies four bytes it is returned in
EAX. Otherwise, the result is returned in an additional var parameter
that is passed to the function after the declared parameters.
The following code (A)
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
is indistinguishable from (B)
vPropValue := GetPropValue(vObject,'Height');
The question of whether GetPropValue is "written correctly" is totally irrelevant.
Let's consider what happens even if you did write GetPropValue incorrectly E.g.
function GetPropValue(AObject: TObject; AStr: String): Integer;
begin
if AStr = 'Hello' then Result := 5;
end;
As you know when the input AStr is anything other than "Hello" the result of the function will be pretty much random. (For the sake of the discussion, lets assume it will return -42.)
Code block (A) will do the following:
Set vPropValue to 0
Then set vPropValue to - 42
Code block (B) will simply set vPropValue to - 42 immediately.
TIP: There is no point in writing a wasteful line of code just because you're worried you might have made a mistake in a function you call.
First, As David points out, you can avoid many mistakes simply by paying attention to your compiler hints and warnings.
Second, that sort of "paranoid" coding simply leads to more wasteful code because now you have to start considering invalid values as possible results.
This becomes worse when one day your "safe-value" is actually a valid value. E.g. how would you tell the difference between "default 0" and "correctly returned 0"?
Don't make programming artificially difficult by bloating code with unnecessary redundancies.
Side Note
There are a couple of special situations where the code can behave differently. But you should in any case avoid the designs that lead to those situations because they make it much more difficult to maintain the code.
I mention them purely for the sake of completeness, the advice above still stands.
1) if vPropValue is implemented as a property, the setter could have side-effects that cause different behaviour. While there's nothing wrong with properties, when they do unexpected things you have a serious problem on your hands.
2) if vPropValue is a field on the class (or worse a global variable), then (A) and (B) could behave differently but only if GetPropValue raises an exception. This is because the exception would prevent the result from being assigned. Note this is something to avoid except in special cases because it does make it more difficult to reason about what your code is doing.
And in fact, this is something that makes it so much more important to avoid the redundant initialisations. You want your special case code to look stand out from the rest.
Scavenging my advices from top-level comments in case Pastebin fails
function GetValueUpdate3(vType:integer):integer;
begin
// with SOME types like Integer you can use Pascal case-block instead of error-prone many-ifs-ladder
case vType of
99: Result:=GetDifferentProperty(vType);// <-- Call to another Function, that could return whatever...
1: Result:=100;
3..9: Result:=200;
else Result := 12345; // initialization with safe default value for illegal input like vType=2
end; // case-block
end;
function GetValueUpdate3e(vType:integer):integer;
begin
case vType of
99: Result:=GetDifferentProperty(vType);// <-- Call to another Function, that could return whatever...
1: Result:=100;
3..9: Result:=200;
else raise EInvalidArgument.Create('prohibited value - GetValue( ' + IntToStr(vType) +' )' );
// runtime eror when vType = 2 or any other illegal input
end;
end;
function GetValueUpdate1(vType:integer):integer;
begin
Result := 12345; // initialization with safe default value;
if vType=1 then Exit(100); // special value for special case #1
if (vType>2) and (vType<=9) then Exit(200); // special value for special case #2
// exit with default value
end;
procedure TForm1.Button1Click(Sender: TObject);
var vValue:integer;
begin
vValue:=GetValue(11);
Button1.Caption:=IntToStr(vValue);
end;
// http://stackoverflow.com/questions/33927750
You can use Assertions to validate if your input is correct.
Assertions help a lot in finding logical errors in your code.
Using assertions forces you to think about valid and invalid input data and helps writing better code.
Enabling and disabling assertions at compile time is done via the \$C or \$ASSERTIONS compiler (global switch)
In your example the function GetValue could use an assertion as follows:
function GetValue(vType:integer):integer;
begin
Assert((vType >= 1) and (vType <=9), 'Invalid input value in function GetValue');
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200
else Result := 0; // always catch the last else!
end;
In addition every if-statement should catch the final else! (In my opinion ALWAYS!)

Error Delphi XE2 - Exception class $C00000005

I am getting this error will debugging a project, which used to be in Delphi 7 and I have been upgrading to Delphi XE2, the same error happens in several methods.
First chance exception at $006DC660. Exception class $C0000005 with message 'access violation at 0x006dc660 read of address 0xffffffff'
This is one of the methods:
PFI = ^TFI;
TFI = record
Id : TToken;
Name : TName;
Parameters : string;
end;
function TListFI.IsIn(S: PChar): PFI;
function SearchName2(Item: PFI):Boolean;
var N1, N2: PChar;
begin
N1:= StrNew(Item^.Name);
N2:= StrNew(S); //Here is the issue
SearchName2:= (StrComp(StrUpper(N1), StrUpper(N2)) = 0);
StrDispose(N1);
StrDispose(N2);
end;
begin
IsIn:= PFI(FirstThat(#SearchName2));
end;
I have googled and I found someone describing a similar problem, and he affirms that when the incremental linker is disabled it works, can someone tell me what and where is it or give some advice to solve this situation.
[EDIT]
Removing the # now gives me the following error in IsIn:= PFI(FirstThat(SearchName2));
E2010 Incompatible types: 'TObject' and 'PFI'
I am adding the FirstThat procedure to see if it may help.
TFuncionColeccion = function (Elemento: TObject): Boolean;
function TColeccion.FirstThat (Rutina: TFuncionColeccion): TObject;
var
i: Integer;
begin
For i:=0 to Count-1 do
if Rutina(Items[i]) then
begin
FirstThat:=Items[i];
exit;
end;
FirstThat:=nil;
end;
It is (and always has been) an error to call local (nested) procedures by pointer, which is clearly what your FirstThat function does. The compiler has to do special things with the stack to call local functions and give them access to the parent scope's variables (S in your code), but the compiler can only know to do those special things when the local function is called directly. The compiler cannot know that the argument to FirstThat will be a local function, so it doesn't include the special code when FirstThat invokes the pointed-to function.
The bottom line is that the stack inside the function doesn't get set up the way it's supposed to, and that means any number of strange symptoms may appear. You'll have to use some other way. Maybe make SearchName2 be a two-argument function, and then write FirstThat to accept S as a parameter that it can forward to the function argument.
You shouldn't need to use the # operator when constructing a function pointer. When you do, the compiler tends to skip type checking, which is what allowed you to pass a local function pointer to FirstThat in the first place. When the function you're passing really matches the required prototype, the compiler will allow you to pass it without the # operator.
You are reporting an access violation in
StrNew(S)
where S is of type PChar. The explanation for that, with probability very close to 1, is that S is not in fact a pointer to null terminated array of WideChar.
In Delphi 7, PChar is an alias for PAnsiChar. That is a pointer to null terminated array of AnsiChar, i.e. 8 bit characters. In Delphi XE2, PChar is an alias for PWideChar, a pointer to null terminated array of WideChar, i.e. 16 bit characters.
It helps to understand what StrNew does. It walks the array until it finds a null character. For 8 bit text that is a single zero byte. For 16 bit text, the null is a zero 16 bit word. Then it allocates a new block of memory of the same length as the input string, and makes a copy into that new memory. The source code is:
function StrNew(const Str: PWideChar): PWideChar;
var
Size: Cardinal;
begin
if Str = nil then Result := nil else
begin
Size := StrLen(Str) + 1;
Result := StrMove(WideStrAlloc(Size), Str, Size);
end;
end;
The only plausible failure mode is that when StrLen walks the array, it attempts an invalid memory read. And that can only happen if your input parameter is invalid. In other words, this must be a programming error on your part.
One possible explanation is that you are in fact passing 8 bit text to this function despite promising to pass 16 bit text. An easy mistake to make, especially if you are not yet fully familiar with the Unicode change. The 8 bit text has a zero terminator, but the byte that follows happens not to be zero. Or the zero byte falls at an odd numbered offset from the start. And then StrNew continues walking the buffer, but now it is off the end and it so happens that it doesn't find a zero word before overrunning into an address that has not been allocated. And that is an access violation.
If that is so then solution will be either:
Change the function's parameter to be of type PAnsiChar, and fix the dubious casting at the call site.
Pass the function 16 bit text as it requires.
In your update you include the address which cannot be read, 0xffffffff. This is -1 in hex. And that would seem to be the most prosaic of errors. Your pointer is completely bogus! Your exact error message can be reproduced with this code: StrNew(PChar(-1)).
I don't have enough information here to tell you why your pointer is bogus. Hopefully you've learnt some debugging and diagnostic techniques that will enable you to solve the problem. At least you now know that the error is in your code.
Assuming that BuscaName2 and SearchName2 are one and the same thing, then you need look no further. Local procedures can only be called from a containing function. As #Rob correctly says, the use of # with procedures is almost always incorrect and is a warning sign of serious problems with your code.

How to prevent wrong inputs (only numbers) in Delphi?

I am trying to build a function/check to prevent wrong inputs from keyboard and I am a little bit lost here.
function ReadInputs : integer;
var
number : integer;
begin
repeat
Write('Set random number (1-10): ');
Readln(number);
if NOT((number <= 10) AND (number >= 1)) then
begin
Writeln('Error! Type 1-10!');
end;
until (number >= 1) AND (number <= 10);
result := column;
end;
How to prevent from any other character to be input except numbers 1-10? Why only numbers define in my function is not enough even when I set integer? When I type for example "A" it crash, so what is the right way? Thank you.
As it stands your program will fail with an error if the user inputs something that cannot be converted to an integer. That's because the variable that you passed to Readln is typed as an Integer. That is effectively an assertion that the user enters a number. But you want to be more flexible than that and allow the user to recover from non-numeric input.
What you need to do is read a string. This will always succeed. Then you can decide how to handle that string. For example you would try to convert to integer, and if that succeeded, perform further validity checks.
Perhaps like this:
var
Input: string;
Num: Integer;
....
Readln(Input);
if TryStrToInt(Input, Num) then
// perform checks on Num, etc.
else
// handle error: the value input was not numeric
You've already had a good answerfrom David H, but a little more explanation might help.
The ReadLn() procedure dates from before applications had GUIs and doesn't really restrict what the user can type in; the user might just press [return] or type characters that aren't digits (or +/-). ReadLn(AnInteger) will succeed if what the user types happens to convert to an integer, otherwise it fails.
On the other hand, Readln(AString) will always succeed, and the problem then is just how to check that it represents an integer, and DH's answer shows you how to do that.
In case you're wondering, a GUI application, you can control what characters an edit control will accept, e.g. by using a TMaskEDit, which allows you specify what character patterns are acceptable (e.g 6 digits and nothing else) - if the user types something which doesn't match the mask, the edit control doesn't accept it. However, even if you use a TMaskEdit, it's best to check that what's been typed in actually converts to the number type you're wanting.
Or you could use this on the OnKeyPress event:
if NOT(key in['0'..'9', #8]) then
key := #0;

How to get the first element in a string?

I'm trying to figure out a way to check a string's first element if it's either a number or not.
if not(myString[0] in [0..9]) then //Do something
The problem is that I get an error "Element 0 inaccessible - use 'Length' or 'SetLength"
Another way came to my head from my C-like exprieince - convert the first element of the string to char and check the char,but there is no difference in the compile errors.
if not(char(myString[0]) in [0..9]) then //Do something
How do I accomplish it?
Strings are 1-based:
if not (myString[1] in ['0'..'9']) then // Do something
Pascal and Delphi indexes string from 1. This is a legacy from time where zero byte contained length, while next 255 (index 1 to 255) contained actual characters.
Joel Spolsky wrote quite good article on string issues:
http://www.joelonsoftware.com/articles/fog0000000319.html
Delphi strings use a 1-based index, so just rewrite to
if not(myString[1] in ['0'..'9']) then //Do something
Also take note of the quotes around the 0..9, otherwise you would be comparing characters to integers.
We should keep in mind some things:
String in Delphi is 0-based for mobile platforms and 1-based for Windows.
String in old versions of Delphi is AnsiString (1-byte per char) and WideString in new versions (2-bytes per char).
Delphi supports set of AnsiChar, but doesn't support set of WideChar.
So if we want to write a code compatible with all versions of Delphi, then it should be something like this:
if (myString[Low(myString)]>='0') and (myString[Low(myString)]<='9') then
// Do something
if not(myString[0] in [0..9]) then //Do something
If you're using Delphi 2009, the TCharacter class in Character.pas has functions like IsDigit to help simplify these kinds of operations.
Once you fix the indexing, of course. :)
With later updates to Delphi mobile code, the bottom string index changed from 0 to 1. When you compile older programmes, they compile and run correctly using 0 starting index. Programmes created with the later IDE produce an error. When you have mixtures, life gets complex!
It would be good to be able to take an older programme and tell the IDE that you want it brought up to date (maybe this would fix other things, like fonts getting scrambled when you answer a phone call!) but it would be good to get things consistent!
The simplest way to check to see if the first character of string is an integer, and then dispatch:
var
iResult : integer;
begin
if TryStrToInt( mySTring[1], iResult) then
begin
// handle number logic here iResult = number
end
else
begin
// handle non number logic here
end;
end;
I use a utility function to test the entire string:
function IsNumeric(const Value: string): Boolean;
var
i: Integer;
begin
Result := True;
for i := 1 to Length(Value) do
if not (Value[i] in ['0'..'9','.','+','-']) then
begin
Result := False;
Break;
end;
end;
The above code is for Delphi versions prior to 2007. In 2007 and 2009, you could change the integer variable i to a character c, and use for c in Value instead.
To test for integers only, remove the '.' from the set of characters to test against.
This is incorrect. ISO strings and older Pascal's also started at one. It is just a general convention, and afaik the s[0] thing is a result of that being vacant, and cheap to code in the UCSD bytecode interpreter. But that last bit is before my time, so only my guessing.
It results from the Pascal ability to have arbitrary upper and lower bounds, which provides for more typesafety accessing arrays.
Really old Pascal strings (till early eighties) strings were even worse than C ones btw. Multiple conventions were in used, but all were based on static arrays (like early C), but they were typically space padded, so you had scan back from the end till the spaces ended.
(removed the legacy tag, since being 1 based is not legacy. Accessing s[0] as length IS legacy, but that is not what the question is about)
Foreach element in strName
if not element in [0-9] then
do something
else
element is a digit
end if
Don't forget the quote between digits number.

Getting a Field List from a DBExpress TSQLQuery

I am having a problem getting a list of fields from a query defined at run time by the users of my program. I let my users enter a SQL query into a memo control and then I want to let them go through the fields that will return and do such things as format the output, sum column values and so forth. So, I have to get the column names so they have a place to enter the additional information.
I would do fine if there were no parameters, but I also have to let them define filter parameters for the query. So, if I want to set the parameters to null, I have to know what the parameter's datatype is.
I am using Delphi 2006. I connect to a Firebird 2.1 database using the DBExpress component TSQLConnection and TSQLQuery. Previously, I was successful using:
for i := 0 to Qry.Params.Count - 1 do Qry.Params[i].value := varNull;
I discovered I had a problem when I tried to use a date parameter. It was just a coincidence that all my parameters up until then had been integers (record IDs). It turns out that varNull is just an enumerated constant with a value of 1 so I was getting acceptable results (no records) was working okay.
I only need a list of the fields. Maybe I should just parse the SELECT clause of the SQL statement. I thought setting Qry.Prepared to True would get me a list of the fields but no such luck. It wants values for the parameters.
If you have an idea, I would sure like to hear it. Thanks for any help.
Replied again 'coz I'm interested. My methods works (with my queries) because they have been pre-defined with the params' datatypes preset to the correct type:)
I'm not sure how you are expecting the query to know or derive the datatype of the param given that you are not even selecting the field that it operates against.
So I think your query setup and user input method will need more attention. I've just looked up how I did this a while ago. I do not use a parameterised query - I just get the "parameter values" from the user and put them directly into the SQL. So your sql would then read:
SELECT s.hEmployee, e.sLastName
FROM PR_Paystub s
INNER JOIN PR_Employee e ON e.hKey = s.hEmployee
WHERE s.dtPaydate > '01/01/2008'
therefore no parameter type knowledge is necessary. Does not stop your users entering garbage but that goes back to input control :)
Although a slightly different dataset type this is what I use with TClientDataset simple and effective :)
for i := 0 to FilterDataSet.Params.Count -1 do
begin
Case FilterDataSet.Params.Items[i].Datatype of
ftString:
ftSmallint, ftInteger, ftWord:
ftFloat, ftCurrency, ftBCD:
ftDate:
ftTime:
ftDateTime:
.
.
.
end;
end;
can you not do something similar with the query?
You guys are making this way too hard:
for i := 0 to Qry.Params.Count - 1 do begin
Qry.Params[i].Clear;
Qry.Params[i].Bound := True;
end;
I'm not sure what version of Delphi you are using. In the Delphi 2006 help under Variant Types, it says:
Special conversion rules apply to the
Borland.Delphi.System.TDateTime type
declared in the System unit. When a
Borland.Delphi.System.TDateTime is
converted to any other type, it
treated as a normal Double. When an
integer, real, or Boolean is converted
to a Borland.Delphi.System.TDateTime,
it is first converted to a Double,
then read as a date-time value. When a
string is converted to a
Borland.Delphi.System.TDateTime, it is
interpreted as a date-time value using
the regional settings. When an
Unassigned value is converted to
Borland.Delphi.System.TDateTime, it is
treated like the real or integer value
0. Converting a Null value to Borland.Delphi.System.TDateTime raises
an exception.
The last sentence seems important to me. I would read that as varNull cannot be converted to a TDateTime to put into the field, and hence you get the exception that you're experiencing.
It also implies that this is the only special case.
Couldn't you do something like:
for i := 0 to Qry.Params.Count - 1 do
begin
if VarType(Qry.Params[i].value) and varTypeMask = varDate then
begin
Qry.Params[i].value := Now; //or whatever you choose as your default
end
else
begin
Qry.Params[i].value := varNull;
end;
end;
What I ended up doing was this:
sNull := 'NULL';
Qry.SQL.Add(sSQL);
for i := 0 to Qry.Params.Count - 1 do begin
sParamName := Qry.Params[i].Name;
sSQL := SearchAndReplace (sSQL, ':' + sParamName, sNull, DELIMITERS);
end;
I had to write SearchAndReplace but that was easy. Delimiters are just the characters that signal the end of a word.
TmpQuery.ParamByName('MyDateTimeParam').DataType := ftDate;
TmpQuery.ParamByName('MyDateTimeParam').Clear;
TmpQuery.ParamByName('MyDateTimeParam').Bound := True;

Resources