Prevent inserting an empty memo - delphi

I am checking the contents of the memo before an insert query.
if memo1.lines.Text = '' then begin
showmessage('Warning:Missing data!');
abort;
end else ....
It works ok as long as there is nothing in the memo1.
However, when user hits enter inside the empty memo and the cursor moves
to the second line,running the query, fires the insert without a warning message,
though theres nothing in the memo.
Is there a way to prevent this ?

After the user hits enter your memo's text contains whitespace (the line-return), and so it doesn't equal ''.
You need to Trim the Text first. http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.Trim
var
memoText : String;
...
memoText := Trim(memo1.lines.Text);
if memoText = '' then
begin
showmessage('Warning:Missing data!');
abort;
end else ....

If the user pressing RETURN is resulting in an additional line in the memo, then you must have the WantReturns property set to TRUE, and an OnKeyPress or OnKeyDown event handling the #13 key or VK_RETURN virtual key code.
If you set WantReturns to FALSE then the user must use CTRL+ENTER to insert a new line, and a simple striking of the RETURN key will not insert an empty line.
But if you are happy with the way your user interface currently behaves and simply want to check whether or not the memo contains only whitespace then you will have to validate this separately using Trim(Memo.Text) or some other mechanism for testing the content of the memo to meet your applications definition of "not empty".
As others have noted, using Trim() on Memo.Text simply in order to test for the presence of non-whitespace characters is potentially very inefficient, though how much of a concern this is in your case will depended a great deal on the expected content of the memo in your application.
A more efficient way to test for a non-whitespace string would be:
function ContainsOnlyWhitespace(const aString: String): Boolean;
var
i: Integer;
begin
result := FALSE;
for i := 1 to Length(aString) do
if (aString[i] > ' ') then
EXIT;
result := TRUE;
end;
Which would be used thus:
if ContainsOnlyWhitespace(Memo.Text) then
begin
// show warning message etc...
end;
This will be significantly more efficient than Trim() since it does not involve producing any new strings or modifying the string being tested.

Related

Search and Replace in a Trichedit

We are working with XE7. We are trying to build a search and replace feature into a TRichEdit.
We would like it to work just like it works in the Delphi compiler. We display our own search and replace dialog, we are not using the Delphi replace dialog.
We display the dialog, and when the user clicks the Replace button, the dialog closes, and for each instance found we want to popup a message dialog asking the user whether they want to replace that occurrence.
When we do this, the user cannot see what is selected, and if they click Yes on the message dialog, nothing is being replaced.
Our code is below:
procedure Tviewform.select_text(fndpos:integer);
begin
setfocus;
asciieditor.selstart:=fndpos;
asciieditor.sellength:=length(vwvars[curviewwin]^.finddata);
end;
procedure Tviewform.search_and_replace;
var position,endpos,fndans,cont:integer; foptions:tsearchtypes; msgques,stopall:word;
begin
with asciieditor do
begin
endpos:=length(asciieditor.text)-vwvars[curviewwin]^.sstartpos;
foptions:=[];
stopall:=0;
cont:=0;
if findinfo.onbut.checked then foptions:=foptions+[stMatchCase];
lines.beginupdate;
fndans:=findtext(vwvars[curviewwin]^.finddata,vwvars[curviewwin]^.sstartpos,endpos,foptions);
while (fndans<>-1) and (stopall=0) do
begin
select_text(fndans);
msgques:=mainform.silang1.messagedlg('Replace with '+vwvars[curviewwin]^.replace+'?.',mtinformation,[mbyes,mbno,mbcancel],0);
if msgques=mryes then
begin
asciieditor.clearselection;
seltext:=vwvars[curviewwin]^.replace;
end else
begin
if msgques=mrcancel then
begin
cont:=0;
stopall:=1;
end else cont:=1;
end;
asciieditor.Repaint;
if cont=1 then
begin
inc(vwvars[curviewwin]^.sstartpos,length(vwvars[curviewwin]^.finddata));
endpos:=length(asciieditor.text)-vwvars[curviewwin]^.sstartpos;
fndans:=findtext(vwvars[curviewwin]^.finddata,vwvars[curviewwin]^.sstartpos,endpos,foptions);
end;
end;
lines.endupdate;
end;
end;
I see a number of issues with your code:
You are moving input focus away from the TRichEdit when you call select_text() and MessageDlg(), so make sure that TRichEdit.HideSelection is set to False (it is True by default) in order to actually see the selection.
You are calling BeginUpdate() on the TRichEdit, which disables screen redraws for it, including Repaint().
You don't need to call TRichEdit.ClearSelection() before setting the TRichEdit.SelText.
When updating vwvars[curviewwin]^.sstartpos after each replacement, you need to reset it to fndAns (the current selection's position) plus the length of vwvars[curviewwin]^.replace (the new text), not the length of vwvars[curviewwin]^.finddata (the old text). You are not moving it beyond the new text, so you are searching older text over and over.
If the user chooses No in the MessageDlg(), you are not updating vwvars[curviewwin]^.sstartpos to move past the text that the user didn't want to replace.
When setting endpos, length(asciieditor.text) can be asciieditor.gettextlen() instead for better efficiency. But more importantly, you should not be subtracting vwvars[curviewwin]^.sstartpos from endpos at all. By doing that, you are skipping more and more text at the end of the TRichEdit from the search with each replacement made. In fact, you should be using just the asciieditor's current text length as the end position of each search, so you don't actually need endpos at all.
Depending on the size of your TRichEdit and its content, if it has scrollbars enabled, you should send the TRichEdit a EM_SCROLLCARET message each time you find a matching text and before you prompt the user with the MessageDlg(), in case the matching text is off-screen.
With that said, try something more like this:
procedure Tviewform.search_and_replace;
var
fndAns: Integer;
fOptions: TSearchTypes;
msgQues: Word;
procedure select_text;
begin
{AsciiEditor.}SetFocus;
AsciiEditor.SelStart := fndAns;
AsciiEditor.SelLength := Length(vwvars[curviewwin]^.finddata);
AsciiEditor.Perform(EM_SCROLLCARET, 0, 0);
AsciiEditor.Update;
end;
begin
fOptions := [];
if FindInfo.OnBut.Checked then Include(fOptions, stMatchCase);
repeat
fndAns := AsciiEditor.FindText(vwvars[curviewwin]^.finddata, vwvars[curviewwin]^.sstartpos, AsciiEditor.GetTextLen, fOptions);
if fndAns = -1 then Break;
select_text;
msgQues := MainForm.SiLang1.MessageDlg('Replace with ' + vwvars[curviewwin]^.replace + '?', mtinformation, [mbYes,mbNo,mbCancel], 0);
if msgQues = mrYes then
begin
AsciiEditor.SelText := vwvars[curviewwin]^.replace;
AsciiEditor.Update;
Inc(fndAns, Length(vwvars[curviewwin]^.replace));
end
else if msgQues = mrNo then
begin
Inc(fndAns, Length(vwvars[curviewwin]^.finddata));
end else
Break;
vwvars[curviewwin]^.sstartpos := fndAns;
until False;
end;

In a textfile, How to express if ( Line 1's answer = Line 2's answer ) then

I just started Delphi.
I'm making a simple login form, and the text file (UserInfo.txt) is set up like this
I just want Delphi to run through the TextFile to look for the string, then if that string equals edtUseranme.Text then it must check if that string is equal to the string on the second line.
If it equals then it can continue to the next Form
Here is my code:
for I := 0 to Eof(tFile) do
begin
Readln(tFile, sLine);
Inc(I);
if ReadLn(tFile) = edtUsername.Text then
begin
if edtUsername.Text = then
begin
frmMain.Show;
frmLogin.Hide;
end
else
begin
ShowMessage('INPUT INVALID (Try again)');
end;
end;
end;
At that second if statement, I don't know what to put after =.
Like Ken White suggested, you could redesign your text file and then use a TStringList to parse it. That would certainly make it easier to work with the data. However, if you want to stick with the code you already have, then you could do something more like this instead:
var
tFile: TextFile;
//Declare variables for temporally storing read username and password
sUser, sPass, sDivider: string;
Valid: Boolean;
...
Valid := False;
//use while loop to read every line till the end of file
while not Eof(tFile) do
begin
//Read username into sUser variable
Readln(tFile, sUser);
//Read password into sPass variable
Readln(tFile, sPass);
//Read divider into sDivider variable
Readln(tFile, sDivider);
//Use if clause to perform multiconditional logical comparison to see if
//read username and password equals to those specified in suitable edit
//boxes
//NOTE: Each separate logical condition must be in its own brackets
if (sUser = edtUsername.Text) and (sPass = edtPassword.Text) then
begin
//If conditions are met we set local variable Valid to true
Valid := True;
//and then break the loop
Break;
end;
end;
//If local variable valid was set to True show the main form
if Valid then
begin
frmMain.Show;
frmLogin.Hide;
end else
//else we show message about invalid input entered
ShowMessage('INPUT INVALID (Try again)');
Your file format is poorly designed for what you're wanting to do, I'm afraid.
You'd be better off using a more standard format (realizing, of course, that it's totally insecure to store names and passwords as plain text). Delphi makes it easy to work with name/value pairs using TStringList with text like this:
Name=Value
This would mean your text file would contain the username=password type lines:
Bob=Password
Sue=Bambi
You then use TStringList to load the file and read the value associated with the name, like this:
var
SL: TStringList;
UserName, Password: string;
begin
UserName := Edit1.Text; // Your username control
Password := Edit2.Text;
// You should check to make sure that both the username and password
// were entered here before proceeding. I'll leave that to you to do.
SL := TStringList.Create;
try
SL.LoadFromFile('C:\Temp\UserInfo.txt'); // Replace with your filename
// The next line looks for a name in the stringlist equal to the
// entered username, and if it's found compares its value to the
// password that was provided.
//
// You'll need to handle making sure that you do a case-sensitive
// (or non-sensitive, depending on your needs) comparison is done
// if needed.
if SL.Values[UserName] = Password then
// Login succeeded
else
// Login failed.
finally
SL.Free;
end;
end;

Change Password Delphi v7

Delphi v7
This code is designed to allow the user to change his password. It appears to execute correctly, but the new password is not saved in the the password data field. I must have done something wrong, but I cannot see it.
procedure TForm4.btnPswordClick(Sender: TObject);
var
I: integer;
begin
tblLogin.First;;
for I := 0 to tblLogin.RecordCount do
Begin
If tblLogin.FieldByName('Username').Value = Edit1.Text then
if tblLogin.FieldByName('Password').Value = Edit2.Text then
sign2.Visible := True; //Test in this case tells the application to make Label1
visible if the //username and password are correct
tblLogin.Next;
end;
I:= I+1; //ends search loop of records so program can move on
If sign2.Visible = False then
begin
MessageDlg('Error Username, or Password not correct',
mtConfirmation, [mbCancel], 0);
end
else
if edit3.Text <> edit4.Text then
begin
MessageDlg('Error New Password does not match',
mtConfirmation, [mbCancel], 0);
end
else
begin
tblLogin.Edit;
tblLogin.FieldByName('Password').Value := Edit3.Text;
tblLogin.Post;
//form1.Show;
//form4.Close;
end;
My comment about formatting your code was just me being snide, but in fact I think it really would have helped you find the error yourself. Properly indented, your first loop is this:
tblLogin.First;;
for I := 0 to tblLogin.RecordCount do
Begin
If tblLogin.FieldByName('Username').Value = Edit1.Text then
if tblLogin.FieldByName('Password').Value = Edit2.Text then
sign2.Visible := True; //Test in this case tells the application to make Label1 visible if the
//username and password are correct
tblLogin.Next;
end;
The next line of code is this:
I:= I+1; //ends search loop of records so program can move on
The comment suggests that you expect that line to cause the loop to terminate. But that line isn't in the loop. If it were in the loop, your code never would have compiled because you're not allowed to modify the loop-control variable inside a loop. Even outside the loop, the compiler should have warned you that the current value of I is undefined. That's because the compiler makes no guarantees about the final value of a loop-control variable once the loop has terminated. Adding 1 to an undefined value is still an undefined value.
Furthermore, your loop iterates over more records than your database table contains. If the upper bound of a for loop hasn't had 1 subtracted from it and isn't the result of High or Pred, then you're probably doing something wrong.
Since your I+1 line doesn't actually terminate the loop, you end up traversing all the way to the end of the table, so when you call tblLogin.Edit, you're putting the final record into edit mode, not the record with the matching account details.
Fixing the indentation will also show that you didn't even include all the code for that function. There's a begin without a matching end somewhere.
You could avoid all this code by using a single UPDATE statement on your database:
UPDATE tblLogin SET Password = ? WHERE Username = ? AND Password = ?

TStringList .add produces duplicates from random function

Having a problem I can't seem to put my finger on. I am trying to gather strings (random code with letters and numbers) from a function call and place into my TStringList variable. Relevant code is below.
If I run a test, the strings repeat for a given amount of time then a new one is produced. If I introduce a sleep(xx) or showmessage command to happen after each time a code is produced (see 'edits' below) it copies/returns to memo fine and everything looks fine. If I remove the 'delay' I get repeats from function again.
The part of function to add to TStringList:
..
AddToMemo:=TStringList.Create;
AddToMemo.Clear;
AddToMemo.Sorted:=False;
for loop := 1 to totalamount do
begin
sResult:=MakeCode(charspercode, cbUpperLowerCase, cbAvoidChars, customchars);
Sleep(50);
// (or):
//ShowMessage(sResult);
// ^ If I leave a Sleep or ShowMessage in, I can see sResult just fine and
// program works fine - results in memo are correct as well. If I remove
// it, I get repeated entries.
AddToMemo.add(sResult+IntToStr(loop));
// If I remove "sResult+" from AddToMemo.add the ".add"
// works - shows loop numbers in my updated memo
// If left in, I see one code (1st one produced) and no
// appended number at all in Memo produced.
end;
Result:=AddToMemo;
end;
Edit: As I mention below if I leave a ShowMessage or Sleep(xx) call in to pause between .add's, it works fine. If I remove it, I get a bunch of duplicate entries in final tmemo.
Edit: MakeCode is a function to return a single random string of chars+numbers (A..Z a..z 0..9). It works fine on it's own.
(Edit for Answer 2)
No exceptions showed up.
So if I do not include sleep() it may generate 500 strings but they are all repeats; after a given amount of time it does change. The amount of repeats from the function call decreases as I increase sleep command. At around Sleep(40); it shows up correctly from function. But of course this is time consuming and unacceptable.
The 'guts' of MakeCode()
function MakeCode(CharsPerCode: Integer; bULCase, bAvoidChars: Boolean; sCChars: String): String;
var
i: integer;
s: string;
begin
//(misc stuff here)
begin
randomize;
s[0]:=chr(CharsPerCode);
for i:=1 to CharsPerCode do
repeat
s[i]:=chr(random(128));
until
(s[i] in ['A'..'Z','a'..'z','0'..'9'])
end;
Result:=s;
end;
This is a behavior of Randomize. The random number generator is initialized by a calculation on the system clock. If you call it in each iteration in a quick loop, it is initialized with the same seed. That's why a Sleep(50) is changing the outcome. Call randomize for once, for instance, before starting to populate the string list.
...
AddToMemo.Clear;
AddToMemo.Sorted:=False;
Randomize; // <-- possibly here
for loop := 1 to totalamount do
...
function MakeCode(CharsPerCode: Integer; bULCase, bAvoidChars: Boolean; sCChars: String):
...
begin
// randomize; // <-- not here!
s[0]:=chr(CharsPerCode);
The below quote is from the Delphi documentation:
Do not combine the call to Randomize in a loop with calls to the
Random function. Typically, Randomize is called only once, before all
calls to Random.
Without seeing what MakeCode() is actually returning in sResult, my guess would be that sResult contains non-printable control characters (null characters in particular) that cause the Memo or even the RTL to skip subsequence characters.
You need to show some more Code, evtl. MakeCode. I would try the same with a constant String in sResult without MakeCode, do you get the same? try something like:
for loop := 1 to totalamount do
begin
try
sResult:=MakeCode(charspercode, cbUpperLowerCase, cbAvoidChars, customchars);
AddToMemo.add(sResult+IntToStr(loop));
except on e: exception do
showmessage(e.message);
end;
end;
Do you get any exceptions?

How do I verify that a text box contains only numbers in Delphi?

Might it is very simple question but I never touched delphi.
I have a edit box and that can accept character. But on some special condition I have to verify the edit box character are only numbers.
How can we do that?
Note: user can enter any char but at the time of validation I have to verify above one.
I don't know what event you intend to use to invoke validation, but the validation can be done like this:
if TryStrToInt(Edit1.Text, Value) then
DoSomethingWithTheNumber(Value)
else
HandleNotANumberError(Edit1.Text);
I don't understand why you would want to allow a user to enter a character, and later not allow it to pass validation.
If you really do need to block entry, then a control that does this for you is better than hacking this up yourself. If your version of delphi is really old, then try out the JVCL: TJvValidateEdit in the JVCL component library, for example, in all versions of delphi. However, in regular recent delphi versions (2009 and later), there is already built in several possible solutions including TMaskEdit and TSpinEdit.
If you really only need to write a validation method, then consider using a regex or hand-coded validation function, and keep that code separate from the control.
// Taking OP question obsessively literally, this
// function doesn't allow negative sign, decimals, or anything
// but digits
function IsValidEntry(s:String):Boolean;
var
n:Integer;
begin
result := true;
for n := 1 to Length(s) do begin
if (s[n] < '0') or (s[n] > '9') then
begin
result := false;
exit;
end;
end;
end;
I know you said user can enter any char but at the time of validation.
However I would like to offer an alternative, because it seems very silly to allow a user to enter values, only to complain to the user 1 minute later; that just smells well... not nice.
I would disallow entry of anything but numbers.
If you have integers thats particularly easy:
Fill in the OnKeyPress event for the editbox.
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char) ;
begin
if not(Key IN ['0'..'9', #8, #9, #13, #27, #127]) then key:= #0;
end;
This will drop anything that's not a number.
If you allow negative numbers you'll need to extra checking to see if the - has not been entered before.
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char) ;
var
Edit1Text: string;
begin
if (Key = '-') and Pos('-',Edit1.Text) = 0 then begin
Edit1.Text:= '-' + Edit1.Text; //Force the '-' to be in the front.
end
else if (Key = '-') and Pos('-',Edit1.Text) <> 0 then begin //else flip the sign
Edit1Text:= Edit1.Text;
Edit1.Text:= StringReplace(Edit1Text, '-', '',[]);
end;
if not(Key IN ['0'..'9', #8, #9, #13, #27, #127]) then key:= #0;
end;
Because the user can also paste data into an edit box, you'll still have to check the data upon change of the text in the edit.
Because this gets rather fiddly in the ONKeyPress event I use a custom edit component that does this kind of checking and prevents the user from entering foot-in-mouth input into an edit box.
Personally I don't believe in ever issuing an error message, you should always strive to not allow the user to enter invalid data in the first place.
But on some special condition I have
to verify the edit box character are
only numbers.
You need two edit-controls. One for the numbers-only value and one for the allow-all value. Enable or disable the control which match the condition. If the two controls have good captions (and perhaps hints, why a control is enabled or disabled) so the user knows what he has to enter and why.
I don't like blocking the user. A scenario:
I enter "abc123"
on leaving the edit control I get an error message "only numbers allowed"
I realize that I have forgotten to reach the special condition
I want to do something to reach the special condition
but I can't because I always get the error message "only numbers allowed"
so I have to correct the value to "123"
do the things to reach the special condition
retype my old "abc123" value again
aarrgghh :-)
For simple data entry forms I do the following: I allow wrong input but switch the font color to red for each edit control with an invalid input (red font color is not enough when an empty value is not allow). If the user try to post the data I give one error message which inform the user about all invalid input fields and abort the post.

Resources