Passing inherited frames as argument to a procedure - function

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.

Related

DBGrid - OnCellClick & OnDblClick, return a form/TBMemo containing cell/column value/s

I'm using the following code added to my DBGrid - OnCellClick event
procedure TForm2.DBGrid1CellClick(Column: TColumn);
begin
if dbmodule.comenziDataSet.Active then
begin
if not Assigned(dbgridCelulaForm) then
begin
dbgridCelulaForm := TdbgridCelulaForm.Create(Self);
dbgridCelulaForm.DBMemoCelula.DataSource := dbmodule.comenziSource;
end;
dbgridCelulaForm.Visible := False;
dbgridCelulaForm.Visible := True;
dbgridCelulaForm.DBMemoCelula.DataField := Column.FieldName;
dbgridCelulaForm.Caption := Format('%s / randul: %d',[Column.FieldName, DBGrid1.DataSource.DataSet.RecNo]);
end;
end;
dbgridCelulaForm = name of the form containing the TDBMemo
DBMemoCelula = name of the TDBMemo
dbmodule.comenziDataSet = comenziDataSet is the name of the DataSet and dbmodule is the name of a data module (unit, like forms) - the DataSet is on the data module, so, dbmodule.comenziDataSet
dbmodule.comenziSource = same as data set, a DataSource on a data module, the source is named comenziSource
Ok so what this code does:
Once I click a cell on my DBGrid it pops up a form (named dbgridCelulaForm) which contains a TBMemo (named DBMemoCelula) and it shows me the information contained in that cell (like, a Customer Name for example, or whatever the cell is holding, in my db)
This is fine, my problem is I can't select rows now in DBGrid, well, I can but once I do the 1st place I click (a cell, any) on the particular row I want to select with my mouse, then cell activates and the form pops up.
Is it possible to use this code in DBGrid - OnDblClick event instead of the OnCellClick ?
Meaning once i double click a row / cell the form should pop up and show me the info, but double click - not single click.
That way, I can still select the row and still view the info in the cell if I need to.
Or any other way/place to use/receive this functionality.
Any thoughts?
I can post a quick video of everything if my explanation is ambiguous and you think that would help, just tell me in the comment / answer.
Also, I'm using RAD Studio 10 Seattle and dbexpress components for the database - if that helps.
Thanks!
The following code shows how to access the Column and Row coordinates of a dbl-clicked cell of a TDBGrid, and the string value of the cell contents.
As written, it displays the cell's Column and Row number + string contents on the form's caption. Up to you what you actually do with these values.
It work because the dataset cursor on the dataset connected to the DBGrid is moved to the dataset row corresponding to the cell where the mouse pointer is.
type
TMyDBGrid = class(TDBGrid);
procedure TForm1.DBGrid1DblClick(Sender: TObject);
var
ARow,
ACol : Integer;
Pt : TPoint;
CellValue : String;
begin
// First, get the mouse pointer coordinates
Pt.X := Mouse.CursorPos.X;
Pt.Y := Mouse.CursorPos.Y;
// Translate them into the coordinate system of the DBGrid
Pt := DBGrid1.ScreenToClient(Pt);
// Use TDBGrids inbuilt functionality to identify the Column and
// row number.
ACol := DBGrid1.MouseCoord(Pt.X, Pt.Y).X -1;
ARow := DBGrid1.MouseCoord(Pt.X, Pt.Y).Y;
CellValue := DBGrid1.Columns[ACol].Field.AsString;
Caption := Format('Col:%d Row:%d Cell Value:%s', [ACol, ARow, CellValue]);
end;
Note that I've used the Caption property of the form to display the grid cell info just as a quick n dirty way of showing the information somewhere. Of course you could equally well display it on another area of the form or somewhere on a different form entirely. The above code will work equally well in the grid's OnCellClick event, btw.
As noted in a comment, you can use the grid's SelectedField property instead of the above, but personally I think the above is more instructive of how to work with a DBGrid, because it shows how to get the cell's Column and Row coordinates. See the DBGrid's SelectedField, SelectedIndex and SelectedRows properties in the Online Help for more info on useful properties of the TDBGrid.
Update You asked in a comment for an example of showing the information on another form. Let's suppose this form is called OtherForm, is in a unit OtherFormu.Pas and is created before the DBGrid1DblClick evenbt is called. You need to use this unit in the Useslist of the unit which contains the DBGrid. Let's suppose this other form contains a TMemo control called Memo1. Then, you could write your DBGrid1DblClick hanndler like this:
procedure TForm1.DBGrid1DblClick(Sender: TObject);
[as above]
begin
[ as above ]
CellValue := DBGrid1.Columns[ACol].Field.AsString;
OtherForm.Memo1.Lines.Add(Format('Col:%d Row:%d Cell Value:%s', [ACol, ARow, CellValue]));
end;

How to call procedure with multiple parameters? [closed]

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.

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;
...

Specify whever a lua parameter should be a copy or a reference

I'm wondering if there is a way to specify if the parameters of a lua function should be copied or just referenced. Color is an object representing a color.
For example, with this code
function editColor(col)
col.r = 0
print(tostring(col.r))
end
color = Color(255, 0, 0)
print(tostring(color.r))
editColor(color)
print(tostring(color.r))
Makes the output
255
0
0
So col is a "reference" to color, but this code:
function editColor(col)
col = Color(0, 0, 0)
print(tostring(col.r))
end
color = Color(255, 0, 0)
print(tostring(color.r))
editColor(color)
print(tostring(color.r))
Makes this output
255
0
255
So here the color is copied.
Is there a way to force a parameter to be copied or referenced? Just like the & operator in C++?
No, parameters in Lua are always passed by value (mirror). All variables are references however. In your second example in editColor you're overriding what the variable col refers to, but it's only for that scope. You'll need to change things around, maybe instead of passing in a variable to be reassigned, have the function return something and do your reassignment outside. Good luck.
This will do what you want. Put the variable that you want to pass by reference into a table. You can use a table to pass anything by ref, not just a string.
-- function that you want to pass the string
-- to byref.
local function next_level( w )
w.xml = w.xml .. '<next\>'
end
-- Some top level function that you want to use to accumulate text
function top_level()
local w = {xml = '<top>'} -- This creates a table with one entry called "xml".
-- You can call the entry whatever you'd like, just be
-- consistant in the other functions.
next_level(w)
w.xml = w.xml .. '</top>'
return w.xml
end
--output: <top><next\></top>
Lua is a bad language. Suffer its simplicity when you need to do something just a little complex.

Passing variables into a function in Lua

I'm new to Lua, so (naturally) I got stuck at the first thing I tried to program. I'm working with an example script provided with the Corona Developer package. Here's a simplified version of the function (irrelevant material removed) I'm trying to call:
function new( imageSet, slideBackground, top, bottom )
function g:jumpToImage(num)
print(num)
local i = 0
print("jumpToImage")
print("#images", #images)
for i = 1, #images do
if i < num then
images[i].x = -screenW*.5;
elseif i > num then
images[i].x = screenW*1.5 + pad
else
images[i].x = screenW*.5 - pad
end
end
imgNum = num
initImage(imgNum)
end
end
If I try to call that function like this:
local test = slideView.new( myImages )
test.jumpToImage(2)
I get this error:
attempt to compare number with nil
at line 225. It would seem that "num" is not getting passed into the function. Why is this?
Where are you declaring g? You're adding a method to g, which doesn't exist (as a local). Then you're never returning g either. But most likely those were just copying errors or something. The real error is probably the notation that you're using to call test:jumpToImage.
You declare g:jumpToImage(num). That colon there means that the first argument should be treated as self. So really, your function is g.jumpToImage(self, num)
Later, you call it as test.jumpToImage(2). That makes the actual arguments of self be 2 and num be nil. What you want to do is test:jumpToImage(2). The colon there makes the expression expand to test.jumpToImage(test, 2)
Take a look at this page for an explanation of Lua's : syntax.