How to call procedure with multiple parameters? [closed] - function

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I have this code:
edit5.Text := IntToStr(j);
rw := j;
myRect.Left := 0;
myRect.Top := rw;
myRect.Right := 5;
myRect.Bottom := rw;
stringGrid1.Selection := myRect;
edit1.SetFocus;
I must rewrite this code because I'm using it for many events (event button1click, button2click, everytime I validate)
so I'm meaning to make then into procedure so I can call it in many event
this code so far I made:
procedure highlight(edit1, edit5: TEdit; myrect: TGridRect;
stringgrid1: TStringgrid; var j, rw: Integer);
begin
edit5.Text := IntToStr(j);
rw := j;
myRect.Left := 0;
myRect.Top := rw;
myRect.Right := 5;
myRect.Bottom := rw;
stringGrid1.Selection := myRect;
edit1.SetFocus;
end;
but I can't call it:
procedure Tform1.Button2Click(Sender: TObject);
begin
highlight;
end;
how to resolve it ? did I must split it ?

Your extracted procedure is not quite right. You pass a rect that you don't use. You pass rw and j as var parameters, but it looks like a single by value parameter really. So I'd have it like this:
procedure Highlight(Edit1, Edit5: TEdit; StringGrid: TStringGrid; rw: Integer);
begin
Edit5.Text := IntToStr(rw);
StringGrid.Selection := Rect(0, rw, 5, rw);
Edit1.SetFocus;
end;
Call it like this:
Highlight(Edit1, Edit5, StringGrid1, j);
Now, this assumes that you sometimes need to pass different controls to the procedure. If you always pass the same controls, then make the procedure be a method of your class:
procedure TMyForm.Highlight(rw: Integer);
begin
Edit5.Text := IntToStr(rw);
StringGrid.Selection := Rect(0, rw, 5, rw);
Edit1.SetFocus;
end;
And call it like this:
Highlight(j);
I'm assuming here that the value you pass as j can vary. So it should be a parameter. That's the sort of reasoning that you need to use when deciding whether something should be a parameter, or use a field. Ask yourself if you will always pass the same value when calling the method?
Finally, you are making life hard by not naming your variables. How can a reader of the code know what is so special about Edit5 and why it is treated differently from Edit1. Give names to your variables.

Related

Passing inherited frames as argument to a procedure

I have a TPageControl with N amount of TTabSheets in my main form which I use to embed several TFrame descendants.
For the frames I created a "TBaseFrame" from which I derive the individual frames which I want to display in the TabSheets, more or less looks like this...
TBaseFrame = class(TFrame)
TBaseFrameDescendant1 = class(TBaseFrame)
TBaseFrameDescendant2 = class(TBaseFrame)
TBaseFrameDescendantN = class(TBaseFrame)
What im struggleing with is this: I want to create a procedure that takes any of my TBaseFrameDescendants as an argument, creates the given frame and displays it in a new tab sheet. I started with something like this...
procedure CreateNewTabSheetAndFrame( What do I put here to accept any of my TBaseFrameDescendants? )
var
TabSheet: TTabSheet;
begin
TabSheet := TTabSheet.Create(MainPageControl);
TabSheet.Caption := 'abc';
TabSheet.PageControl := MainPageControl;
// Here I want to create the given TBaseFrameDescendant, set the Parent to the above TabSheet and so on
end;
Guess my main question here is how to set up my procedure so I can pass in any frame which is derived from my TBaseFrame so I can work with it within the procedure, or am I heading in the wrong direction here?
You need to use what is known as a metaclass.
type
TBaseFrameClass = class of TBaseFrame;
procedure TMainForm.CreateNewTabSheetAndFrame(FrameClass: TBaseFrameClass)
var
TabSheet: TTabSheet;
Frame: TBaseFrame;
begin
TabSheet := TTabSheet.Create(Self);
TabSheet.PageControl := MainPageControl;
Frame := FrameClass.Create(Self);
Frame.Parent := TabSheet;
end;
Make sure that if you declare any constructors in any of your frame classes, that they derive from the virtual constructor introduced in TComponent. That is necessary in order for the instantiation via metaclass to invoke the appropriate derived constructor.

Is there a delphi ScrollInView equivalent in Free Pascal?

I have a ScrollBox and I'm adding controls to it at runtime. However, when the controls exceed the ScrollBox height, I want the ScrollBox to scroll all the way to the bottom so that the newly added controls are visible.
Doing some research, I've found something called "ScrollInView" for delphi. Seeing how many (quite a lot) of Delphi methods/functions are available in Free Pascal, do you know of any equivalent to this particular one? If not, can you help me achieve my goal (Auto-scrolling the ScrollBox to the bottom) with a different solution?
Thanks in advance,
Oscar
Something like this?
procedure TForm1.Button1Click(Sender: TObject);
begin
with TEdit.Create(Self) do
begin
Parent := ScrollBox1;
Left := 10;
Top := ScrollBox1.ControlCount * 40;
ScrollBox1.VertScrollBar.Position := Top;
end;
end;
And here is hte simple implementation of the ScrollInView method:
TScrollBoxHelper = class helper for TScrollBox
procedure ScrollInView(AControl: TControl);
end;
implementation
procedure TScrollBoxHelper.ScrollInView(AControl: TControl);
begin
if AControl.Parent = Self then
begin
Self.VertScrollBar.Position := AControl.Top;
Self.HorzScrollBar.Position := AControl.Left;
end;
end;
Usage:
procedure TForm1.Button2Click(Sender: TObject);
begin
ScrollBox1.ScrollInView(ScrollBox1.Controls[3]);
end;

FPC : RTTI on records

This is my first time on this site. Usually, I have no problem to found replies in the old posts but I don't success with my actual problem.
I would like to know how use RTTI functions to know at running time the properties/members of a record under Lazarus/FPC? I know how to do it for a class (Tpersistent descendant and published properties) but not for FPC. Some links indicates how to do it under Delphi (From D2010), but I don't know how to transpose it under Lazarus.
Thanks in advance for help and assistance.
Salim Larhrib.
To kevin : As I told before, this is my first demand. But I understand. You are right. This is my code
procedure TMainForm.btRecordTHashListClick(Sender: TObject);
var
pTData : PTypeData;
pTInfo : PTypeInfo;
TablePtr : PatableRecord;
Loop : Integer;
begin
// Set of Record pointers + HashList
// Create Container
if not Assigned(FTableRecList) then FTableRecList := TFPHashList.Create;
// Insert data
new(TablePtr);
TablePtr^.description := 'Dictionnaire des tables.';
FTableRecList.add('atable', TablePtr );
new(TablePtr);
TablePtr^.description := 'Dictionnaire des fonctions.';
FTableRecList.add('afunction', TablePtr );
new(TablePtr);
TablePtr^.description := 'Dictionnaire des listes d''option.';
FTableRecList.add('alist', TablePtr );
// Read records
for Loop:=0 to FTableRecList.Count-1 do
begin
TablePtr := FTableRecList[Loop];
ShowMessage('Parcours Index : ' + TablePtr^.description);
end;
// Find records
try
TablePtr := FTableRecList.Find('ddafunction');
ShowMessage('Record finded : ' + TablePtr^.description);
except
ShowMessage('Not such record .');
end;
try
TablePtr := FTableRecList.Find('afunction');
ShowMessage('Record finded : ' + TablePtr^.description);
except
ShowMessage('No such record.');
end;
// Free memory : To put later in TFPHashList wrapper
for Loop:=0 to FTableRecList.Count-1 do Dispose(PatableRecord(FTableRecList[Loop]));
// RTTI
pTInfo := TypeInfo(TatableRecord);
pTData := GetTypeData(pTInfo);
ShowMessage('Member count = '+IntToStr(pTData^.PropCount));
end;
WARNING: It works with FPC 2.7.1 or later.
You can deal with record fields using pointers. Here is example:
program rttitest;
uses
TypInfo;
type
TMyRec = record
p1: Integer;
p2: string;
end;
var
td: PTypeData;
ti: PTypeInfo;
mf: PManagedField;
p: Pointer;
f: Pointer;
r: TMyRec;
begin
r.p1 := 312;
r.p2 := 'foo-bar';
ti := TypeInfo(r);
td := GetTypeData(ti);
Writeln(td^.ManagedFldCount); // Get count of record fields
// After ManagedFldCount TTypeData contains list of the TManagedField records
// So ...
p := #(td^.ManagedFldCount); // Point to the ManagedFldCount ...
// Inc(p, SizeOf(Integer)); // Skip it (Wrong for 64-bit targets)
// Next line works for both
Inc(p, SizeOf(td^.ManagedFldCount)); // Skip it
mf := p; // And now in the mf we have data about first record's field
Writeln(mf^.TypeRef^.Name);
Write(r.p1); // Current value
f := #r;
Inc(f, mf^.FldOffset); // Point to the first field
Integer(f^) := 645; // Set field value
Writeln(r.p1); // New value
// Repeat for the second field
Inc(p, SizeOf(TManagedField));
mf := p;
Writeln(mf^.TypeRef^.Name);
Write(r.p2);
f := #r;
Inc(f, mf^.FldOffset);
string(f^) := 'abrakadabra';
Writeln(r.p2);
Readln;
end.

Replace chars in a HTML string - Except Tags

I need to go through a HTML string and replace characters with 0 (zero), except tags, spaces and line breaks. I created this code bellow, but it is too slow. Please, can someone help me to make it faster (optimize)?
procedure TForm1.btn1Click(Sender: TObject);
var
Txt: String;
Idx: Integer;
Tag: Boolean;
begin
Tag := False;
Txt := mem1.Text;
For Idx := 0 to Length(Txt) - 1 Do
Begin
If (Txt[Idx] = '<') Then
Tag := True Else
If (Txt[Idx] = '>') Then
Begin
Tag := False;
Continue;
end;
If Tag Then Continue;
If (not (Txt[Idx] in [#10, #13, #32])) Then
Txt[Idx] := '0';
end;
mem2.Text := Txt;
end;
The HTML text will never have "<" or ">" outside tags (in the middle of text), so I do not need to worry about this.
Thank you!
That looks pretty straightforward. It's hard to be sure without profiling the code against the data you're using, (which is always a good idea; if you need to optimize Delphi code, try running it through Sampling Profiler first to get an idea where you're actually spending all your time,) but if I had to make an educated guess, I'd guess that your bottleneck is in this line:
Txt[Idx] := '0';
As part of the compiler's guarantee of safe copy-on-write semantics for the string type, every write to an individual element (character) of a string involves a hidden call to the UniqueString routine. This makes sure that you're not changing a string that something else, somewhere else, holds a reference to.
In this particular case, that's not necessary, because you got the string fresh in the start of this routine and you know it's unique. There's a way around it, if you're careful.
CLEAR AND UNAMBIGUOUS WARNING: Do not do what I'm about to explain without making sure you have a unique string first! The easiest way to accomplish this is to call UniqueString manually. Also, do not do anything during the loop that could assign this string to any other variable. While we're doing this, it's not being treated as a normal string. Failure to heed this warning can cause data corruption.
OK, now that that's been explained, you can use a pointer to access the characters of the string directly, and get around the compiler's safeguards, like so:
procedure TForm1.btn1Click(Sender: TObject);
var
Txt: String;
Idx: Integer;
Tag: Boolean;
current: PChar; //pointer to a character
begin
Tag := False;
Txt := mem1.Text;
UniqueString(txt); //very important
if length(txt) = 0 then
Exit; //If you don't check this, the next line will raise an AV on a blank string
current := #txt[1];
dec(current); //you need to start before element 1, but the compiler won't let you
//assign to element 0
For Idx := 0 to Length(Txt) - 1 Do
Begin
inc(current); //put this at the top of the loop, to handle Continue cases correctly
If (current^ = '<') Then
Tag := True Else
If (current^ = '>') Then
Begin
Tag := False;
Continue;
end;
If Tag Then Continue;
If (not (current^ in [#10, #13, #32])) Then
current^ := '0';
end;
mem2.Text := Txt;
end;
This changes the metaphor. Instead of indexing into the string as an array, we're treating it like a tape, with the pointer as the head, moving forward one character at a time, scanning from beginning to end, and changing the character under it when appropriate. No redundant calls to UniqueString, and no repeatedly calculating offsets, which means this can be a lot faster.
Be very careful when using pointers like this. The compiler's safety checks are there for a good reason, and using pointers steps outside of them. But sometimes, they can really help speed things up in your code. And again, profile before trying anything like this. Make sure that you know what's slowing things down, instead of just thinking you know. If it turns out to be something else that's running slow, don't do this; find a solution to the real problem instead.
Edit: Looks like I was wrong - UniqueString is not the problem. The actual bottleneck seems to be accessing the string by character. Given that my entire answer was irrelevent, I've completely replaced it.
If you use a PChar to avoid recalculating the string offset, while still updating the string via Txt[Idx], the method is much faster (5 seconds down to 0.5 seconds in my test of 1000 runs).
Here's my version:
procedure TForm1.btn1Click(Sender: TObject);
var
Idx: Integer;
Tag: Boolean;
p : PChar;
Txt : string;
begin
Tag := False;
Txt := Mem1.Text;
p := PChar(txt);
Dec(p);
For Idx := 0 to Length(Txt) - 1 Do
Begin
Inc(p);
If (not Tag and (p^ = '<')) Then begin
Tag := True;
Continue;
end
Else If (Tag and (p^ = '>')) Then
Begin
Tag := False;
Continue;
end;
If Tag Then Continue;
If (not (p^ in [#10, #13, #32])) Then begin
Txt[Idx] := '0';
end;
end;
mem2.Text := Txt;
end;
I did some profiling and came up with this solution.
A test for > #32 instead of [#10,#13,#32] gains some speed (thanks #DavidHeffernan).
A better logic in the loop also gives a bit extra speed.
Accessing the string exclusively with the help of a PChar is more effective.
procedure TransformHTML( var Txt : String);
var
IterCnt : Integer;
PTxt : PChar;
tag : Boolean;
begin
PTxt := PChar(Txt);
Dec(PTxt);
tag := false;
for IterCnt := 0 to Length(Txt)-1 do
begin
Inc(PTxt);
if (PTxt^ = '<') then
tag := true
else
if (PTxt^ = '>') then
tag := false
else
if (not tag) and (PTxt^ > #32) then
PTxt^ := '0';
end;
end;
This solution is about 30% more effective than Mason's solution and 2.5 times more effective than Blorgbeard's.

Calling TEdit objects based on DB query

I have a form with 7 TEdit having name EditPhone1, EditPhone2 and so on.
In the same form I query a DB to get data to fill those TEdits. Of course I cannot know in advance how many results the query will return.
How can I call the various TEdit objects when looping on the rowcount of the query?
Use FindComponent to "convert" a component name to the component itself:
var
Edit: TEdit;
I: Integer;
begin
DataSet.First;
I := 1;
while not DataSet.Eof do
begin
Edit := TEdit(FindComponent(Format('EditPhone%d', [I])));
if Edit <> nil then
Edit.Text := DataSet.FieldValues['PhoneNo'];
DataSet.Next;
Inc(I);
end;
Now, this requires to hard-code the EditPhone%d string into the source which results in all kinds of maintainability issues. For example: consider renaming the edits.
Alternative 1:
To not rely on the component names, you could instead make use of TLama's idea and add all the edits to a list:
uses
... , Generics.Collections;
type
TForm1 = class(TForm)
EditPhone1: TEdit;
...
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FEdits: TList<TEdit>;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FEdits := TList<TEdit>.Create;
FEdits.AddRange([EditPhone1, EditPhone2, EditPhone3, EditPhone4, EditPhone5,
EditPhone6, EditPhone7]);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FEdits.Free;
end;
procedure TForm1.ADOQuery1AfterOpen(DataSet: TDataSet);
var
I: Integer;
begin
DataSet.First;
I := 0;
while (not DataSet.Eof) and (I < FEdits.Count) do
begin
FEdits[I].Text := DataSet.FieldValues['PhoneNo'];
DataSet.Next;
Inc(I);
end;
end;
This still requires some maintenance in case of adding edits in future.
Alternative 2:
You could also loop over all edits in the form to find the ones tagged to be added to the list, instead of adding them each explicitly:
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
FEdits := TList<TEdit>.Create;
for I := 0 to ComponentCount - 1 do
if (Components[I] is TEdit) and (TEdit(Components[I]).Tag = 1) then
FEdits.Add(TEdit(Components[I]));
end;
But keeping those tags up to date is another burden.
Alternative 3:
I suggest you use a TDBGrid which is a data-component. Opening the linked dataset will automatically add all phone numbers to the grid. With some settings, the grid may kind of look like a couple of edits below each other.
You can, for example, use Tag property, to find needed component. Set all you TEdit's tag from 1 to 7 (or more), and find component by:
Var I: Integer;
MyEdit : TEdit;
For I = 0 To Self.ComponentCount - 1 Do
if (Self.Components[I] IS TEdit) AND (Self.Components[I] AS TEdit).Tag = YourTag
MyEdit = (Self.Components[I] AS TEdit);
You can also dynamically create so many TEdits, you need, and assign Tag property on creation, and find it this code later in runtime.
I'd suggest using DBCtrlGrid. You place your controls for one row on it, and it repeats the controls for as many rows as your data set has.
Get query result (usually using .RowCount property of TDataset return)
After getting the number of row, do iteration to make TEdit and set the text property
Here is sample of code:
...
For i:=0 to RowCount do
Begin
A:=TEdit.Create(self);
A.Parent:=AForm;
A.Top:=i*14;
A.Text:=ADataset.Field(i).AsString;
End;
...