lazarus DBGrid lookup field (no creation) - lazarus

I have been using Lazarus 2.x with Firebird 3 (via flamerobin) and i have got two tables: appeals with a foreign key (promoter_id) and promoter (id,fullname).
I would like to create a lookup field for DBGrid with field properties from dataset appeals and lookup fields from dataset promoter.
Thus i have got the following script:
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, DBGrids,Unit3,DB;
type
{ TForm5 }
TForm5 = class(TForm)
DBGrid1: TDBGrid;
procedure FormShow(Sender: TObject);
procedure showgrid;
procedure showlookup;
private
public
end;
var
Form5: TForm5;
implementation
{$R *.lfm}
{ TForm5 }
procedure TForm5.FormShow(Sender: TObject);
begin
//showlookup;
showgrid;
showlookup;
end;
procedure TForm5.showgrid;
begin
appeals.SQLQuery2.Close;
appeals.SQLQuery2.SQL.Text:='select * from appeals where datediff(day,cast(''Now'' as date),cast(date_judgement as date))<5 and date_court IS NOT NULL';
appeals.DBConnection.Connected:=True;
appeals.SQLTransaction1.Active:=True;
//appeals.SQLQuery2.Open;
//appeals.DataSource1.DataSet:=appeals.SQLQuery2;
//DBGrid1.DataSource:=appeals.DataSource1;
DBGrid1.Columns.Items[1].Width:=120;
DBGrid1.Columns.Items[0].Visible:=false;
DBGrid1.Columns.Items[1].Title.Caption:='Name';
DBGrid1.Columns.Items[2].Title.Caption:='Date Entry';
DBGrid1.Columns.Items[3].Title.Caption:='Date Suspension';
DBGrid1.Columns.Items[4].Title.Caption:='Date Examination';
DBGrid1.Columns.Items[5].Title.Caption:='Date Final';
DBGrid1.Columns.Items[6].Title.Caption:='Promoter';
end;
procedure TForm5.showlookup;
begin
appeals.SQLQuery4.Close;
appeals.SQLQuery4.SQL.Text:='select id,fullname from promoter';
//appeals.SQLQuery4.Open;
DBGrid1.Columns.Items[6].Field.FieldName:='promoter_id';
DBGrid1.Columns.Items[6].Field.FieldKind:=fkLookup;
DBGrid1.Columns.Items[6].Field.KeyFields:='promoter_id';
DBGrid1.Columns.Items[6].Field.LookupDataSet:= appeals.SQLQuery4;
DBGrid1.Columns.Items[6].Field.LookupKeyFields:='id';
DBGrid1.Columns.Items[6].Field.LookupResultField:='fullname';
DBGrid1.Columns.Items[6].Field.DataSet:=appeals.SQLQuery2;
appeals.SQLQuery4.Open;
appeals.SQLQuery2.Open;
appeals.DataSource1.DataSet:=appeals.SQLQuery2;
DBGrid1.DataSource:=appeals.DataSource1;
end;
end.
As soon as i run it, i receive the following error (attached) or in text description ElistError:ListIndex (1) out of bounds.
Additionally I have played with my code by running and opening first the queries but no lookupfield is shown in Promoter column grid (only numbers and no text description). Unfortunately there is no bibliography or code examples of creating lookup fields or computed in Lazarus IDE.
Any help would be convenient to me!
Regards

Related

Delphi - dbGo/Oracle method GetFieldNames returns no data

Delphi Rio - I am just starting to learn ADO, specifically the dbGo components, connected to a local Oracle RDBMS (Oracle 12.2 64 bit). I am able to connect, issue simple queries, etc. I found the method TADOConnection.GetFieldNames, and I am experimenting with it. I am not able to get it to work. Here is my code...
procedure TForm1.BitBtn1Click(Sender: TObject);
var
S1 : TStringList;
begin
S1 := TStringList.Create;
ADO1.Connected := True;
ADO1.GetFieldNames('EGR.ACCOUNTS', S1);
//ADO1.GetTableNames(S1, False);
ShowMessage(IntToStr(S1.Count));
S1.Free;
end;
I have tried with and without the Schema name, yet S1.Count always returns 0. The GetTableNames function works fine. If I go into SQL*Plus and query, I see the appropriate data
select count(*) from EGR.ACCOUNTS;
So I know my SCHEMA.TABLENAME is correct. What am I doing wrong?
To get hand on a data field by its name, you shall use this code:
var
FieldEgrAccount : TField;
begin
FieldEgrAccount := AdoQuery1.FieldByName('SomeFieldName');
Memo1.Lines.Add(FieldEgrAccount.AsString);
end;
If you really need to have all field names, use this code:
var
Names : TStringList;
begin
Names := TStringList.Create;
try
AdoQuery1.GetFieldNames(Names);
// Do something with the field names
finally
Names.Free;
end;
end;
It is much faster to use one TField per field, get it once and reuse it as many times as needed (Make the variable fields of the form or datamodule class). FieldByName is relatively costly because it has to scan the list of field names.
You need to assign it to the items property of the stringlist which is of type TStrings.
procedure GetFieldNames(const TableName: string; List: TStrings);

Delphi TTreeView OnCustomDrawItem event slows it down

I have an outliner application (in Delphi 10.2 Tokyo) that I use for taking notes (I'll call it NoteApp). I have another application that I use for editing plain text (TextApp). Since I switch between these applications a lot, I decided to integrate note-taking abilities within TextApp.
I copy/pasted the code from NoteApp to TextApp, and I put the components (one TTreeView, one TRichEdit, and one TActionToolbar) on TextApp.Form_Main .
OnCustomDrawItem event of the TTreeView is set to change FontStyle of each Node based on the NoteType of the corresponding note item which is an array of simple records:
type
///
/// Note types
///
TNoteType = (ntNote, ntTodo, ntDone, ntNext, ntTitle) ;
///
///
///
TNote = Record
Text ,
Attachment ,
Properties ,
CloseDate : String ;
NoteType : TNoteType ;
End;
Our array:
var
Notes: Array of TNote ;
And the event:
procedure TForm_Main.TreeView_NotesCustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
///
/// First check to see if the application allows us to change visuals. If the
/// application is in processing mode, visual updates are not allowed.
///
if ChangeAllowed AND Node.IsVisible then
begin
///
/// Check NoteType of the corresponding note:
///
case Notes[Node.AbsoluteIndex].NoteType of
ntTitle:
begin
TreeView_Notes.Canvas.Font.Style := [fsBold] ;
end;
//ntNote:
// begin
// end;
ntTodo:
begin
TreeView_Notes.Canvas.Font.Style := [fsBold] ;
end;
ntNext:
begin
TreeView_Notes.Canvas.Font.Style := [fsUnderline] ;
end;
ntDone:
begin
TreeView_Notes.Canvas.Font.Style := [fsStrikeOut] ;
end;
end;
end;
end;
When I open a note file in NoteApp, it works perfectly. When I open the same file in TextApp, TTreeView refreshes slowly. The top items in the TTreeView are alright, but the lower you go, the lower the refreshing rate.
The properties of all of the components are identically set.
I suspect I have made a mistake somewhere. I set the visibility of all of the other components on TextApp to false, but the TTreeView is still abysmally slow.
If I remove the code above, it becomes fast again. I don't use runtime themes in TextApp.
Okay, I found the answer to the question.
The answer was hidden in the code above, and what I posted was enough to answer the question. Turns out the code that I have posted was MCVE after all. I am posting the answer in case it happens to someone else.
Answer:
Turns out Node.AbsoluteIndex is incredibly slow. It shouldn't be used as an index.
Solution 1:
I used Node.Data as an index, and now it is very fast.
Solution 2:
An alternative solution that I tried and worked:
TTreeNodeNote = class(TTreeNode)
public
Note: TNote;
end;
procedure TForm_Main.TreeView_NotesCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
begin
NodeClass := TTreeNodeNote;
end;
Then we store the data in the Note property of each Node, instead of a separate array. Works like a charm.

Defining method body inside class declaration?

I'm trying to define class methods all inside of the class declaration in Free Pascal, which I haven't been able to find any examples of online. Currently I have to do it like so:
unit Characters;
{$mode objfpc}{$H+}
// Public
interface
type
TCharacter = class(TOBject)
private
FHealth, FAttack, FDefence: Integer;
procedure SetHealth(newValue: Integer);
public
constructor Create(); virtual;
procedure SayShrek();
function GetHealth(): Integer;
published
property Health: Integer read GetHealth write SetHealth;
end;
// Private
implementation
constructor TCharacter.Create;
begin
WriteLn('Ogres have LAYERS!');
end;
procedure TCharacter.SayShrek;
begin
WriteLn('Shrek!');
end;
procedure TCharacter.SetHealth(newValue: Integer);
begin
FHealth:= FHealth + newValue;
end;
function TCharacter.GetHealth() : Integer;
begin
GetHealth:= FHealth;
end;
end.
Is there any possible way to make this a little cleaner? Defining everything elsewhere looks messy and is unorganized.
EDIT:
To clarify, I'd like to do something along the lines of this:
TMyClass = class(TObject)
public
procedure SayHi();
begin
WriteLn('Hello!');
end;
end;
Instead of having to define it further down. Is that possible?
That is not possible in Pascal. It is just not allowed by its grammar.
It is a fundamental design in Pascal that units are divided in interface (What can be done?) and implementation (How is something done?).
The compiler reads all interface sections before parsing the implementation parts. You might know this from C language. implementation could be described as *.c-files, whereas interface is equivalent to *.h-files in C.
Furthermore such code would heavily decrease readability of interface sections (f.i. class declaratons).
What benefits do you hope to get with that?
No, you can not do this. Pascal has a single-pass compiler from the outset was designed for the single-pass compilation so you can not use something before it will be declared.
As a simple example in pseudocode:
MyClass = class
procedure MethodA;
begin
MethodB; <== At this point the compiler knows nothing about MethodB
end;
procedure MethodB;
begin
end;
end;
It is why each unit have at least two sections: interface (declarations, you can think about it as about C++ header files) and implementation.
However there are some tricks in the language syntax for implementing cyclic declarations where you can use forward declarations.
For the pointers:
PMyRec = ^TMyRec; // Here is TMyRec is not declared yet but compiler can to handle this
TMyRec = record
NextItem: PMyRec;
end;
For the classes:
MyClassA = class; // Forward declaration, class will be fully declared later
MyClassB = class
SomeField: MyClassA;
end;
MyClassA = class
AnotherField: MyClassB;
end;
In the IDE you can use Shift+Ctrl+Up/Down keys to navigate between declaration and implementation of the item.

Inheritance in FreePascal Lazarus

I'm learning free pascal using the Lazarus IDE and I don't know how to inheritance methods in the derived form.
I want something like this:
Form base or father:
procedure HelloWorld;
begin
ShowMessage('Hello World from base form or father');
end;
and form derived or child:
procedure HelloWorld;
begin
inherited;
ShowMessage('Hello World from derived form or child');
end;
I want the result shows 2 messages by clicking (e.g Button1)
Thanks!!!
For a better appreciation of the Object Pascal language, i believe you should start by reading the freepascal reference guide. FreePascal is the underlaying compiler below lazarus.
Its important to understand that Forms, Labels, Buttons, etc, are specific incarnations of the concepts of objects, instances, classes etc.
In that regard, a class is a structure binding code and data. What you want to achieve is something like this :
Type
TMyClass = Class(<ancestorclass>)
<fields and methods>
End;
TMyChildClass = Class(TMyClass)
<fields and methods>
End;
This means that TMyChildClass is a class derived from TMyClass. In the event that you have methods in both classes with same name, you can use the keyword "override" to show the compiler that that method was overriden by the child class, like this :
TMyClass = Class /* No parenthesis or ancestor name means the class derives from TObject */
Procedure ParentMethod;
End;
TMyChildClass = Class(TMyClass)
Procedure ParentMethod; Override;
End;
Procedure TMyClass.ParentMethod;
Begin
DoSomething;
End;
Procedure TMyChildClass.ParentMethod; /* Dont repeat the override */
Begin
Inherited; // This will call the parents method
End;
This is the proper way to do method override in object pascal. If the definition of the class where you want to use "inherited" has no parenthesis and the name of the ancestor class, theres no ancestry relation between then and inherited will not do what you are expecting to do.
In Pascal procedure is not an object oriented programming construct.
FreePascal includes objects and objects can include procedures:

How can I call an Oracle function from Delphi?

I created a function in Oracle that inserts records in specific tables and return an output according to what occurs within the function. e.g (ins_rec return number)
How do I call this function and see its output in Delphi?
I got a reply (with all my thanks) for sql plus but I need to know how can I do this in Delphi
Just pass the user defined function as column name in the query and it will work.
Example:
Var
RetValue: Integer;
begin
Query1.Clear;
Query1.Sql.Text := 'Select MyFunction(Param1) FunRetValue from dual';
Query1.Open;
if not Query1.Eof then
begin
RetValue := Query1.FieldByName('FunRetValue').AsInteger;
end;
end;
How to accomplish it may depend on what DB access library you use (BDE? dbExpress? ADO? others), some may offer a "stored procedure" component that may work with functions as well.
A general approach it to use an anonymous PL/SQL block to call the function (and a parameter to read the return value), PL/SQL resembles Pascal a lot...:
Qry.SQL.Clear;
Qry.SQL.Add('BEGIN');
Qry.SQL.Add(' :Rez := ins_rec;');
Qry.SQL.Add('END;');
// Set the parameter type here...
...
Qry.ExecSQL;
...
ReturnValue := Qry.ParamByName('Rez').Value;
I would not have used a function, though, but a stored procedure with an OUT value. Moreover, Oracle offers packages that are a very nice way to organize procedure and functions, and they also offer useful features like session variables and initialization/finalization sections... very much alike a Delphi unit.
we run an Oracle stored procedure using this code that utilizes the BDE (I know please don't bash because we used the BDE!)
Try
DMod.Database1.Connected:= False;
DMod.Database1.Connected:= True;
with DMod.StoredProc1 do
begin
Active:= False;
ParamByName('V_CASE_IN').AsString:= Trim(strCaseNum);
ParamByName('V_WARRANT_IN').AsString:= strWarrantNum;
ParamByName('V_METRO_COMMENTS').AsString:= strComment;
Prepare;
ExecProc;
Result:= ParamByName('Result').AsString;
end;
Except

Resources