I would like that user could write only numbers from 1 to 49 in edit box. I know how to exclude letters and has possibility to put only numbers but I can't limit it to specific numbers (e.g from 1 to 49 - like in lottery game).
I added KeyDown event to edit box and put this code:
if not (KeyChar in ['1'..'9']) then
begin
ShowMessage('Invalid character');
KeyChar := #0;
end;
How can I modify it?
Following David's advice, a pattern I often use looks something like this :
function Validate1To49(AStr : string; var Value : integer) : boolean;
begin
result := TryStrToInt(AStr, Value) and
(Value >= 1) and (Value <= 49);
end;
procedure TForm1.Edit1Change(Sender: TObject);
var
tmp : integer;
begin
if Validate1To49(Edit1.Text, tmp) then
Edit1.Color := clWhite
else
Edit1.Color := clRed;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
theValue : integer;
begin
if Validate1To49(Edit1.Text, theValue) then begin
// go ahead and do something with "theValue"
end else
ShowMessage('Value not valid');
end;
Here if the user inputs anything invalid there is immediate visual feedback that is not intrusive like a modal message box. Here I coloured the edit box red, but you can also show/hide or change the colour of a warning label above the edit box with a message detailing the expected input, use a green checkmark, or whatever else seems sensible
This has the benefit that the user can see immediately whether or not their inputs are valid. The validation methods can be wrapped up so that they can be re-used when the user attempts to initiate an action requiring those inputs. At this point I feel it's fine to use a modal messagebox because the user has plainly missed the obvious cues already in front of them. Alternatively, when validating in the OnChange handler you can simply disable any action controls (like buttons, etc) that would allow the user to proceed. This requires validating all input controls required for the action - again, usually you would wrap the entire validation action into a single method for sensible re-use.
For simple values like integers, a good SpinEdit control can be useful (the VCL one is included in the Samples package - not always installed by default). The above pattern is more flexible, however and can be used for any type of input. A SpinEdit won't provide any feedback, however - the user will simply type and nothing will show up. They may wonder whether the application is broken if there is no clear guidance visible about what the input element will accept.
The same question can also be answered in this way also by writing a OnKeyPress event for a Edit box. By this way the user will not be able to enter the number greater than the limit which we define.
procedure TfrmCourse.edtDurationKeyPress(Sender: TObject; var Key: Char);
var
sTextvalue: string;
begin
if Sender = edtDuration then
begin
if (Key = FormatSettings.DecimalSeparator) AND
(pos(FormatSettings.DecimalSeparator, edtDuration.Text) <> 0) then
Key := #0;
if (charInSet(Key, ['0' .. '9'])) then
begin
sTextvalue := TEdit(Sender).Text + Key;
if sTextvalue <> '' then
begin
if ((StrToFloat(sTextvalue) > 12) and (Key <> #8)) then
Key := #0;
end;
end
end;
end;
Related
if Length(idStrArray)>0 then
begin
with DataModule4.ADQueryTemp do
begin
Close;
SQL.Clear;
SQL.Add('SELECT id, pato, ftest, res FROM tbl ');
SQL.Add('WHERE id IN ('+idStrArray+')');
Open;
(rprMasterDataFish as Tfrxmasterdata).DataSet := frxDst_Multi;
(rprMasterDataFish as Tfrxmasterdata).DataSetName := 'Multi';
end;
end;
Hello,
I have TfrxDBDataset component. I can add fields from table like above. But i also want to add fields and values manually at runtime.
I have text file like this :
id note
1 sample
2 sample
I want to read this text file and insert note to frxDst_Multi. Is this possible ?
I dont want to create a new column as note in tbl. Because, i have too many mysql server.
Thanks in advice,
You can't add fields to a dataset while it is open, so you have to do it before
it is opened, either in code or using the TDataSet fields editor. If you are
doing it in code, you can add the field in the dataset's BeforeOpen
event.
The next problem is that is you don't want to field to be bound to the table the
dataset accesses, you need to add it as a calculated field and set its value
in the dataset's`OnCalcFields' event - see example below.
Ideally, the added field would be a TMemoField, but unfortunately a TMemoField
can't be a calculated field (FieldKind = ftMemo). So probably the best thing you can do is to make it a String field, but then you will need to
give it a fixed maximum size and truncate the field's value at that size when you calculate its value.
Btw, I don't know whether your TfrxDBDataset supports the fkInternalCalc fieldkind, but if it does, then you could try adding the note field as a TMemoField instead of a TStringField one.
The one thing I haven't been able to do is to load the field's value from
an external file because you haven't said in your q how to determine the
name of the file which is to be read.
Obviously, the IsNull check in the frxDst_MultiCalcFields event is to avoid the overhead of reloading the file if its contents have already been read.
const
NoteFieldSize = 4096;
procedure TForm1.AddNoteField;
var
NoteField : TField;
begin
if frxDst_Multi.FindField('Note') = Nil then begin
NoteField := TStringField.Create(frxDst_Multi);
NoteField.FieldName := 'Note';
NoteField.Size := NoteFieldSize;
NoteField.FieldKind := fkCalculated;
NoteField.DataSet := frxDst_Multi;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
frxDst_Multi.Open;
end;
procedure TForm1.frxDst_MultiCalcFields(DataSet: TDataSet);
var
S : String;
begin
if DataSet.FieldByName('Note').IsNull then begin
S := 'a note'; // replace by code to set the field value
DataSet.FieldByName('Note').AsString := Copy(S, 1, NoteFieldSize);
end;
end;
procedure TForm1.frxDst_MultiBeforeOpen(DataSet: TDataSet);
begin
AddNoteField;
end;
I am looking at creating autoform filler for Delphi, and obviously need a good method to capture which input boxes are the login boxes on each site so was wondering if I use a Twebbrowser component and load the page then click on the username and password boxes on the particular sites if I can then extract the form name and input box names that I clicked on.
In Short I need delphi to capture the name of the selected input box on a web page loaded into a twebbrowser component.
Any good methods to capture this information from the page loaded in a twebbrowser page would be appreciated!.
The following code shows how to find an INPUT element named 'input1':
var
E : IHtmlElement;
D : IHtmlDomNode;
Doc2 : IHtmlDocument2;
Doc3 : IHtmlDocument3;
All : IHTMLElementCollection;
i : Integer;
begin
Doc3 := WebBrowser1.Document as IHtmlDocument3;
D := Doc3.GetElementByID('input1') as IHtmlDomNode;
if D <> Nil then begin
...
If you need to find more than one INPUT element or you wish to pattern-match the
name of the INPUT element(s), you can do this by retrieving the document's
IHtmlDocument2 interface and then iterating its all collection:
Doc2 := WebBrowser1.Document as IHtmlDocument2;
All := Doc2.all;
for i := 0 to All.Length - 1 do begin
E := All.Item(Null, i) as IHtmlElement;
// Test E and do what you like with it
end;
You could use a function like this to find the parent FORM element of an INPUT element
function GetParentFormElement(E : IHtmlElement) : IHtmlElement;
begin
Result := Nil;
while E <> Nil do begin
if CompareText(E.tagName, 'form') = 0 then begin
Result := E;
exit;
end;
E := E.parentElement;
end;
end;
and use it like this:
E := D as IHtmlElement;
E := GetParentFormElement(E);
Assert(E <> Nil);
not all forms have a name or id so how do I get the number or reference of a parent form if there are a number of forms in a page?
Equally, not all INPUT elements are contained in a FORM one. TBH, I don't know of a robust way of doing what you want which will survive a page's author making changes to it. Anyway, there must be some way of identifying a given INPUT element, otherwise the server wouldn't be able to extract the user's response, mustn't there? So it's just a question of figuring out what that might be for a particular page. It it is not in the attributes of the element, then maybe you could look for the text of a nearby text element - after all, there must be some kind of prompt to the user to tell them what to fill in where. But this is really a different issue than the substance of your original q, which I hope I have answered. If you need more help on this specific point, I suggest you ask in a new q. Make sure you include details (code) of what you've already tried, as qs lacking them tend not to be received well at SO.
Sorry about my formatting, new to posting here!. Reduced version.
procedure TForm5.Button2Click(Sender: TObject);
var
Document: IHTMLdocument2;
MyEl: IHTMLElement;
begin
MyEl := (WebBrowser1.Document as IHTMLDocument2).activeElement;
If MyEl.tagName = 'INPUT' then
begin
edit2.Text := MyEl.getAttribute('Name', 0);
end;
end;
Code like this logs all table inserts (from the entire application):
procedure TForm1.ACRDatabase1AfterInsertRecord(Sender: TACRDataSet;
const TableName: WideString; const FieldValues: TACRArrayOfTACRVariant);
begin
if (AnsiUpperCase(TableName) = AnsiUpperCase(LogTable.TableName)) then
Exit;
if (Sender is TACRTable) then
LogTable.Insert();
LogTable.FieldByName('EventTime').AsDateTime := Now;
LogTable.FieldByName('TableName').AsString := TableName;
LogTable.FieldByName('EventType').AsString := 'Insert ';
LogTable.FieldByName('Whatever').AsString := FieldValues[4].AsString;
LogTable.Post();
end;
But fieldValues are different for each table so you might crash
the application (almost sure) using fieldvalues i.e their index number.
How do you overcome this ? Is it possible to log each table separately ?
As I mentioned in a comment, I don't have Accuracer, but thought it might be helpful
to post a generic method of doing client-side logging, which can capture the value
of one or more fields and be used for as many datasets as you need. You may be
able to use part of it in your ACRDatabase1AfterInsertRecord handler, as its Sender
parameter appears to identify the dataset into which the new row has been inserted.
As you can see, there is a LogFields procedure which can be included in the AfterInsert
handler of any dataset you like and this calls a separate GetFieldsToLog procedure which
adds the names of the field(s) to log for a given dataset to a temporary StringList. It's
only the GetFieldsToLog procedure which needs to be adapted to the needs of a given set of datasets.
procedure TForm1.GetFieldsToLog(ADataSet : TDataSet; FieldList : TStrings);
begin
FieldList.Clear;
if ADataSet = AdoQuery1 then begin
FieldList.Add(ADataSet.Fields[0].FieldName);
end
else
// obviously, deal with other specific tables here
end;
procedure TForm1.LogFields(ADataSet : TDataSet);
var
TL : TStringList;
i : Integer;
ValueToLog : String;
begin
TL := TStringList.Create;
try
GetFieldsToLog(ADataSet, TL);
for i := 0 to TL.Count - 1 do begin
ValueToLog := ADataSet.FieldByName(TL[i]).AsString;
// do your logging here however you want
end;
finally
TL.Free;
end;
end;
procedure TForm1.ADOQuery1AfterInsert(DataSet: TDataSet);
begin
LogFields(DataSet);
end;
Btw, one of the points of having a separate GetFieldsToLog procedure is that it helps to extend
client-side logging to changes in existing dataset records. If you generate this list
at start-up and save it somewhere, you can use it in the BeforePost event of a dataset to
pick up the current and previous values of the field (using its Value and OldValue properties),
save those in an another StringList and log them in the AfterPost event. Of course,
if you'e using a common store for these value from more than one dataset, you need to make
sure that the AfterPost of one dataset fire before the BeforePost of any other, or do the logging
entirely within the BeforePost (having to store the old and current field values between
Before- and AfterPost is messy, and it would be better to do everything in the AfterPost,
but unfortunately the OldValue is out-of-date by the time AfterPost occurs.
Be aware that getting the OldValue requires the specific dataset type to correctly implement
it. Not all types of dataset I've come across do, though, so it needs checking.
Btw #2, supposing you have a procedure like this
procedure TForm1.DoSomething(AnObject : TObject);
then you can use "if AnObject is ..." to do something like this
var
AnAdoQuery : TAdoQuery;
begin
if AnObject is TAdoQuery then begin
// First, use a cast to assign Sender to the local AnAdoQuery variable
AnAdoQuery := TAdoQuery(AnObject);
// Then, we can do whatever we like with it, e.g.
Caption := AnAdoQuery.Name;
end;
end;
Otoh, if for some reason (and I can't immediately think why we would want to but never mind)
we just want to check that what we've been passed as the AnObject parameter is a particular
object, we can omit the cast and just do
if AnObject = AdoQuery1 then
ShowMessage('Received AdoQuery1');
This equality check works, regardless of the actual class of what we've been passed
as the AnObject parameter because all other classes are descendants of AnObject's
declared class, namely TObject.
I have two identical statusbars (AdvOfficeStatusBar) on each form. That means Form1 has the same status bar as the Form2.Now,before I close the Form1 I would like all the values from the status bar to be transfered to that one on the form2. I suppose I could do it one by one like... :
procedure TForm2.FormShow(Sender: TObject);
begin
AdvOfficeStatusBar1.Panels[0].Text := Form1.AdvOfficeStatusBar1.Panels[0].Text;
AdvOfficeStatusBar1.Panels[1].Text := Form1.AdvOfficeStatusBar1.Panels[1].Text;
AdvOfficeStatusBar1.Panels[2].Text := Form1.AdvOfficeStatusBar1.Panels[2].Text;
AdvOfficeStatusBar1.Panels[4].Text := Form1.AdvOfficeStatusBar1.Panels[4].Text;
AdvOfficeStatusBar1.Panels[5].Text := Form1.AdvOfficeStatusBar1.Panels[5].Text;
AdvOfficeStatusBar1.Panels[6].Text := Form1.AdvOfficeStatusBar1.Panels[6].Text;
end;
I was wondering if there's a more simple way?Less code...
You're suffering from an anti-pattern called copy-paste-programming.
It makes for very easy programming, but difficult maintenance.
Every time you add a line to one statusbar, you have to go back and update to code to have it be linked into the other statusbar.
It's easy to forget updating the code and ehm well it's work, which is why this is bad practice.
A better way is to use Assign or if that does not work a loop. Both are demonstrated below.
Note that the Panel is an array property.
Normally every array_property has a associated count property.
I'm not sure what it is in this instance, but I'm guessing it's called PanelCount.
As per David's suggestion it's better to store the state somewhere inside your program, because you might redesign the form and lose the StatusBar, in which case you'd also lose the storage.
type
TForm2 = class(TForm)
private
StatusStore: array of string;
.....
end;
implementation
procedure TForm2.FormCreate(Sender: TObject);
begin
//Initialisation, you cannot use a loop, unless you'd read it from a file.
SetLength(StatusStore,6);
StatusStore[0]:= 'a';
StatusStore[1]:= 'b';
StatusStore[2]:= 'c';
StatusStore[3]:= 'd';
StatusStore[4]:= 'e';
StatusStore[5]:= 'f';
end;
procedure TForm2.FormShow(Sender: TObject);
var
i,maxi: integer;
begin
StatusStore[0]:= 'Showing Form2';
Maxi:= SizeOf(StatusStore);
i:= 0;
AdvOfficeStatusBar1.PanelCount:= Maxi;
while (i < Maxi) do begin
AdvOfficeStatusBar1.Panels[i].Text:= StatusStore[i];
end; {while}
Form1.AdvOfficeStatusBar1.Panels.Assign(Form2.AdvOfficeStatusBar1.Panels);
end;
Now whatever data is to be displayed and however many items there are, the display will update.
You can even program the loop to skip an item if you want the first or last item to be different for each form.
I'm using a home-grown translation tool. (Next time I'll use one of the libraries, as described here: delphi translation tool.)
My translators complain that translating a long list of strings is difficult because they're not seeing them in context (on the screen in which they appear.)
One translator made a great suggestion that he should be able to click on a component to change its text. I can implement this if I can find an way to hook program wide, an event so that when a user clicks on a component while holding down the CTRL key, an event handler is called. The event handler would determine if the component has a .Caption property, and if so, get the value of the Tag component (and then allow some user input.)
(Each translatable component has unique integer in its Tag properly, which I use to look up the component's .Caption.)
Any suggestions on how to go about this? It's over my head. I need something like a form's KeyPreview, but for mouse-clicks that would figure out what VCL component was clicked, and determine it's .Tag value.
Tom
EDIT:
Using David H.'s suggestion, the only events I get are when the app gets focus or loses it. What have I done wrong?
function TForm1.AppHookFunc(var Message : TMessage) : Boolean;
begin
Result := FALSE;
inc(i); outputdebugstring(Pchar(inttostr(i) + ': ' + IntTostr(Message.msg)));
if Message.Msg = WM_MBUTTONDOWN then
begin Beep;
//...DoSomething...
//Result := True;
end;
end;
procedure TForm1.FormCreate( Sender: TObject);
begin
Application.HookMainWindow(AppHookFunc);
end;
procedure TForm1.FormDestroy(
Sender: TObject);
begin
Application.UnHookMainWindow(AppHookFunc);
end;
EDIT 2
I'm almost there! But FindDragTarget seldom returns anything but nil. If I make an enormous button covering most of the control, I can sometimes get it to work. The X,Y coordinates in the tagMSG received are relative to the control. I would have though they'd relative to the form. Am I still using a different event hook than I should? Any suggestions:
procedure TForm1.ApplicationEvents1Message( var Msg: tagMSG;
var Handled: Boolean);
var
Target: TControl;
Point: TPoint;
begin
Handled := FALSE;
if (Msg.Message = WM_LBUTTONDOWN) And isAltDown then
begin
Point.X := LongRec(Msg.lParam).Lo;
Point.Y := LongRec(Msg.lParam).Hi;
Target := FindDragTarget( Point, {AllowDisabled=}TRUE);
if Assigned(Target) then
begin
if Target Is TButton then
outputdebugString(Pchar(TButton(Target).Caption));
end
else
outputdebugstring(Pchar(IntToStr(Point.X) + ', ' + IntToStr(Point.Y)));
end;
end;
FINAL EDIT:
I changed the code above to use GetCursorPos rather than Msg.lParam. It's working now. Very cool! SO Rocks!
THANK YOU BOTH FOR YOUR HELP!
I'm assuming this is a VCL app. For FireMonkey this would not work.
Add an Application.OnMessage event handler.
In the event handler look for WM_LBUTTONDOWN or perhaps WM_LBUTTONUP and check that the modifier key state is as you desire, e.g. CTRL is down.
Call FindDragTarget passing the position associated with the mouse event. This will give you the control under the mouse, if indeed there is one (i.e. check for nil).
Do whatever it is you want to that control.