IS there reflection in Oracle Forms 6 or later? - oracle

IS there reflection in Oracle Forms 6 or later?
Is it possible to enumarate labels or other elements?

Forms is an old and venerable programming language, and it doesn't support full-on reflection, Java style. However, it does have a complement of GET and SET functions which enable us to interrogate and manipulate a Form's metadata.
So we can step through the items of a block and get their labels using GET_ITEM_PROPERTY like this (example adapted from the documentation):
DECLARE
cur_itm VARCHAR2(80);
cur_block VARCHAR2(80) := :System.Cursor_Block;
cur_label VARCHAR2(120);
BEGIN
cur_itm := Get_Block_Property( cur_block, FIRST_ITEM );
WHILE ( cur_itm IS NOT NULL ) LOOP
cur_itm := cur_block||’.’||cur_itm;
cur_label := Get_Item_Property( cur_itm, LABEL);
-- do whatever you want with the label here
cur_itm := Get_Item_Property( cur_itm, NEXTITEM );
END LOOP;
END;
You could change the LABEL of the current item using SET_ITEM_PROPERTY.
Note: LABEL is a property which only applies to certain items (buttons, checkboxes, etc) so you might what to include a test for the item type and perhaps grab the PROMPT_TEXT instead, if that's appropriate.
There are loads of ways we can change the appearance and behaviour of a Form on the fly. The Form Builder Reference covers all the built-ins, so there's no point in recapitulating it here. Find out more.

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.

TEdit property variable delphi

I have a form in Delphi including several TEdit boxes. I would like a way to check the validation all of the boxes kind of, currently I would have to have a boolean variable assigned to each TEdit. e.g.
Var
TEdit1Valid:boolean;
TEdit2Valid:boolean;
etc, etc for every TEdit on the form.
For the first method it would have to look something like this:
If TEdit1Valid then
ShowMessage('Your', Edit1.Name, 'is invalid');
etc, etc for each TEdit box.
This creates many variables and I was just wondering if there is a better way to do it. One idea I had is to assign a property called Valid to a TEdit, but I don't know how to do this. With this method I could then write an array of TEdits for each TEdit and execute the following
for I := 1 to High(EditList) do
begin
if EditList[I].Valid = false then
ShowMessage('Your ', EditList[I].Name, 'is invalid');
end
This is just an example bit of rough code that I just wrote and could use along with other things I could do iteratively like this.
So could someone either suggest a good method of doing this or show me how to do the afore mentioned method.
How about using the OnExit event handler on the TEdit controls? For example:
procedure TForm1.Edit1Exit(Sender: TObject);
begin
with Sender as TEdit do
begin
if not IsValidEdit(Text) then
ShowMessage('Your ' + Name + ' is invalid');
end;
end;
Then apply that same OnExit event to all your TEdit controls. This example "IsValidEdit" function that I used above can validate the Text values of your TEdit, or you could just put your validation code within the OnExit event (but it would be "cleaner", in my opinion, to use a specific function to validate).
function IsValidEdit(const AValue: string): boolean;
begin
if Length(AValue) < 5 then // not valid if length less than 5
Result := False
else
Result := True;
end;
You can use the Tag property. It an integer rather than a boolean. According to the Embarcadero help -
Tag has no predefined meaning. The Tag property is provided for the
convenience of developers. It can be used for storing an additional
integer value or it can be typecast to any 32-bit value such as a
component reference or a pointer.
OnExit is good for individual field validations, and is useful when you don't want the user to leave the field empty or with an improper value. It's not advisable for overall form validation, although it works.
For form validation, you're better off creating a method (eg., Validate) that's called in the OnClose handler, and if it fails, show a message, position the cursor on the field in question, then set Action := caNone, which will prevent the form from closing.

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

Component disabling and enabling at runtime in Delphi 2K9. Weird problem

Here is code:
procedure DisableContrlOL(const cArray : array of string; ReEnable : boolean = False);
// can be called from VKP / RAW / Generation clicks
var
AComponent: TComponent;
CompListDis, CompListEna : TStringList;
begin
CompListDis := TStringList.Create;
CompListEna := TStringList.Create;
for i := Low(cArray) to High(cArray) do begin
AComponent := FindComponent(cArray[i]);
if Assigned(AComponent) then
if (AComponent is TControl) then begin
if TControl(AComponent).Enabled then
CompListEna.Add(TControl(AComponent).Name)
else
CompListDis.Add(TControl(AComponent).Name);
ShowMessage(TControl(AComponent).Name);
if ReEnable then begin // if reenabling needed, then all whi
if not TControl(AComponent).Enabled then
TControl(AComponent).Enabled := True;
end else if (TControl(AComponent).Enabled) then
TControl(AComponent).Enabled := False;
end;
end;
end;
I think no more explanations are needed.
The ShowMessage correctly shows name of each component, but nothing is added in StringLists. Why?
UPDATE: As question has gone pretty wild, I did confirm answer, which a bit helped me.
I understand that I did write things pretty unclear, but I am very limited, because these code lines is part of commercial project, and my hobby and heart thing. The main problem was found already 6h ago, but Rob just wanted to extend this whole question :D No, no offense, mate, it's OK. I am happy to receive so willing and helpful posts. Thanks again.
How do you know that nothing is added to the lists? You create them in this code and the only references to them are in local variables. The objects are leaked when this function returns, so you never actually use the lists anywhere.
You've said you have code for "modular testing." Since that code isn't here, I must assume the code is not part of this function. But if you have external code that's supposed to check the contents of the lists, then the lists can't be just local variables. No other code can access them. You need to either return those lists or accept lists from outside that you then fill. Here's an example of the latter:
procedure DisableContrlOL(const cArray: array of string;
Reenable: Boolean
CompListDis, CompListEna: TStrings);
// can be called from VKP / RAW / Generation clicks
var
AComponent: TComponent;
AControl: TControl;
i: Integer;
begin
for i := Low(cArray) to High(cArray) do begin
AComponent := FindComponent(cArray[i]);
if not Assigned(AComponent) or not (AComponent is TControl) then
continue;
AControl := TControl(AComponent);
if AControl.Enabled then
CompListEna.Add(AControl.Name)
else
CompListDis.Add(AControl.Name);
ShowMessage(AControl.Name);
AControl.Enabled := Reenable;
end;
end;
The caller of this function will need to provide a TStrings descendant for each list. They could be TStringList, or they could be other descendants, such as TMemo.Lines, so you can directly observe their contents in your program. (They can't be just TStrings, though, since that's an abstract class.)
As you can see, I made some other changes to your code. All your code using the Reenable parameter can be simplified to a single statement. That's because enabling a control that's already enabled, and disabling a control that's already disabled, are no-ops.
Also, Name is a public property of TComponent. You don't need to type-cast to TControl before reading that property, but since you're type-casting so often elsewhere, it made sense to introduce a new variable to hold the type-casted TControl value, and that can make your code easier to read. Easier-to-read code is easier-to-understand code, and that makes it easier to debug.
Emphasizing that this is largely based on Rob's excellent suggestions, it looks as though you could simplify the code to:
procedure DisableContrlOL(const cArray : array of string;
ReEnable : boolean = False);
var
AComponent: TComponent;
begin
for i := Low(cArray) to High(cArray) do
begin
AComponent := FindComponent(cArray[i]);
if Assigned(AComponent) then
if (AComponent is TControl) then
begin
ShowMessage(TControl(AComponent).Name);
TControl(AComponent).Enabled := ReEnable;
end;
end;
end;
Not clear what the stringlists were for, since their contents were lost when execution left the scope of this procedure. If you want to return them, you should create and free them in the calling code.
That sure looks like it ought to work. This is the sort of thing that the debugger can probably help with more than we can here.
Try breaking the problematic line down into multiple lines, like so:
if TControl(AComponent).Enabled then
CompListEna.Add(TControl(AComponent).Name)
else CompListDis.Add(TControl(AComponent).Name);
Rebuild with the "Use Debug DCUs" option on, and place a breakpoint on the if statement. Then use F7 to trace your way through the logic and see what's going on.

Resources