Reputation: 199
I have a form with several labeled edits and radio buttons (grouped in radio groups) for the user to enter information, and a Save button to save the entries. The Save button also closes the form. If there are invalid entries (e.g., an alpha character instead of a number) or if some entries are left blank, I want to alert the user to correct the errors, and not close the form until there are no blank edit boxes or unchecked radio groups. I do an error check separately for each edit box and radio group by means of raising a messageDlg alerting the user to the mistake. However, if the user does not correct a mistake and tries to save the form, I want to keep the form open (not be able to close it) and alert the user to the mistake. He should be able to close the form only after all the entries are valid and there are no unchecked radio groups.
Based on some replies to my earlier message, I have written the following code. The first one is an example of data entry validation for one edit box (there are several such) and the second is the code for the OnClick event of my Save button.
procedure TfrmAnalysisOptions.lbleConstraintsMaxChange(Sender: TObject);
var
I: integer;
Val, ValidEntry: string;
Chr: char;
RangeMin, RangeMax: Double;
const Allowed = ['0'..'9', '.'];
begin
Val := lbleConstraintsMax.Text;
//initialize values
ValidEntry := '';
ConstraintsMaxChange := '';
//value can contain only numerals, and "."
for I := 1 to Length(Val) do
begin
Chr := Val[I];
if not (Chr in Allowed) then
begin
MessageDlgPos('The value entered for the max value of the ' +
'constraint must contain only a numeral, a decimal ' +
'point or a negative sign.',
mtError, [mbOK], 0, 300, 300);
Exit;
end
else ValidEntry := 'OK'; //validity check for this part
end;
//max value cannot be zero or less than the min value
if not TryStrToFloat(Val, RangeMax) then Exit
else if RangeMax = 0 then
begin
MessageDlg('Max value cannot be zero.', mtError, [mbOK], 0);
Exit;
end
else if not TryStrToFloat(lbleConstraintsMin.Text, RangeMin) then Exit
else if RangeMax < RangeMin then
begin
MessageDlgPos('Max value cannot be less than Min value.',
mtError, [mbOK], 0, 300, 300);
Exit;
end
else if (RangeMax < 0) then
begin
MessageDlgPos('A constraint cannot be negative.',
mtError, [mbOK], 0, 300, 300);
Exit;
end
//final validity check
else if ValidEntry = 'OK' then ConstraintsMaxChange := 'OK'
else MessageDlgPos('There was an unexpected problem with the ' +
'value entered in the max constraints box.',
mtError, [mbOK], 0, 300, 300);
end;
And this is the code for the OnClick event of the Save button. As you can see I am checking for the validity of several entries - and only if all of them are valid (that is have an 'OK' as the value of the corresponding variable), I allow the form to be closed. it is raising the messageDlg correctly when there is an invalid entry. However, it is continuing to raise the messageDlg even after the error is corrected and the Save button is pressed.
procedure TfrmAnalysisOptions.btnSaveOptionsClick(Sender: TObject);
//save input and output options
begin
//check if all the option questions have been answered
if not ((ConstraintsMinChange = 'OK') and //validation of correct entry as it is being entered
(ConstraintsMinExit = 'OK') and //validation of entry as user is moving to another entry after an incorrect entry
//several other such 'OK's
then
begin
MessageDlgPos('There is an invalid entry on the form. Please ' +
'correct it.', mtError, [mbOK], 0, 300, 300);
Exit;
end
else if //more messageDlgs if some other conditions are not met
else
begin
//save input and output options
end;
//finally if all the conditions are met, close the form
Close;
end;
Upvotes: 1
Views: 3387
Reputation: 1933
In your place I would approach the problem differently. Since you want to perform your check when the Save button is clicked and report any data entry problems at once, I would run a validation process that would return all the error messages at once. It would allow the user to see and fix all the problems before the next attempt to save.
It would be something like this:
Add a TMemo
Component to your form, make it invisible and aligned as alTop
. This memo will hold all the error messages, if any, and will show them. A good idea is to put all your editing controls on a panel and align it as alClient
.
The OnClick
event handler of the Save button should have something like this:
procedure TfrmAnalysisOptions.btnSaveOptionsClick(Sender: TObject);
begin
ErrorsMemo.Lines.Clear; // clear previous messages
GetErrorMessages(ErrorsMemo.Lines); // run our error check and retrieve messages
ErrorsMemo.Visible := ErrorsMemo.Lines.Count>0; // if there are messages, show them
if not ErrorsMemo.Visible then // if no errors are showing, close
Close;
end;
The GetErrorMessage
method will hold all the checks and will be something like
procedure TfrmAnalysisOptions.GetErrorMessages(aMessageList: TStrings);
var
I: Integer;
Chr: Char;
begin
// Here you put all the checks and add the error message to aMessageList,
// for instance:
// value can contain only numerals, and "."
for I := 1 to Length(Val) do
begin
Chr := Val[I];
if not (Chr in Allowed) then
aMessageList.Add('The value entered for the max value of the ' +
'constraint must contain only a numeral, a decimal ' +
'point or a negative sign.');
// Other validations follow here...
end;
The main idea here is to separate your checking process from your message presentation and make the form closing process simpler.
Upvotes: 1
Reputation: 596256
Set the button's ModalResult
property to mrNone
so it does not close the Form automatically, then have the OnClick
event validate the entries as needed and set the Form's ModalResult
property as needed only if they validate OK, eg:
procedure TfrmAnalysisOptions.btnSaveOptionsClick(Sender: TObject);
begin
// check the entries as needed ...
if (there is a validation error) then
begin
// do not close this form if there is an invalid entry
MessageDlgPos('There is an invalid entry. Please check and correct the entry.', mtError, [mbOK], 0, 300, 300)
Exit;
end;
// save all the entries into different variables ...
// now close the Form
ModalResult := mrOk;
end;
Update: based on your updates, try this instead:
type
TfrmAnalysisOptions = class(TForm)
private
//...
ConstraintsMinOK: Boolean;
ConstraintsMaxOK: Boolean;
//...
end;
procedure TfrmAnalysisOptions.lbleConstraintsMaxChange(Sender: TObject);
var
I: integer;
Val: string;
Chr: char;
RangeMin, RangeMax: Double;
begin
//initialize values
ConstraintsMaxOK := False;
Val := Trim(lbleConstraintsMax.Text);
if not TryStrToFloat(Val, RangeMax) then
begin
MessageDlgPos('The value entered for the max value of the ' +
'constraint is not a valid numeric value.',
mtError, [mbOK], 0, 300, 300);
Exit;
end;
//max value cannot be zero or less than the min value
if RangeMax < 0 then
begin
MessageDlgPos('The value entered for the max value of the ' +
'constraint cannot be negative.',
mtError, [mbOK], 0, 300, 300);
Exit;
end;
if RangeMax = 0 then
begin
MessageDlg('The value entered for the max value of the ' +
'constraint cannot be zero.',
mtError, [mbOK], 0);
Exit;
end;
if not TryStrToFloat(lbleConstraintsMin.Text, RangeMin) then Exit;
if RangeMax < RangeMin then
begin
MessageDlgPos('The value entered for the max value of the ' +
'constraint cannot be less than Min value.',
mtError, [mbOK], 0, 300, 300);
Exit;
end;
//final validity check
ConstraintsMaxChange := True;
end;
procedure TfrmAnalysisOptions.btnSaveOptionsClick(Sender: TObject);
begin
if (not ConstraintsMinOK) or
(not ConstraintsMaxOK)
//or several other such 'OK's missing
then
begin
MessageDlgPos('There is an invalid entry on the form. Please ' +
'correct it.', mtError, [mbOK], 0, 300, 300);
Exit;
end;
if //more messageDlgs if some other conditions are not met
//...
Exit;
end;
//save input and output options...
//finally if all the conditions are met, close the form
Close;
end;
Or better, don't do dynamic validation while the user is still entering data, that can lead to all kind of weird side effects if you are not careful. Wait until the Save button is pressed instead, then validate everything:
function ValidateNumeric(const S, Desc: String; var Value: Double): Boolean;
var
I: Integer;
begin
Result := TryStrToFloat(Trim(S), Value);
if not Result then
begin
MessageDlgPos('The value entered for the ' + Desc +
' is not a valid numeric value.',
mtError, [mbOK], 0, 300, 300);
Exit;
end;
end;
procedure TfrmAnalysisOptions.btnSaveOptionsClick(Sender: TObject);
var
RangeMin, RangeMax: Double;
begin
if not ValidateNumeric(lbleConstraintsMin.Text, 'min value of the constraint', RangeMin) then
begin
lbleConstraintsMin.SetFocus;
Exit;
end;
if not ValidateNumeric(lbleConstraintsMax.Text, 'max value of the constraint', RangeMax) then
begin
lbleConstraintsMax.SetFocus;
Exit;
end;
if RangeMax < 0 then
begin
MessageDlgPos('The value entered for the max value of the ' +
'constraint cannot be negative.',
mtError, [mbOK], 0, 300, 300);
Exit;
end;
if RangeMax = 0 then
begin
MessageDlg('The value entered for the max value of the ' +
'constraint cannot be zero.', mtError, [mbOK], 0);
Exit;
end;
if RangeMax < RangeMin then
begin
MessageDlgPos('The value entered for the max value of the ' +
'constraint cannot be less than Min value.',
mtError, [mbOK], 0, 300, 300);
Exit;
end;
//more validations...
//more messageDlgs if some other conditions are not met ...
//save input and output options...
//finally if all the conditions are met, close the form
Close;
end;
Upvotes: 3
Reputation: 1007
Here is a typical scheme how we treate modal result and validity:
procedure TSomeModalForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if ModalResult = mrOK then
begin
CanClose := CheckValidity;
if CanClose then
CanClose := SaveData;
end
else if dtsPacket.Modified then
CanClose := dtsPacket.Cancel
else if dtsNote.Modified then
CanClose := dtsNote.Cancel
else
CanClose := TRUE;
end;
CheckValidity functions looks like this:
function TSomeModalForm.DoCheckValidity: boolean;
var
InvCnt: Integer;
begin
Result := inherited;
InvCnt := 0;
//
HighlightInvalidValue(not edClient.IsEmpty, edClient, InvCnt);
HighlightInvalidValue(not edBranch.IsEmpty, edBranch, InvCnt);
HighlightInvalidValue(not edDstClient.IsEmpty, edDstClient, InvCnt);
HighlightInvalidValue(not edDstBranch.IsEmpty, edDstBranch, InvCnt);
//
Result := Result and (InvCnt = 0);
if not Result then
ShowInErrBox(1);
end;
The code is based on our proprietary non VCL controls and assumes some validation support in a form ascendant, but a principle should be obvious.
Upvotes: 2