How to send event to multiple forms in Delphi - forms

In my application I have multiple forms that can be visible at the same time and they all show disk space (files, hard disk size etc.) in the same size units. So all of them show disk space in Bytes, KB, MB, GB or TB. I also have a seperate settings form in which the user can change the display size, he wants in the other forms. Once the user clicks OK in the settings form, I want all the other (open) forms to immediately change their size settings.
Every form has a protected procedure SetViewSettings, which takes care of the job. They are all descendants of an ancestor form which defines SetViewSettings as virtual and abstract. The actual displayed forms override the SetViewSettings method of the ancestor. So far no problems.
Because I don't want to call every individual form (FormX.SetViewSetttings, FormY.SetViewSettings, etc.), I am using the following solution:
procedure TApplicationForms.SetUnits;
var
I: Integer;
begin
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I] is TfrAncestorInfo then
with Screen.Forms[I] as TfrAncestorInfo do
acSetUnits.Execute;
end;
This procedure is called from the SettingsForm as the user clicks OK.
TFrAncestorInfo is a descendant of TForm, declaring the SetViewSettings method as virtual and abstract. acSetUnits is an Action, declared in TfrAncestorInfo, which only calls SetViewSettings. This all works fine, but the risk lies in creating a new descendant form of TFrAncestorInfo, whilest forgetting to override the SetViewSettings method, in which case you will run into an 'Abstract Error' exception.
Are there any alternatives to calling the SetViewSettings method in the forms, without listing (calling) all the descendant forms individually? I know of messages and events, but I don't know how to use these in a multiple forms situation. In general: how can I directly send a message to or generate an event for all TFrAncestorInfo descendant forms, without listing them individually?

One option (there are many other possible options) would be to send a custom message to every Form. No need for worry about virtual/abstract overriding, type checking, etc. Only the Forms that implement a message handler will react to the message, the rest will simply ignore it.
const
WM_SETTINGS_UPDATED = WM_APP + 1;
procedure TApplicationForms.SetUnits;
var
I: Integer;
begin
for I := 0 to Screen.FormCount - 1 do
Screen.Forms[I].Perform(WM_SETTINGS_UPDATED, 0, 0);
end;
type
TSomeForm = class(TBaseForm)
private
procedure WMSettingsUpdated(var Message: TMessage); message WM_SETTINGS_UPDATED;
protected
procedure SetViewSettings;
end;
procedure TSomeForm.WMSettingsUpdated(var Message: TMessage);
begin
SetViewSettings;
end;
procedure TSomeForm.SetViewSettings;
begin
//...
end;

Related

Access object on another form with the form as a variable

I'm writing a program in Delphi which includes creating the same dynamic object on multiple forms (never simultaneously), and then a procedure in another unit writes certain text to it.
How the object (TMemo) is created:
memHulp := TMemo.Create(frmHome);
with memHulp do
begin
Parent := frmHome;
Top := 208;
Left := 88;
Height := 98;
Width := 209;
ReadOnly := True;
end;
The properties aren't that important, it's just to show the creation of the object and how it is referred to.
Now, I need to read certain text into the memo from a text file, which there is no problem with, but the problem comes when there are different forms involved that all use that same self-defined procedure.
It's easy to say frmHome.memHulp.Lines.Add() in this particular case, but when I need it to display the text on the memo named exactly the same in all cases, but on a different form, I'm having some trouble.
The frmHome part needs to be a variable. So I tried this:
var
Form: TForm;
begin
Form := Application.FindComponent('frmHome') as TForm;
end;
That doesn't warn me or give an error, but as soon as I try to say Form.memHulp.Lines.Add(), it does not work, and I understand that it probably doesn't have any properties for Form, but how do I make it look at the correct place? I need to be able to tell the program to look on whichever form name I pass as a parameter into the FindComponent() part.
If this is completely not possible, please suggest other solutions to achieve the same.
Form.memHulp doesn't work because Form is a plain vanilla TForm pointer, and TForm doesn't have a memHulp member. You could use Form.FindComponent('memHulp') instead, since you are assigning the TForm object as the Memo's Owner, but that would require you to assign a Name to the Memo, eg:
memHulp := TMemo.Create(frmHome);
with memHulp do
begin
Parent := frmHome;
Name := 'memHulp';
...
end;
Alternatively, since you say you are creating only 1 Memo object at a time, you could simply make memHulp be a global variable in some unit's interface section, and then you would have direct access to it without having to hunt for it.

Delphi REST - How do I know when the data is all retrieved?

In Delphi Tokyo, I have a series of REST components (the ones shipped with Delphi: RESTClient, RESTRequest, RESTResponse, RESTAdapater) tied together to retrieve REST data.
The REST call, as defined on the server, has pagination set to some value.
As such, within my Delphi app I have to repeatedly update the RESTRequest.
ResourceSuffix to add '?page=' and then a page number.
Since various REST Services may have different pagination, or will have different result row counts.
How do I know when I have retrieved all the data?
Surely there is something more elegant that keep trying until rows retrieved = 0/some error.
I found a solution that works for me... My data is coming from Oracle RDBMS, via ORDS and APEX. The REST content (Specifically RAW data) has a URL in it for the next pagination set. For the FIRST set of data, this URL reference is at the end of the REST data stream. For each subsequent data set, the URL reference is at the beginning of the raw data stream, so you have to check both locations. Here is the code I am using...
function IsMoreRESTDataAvailable(Content: String) : Boolean;
var
Test600 : String;
begin
// This routine takes the RESTResponse.Content, aka the raw REST data, and checks to see if there is a NEXT: string
// in either the end of the data (only available for the FIRST DATA set
// or the beginning of the data
result := False;
Test600 := RightStr(Content, 600);
If AnsiPos('"next":{"$ref":"https://<YOUR_SERVER_HERE>', Test600) <> 0 then
begin
result:= True;
Exit;
end;
// If we didn't find it at the end of the REST content, then check at the beginning
Test600 := LeftStr(Content, 600);
If AnsiPos('"next":{"$ref":"https://<YOUR_SERVER_HERE>', Test600) <> 0 then
begin
result:= True;
Exit;
end;
end;

TYPO3 DataHandler: Copying record and relate it to itself --> running into loop

I'm developing an event extension with recurring dates. Therefor, I have a recurring date pattern and copy the record for each date. So the record has a relation to itself:
- Main Event
-- N Child Events
Currently, I use the DataHandler method copyRecord, which works perfect. But this just copies the record, without mapping the relation.
- Main Event --> should have the count of children in the database
-- N Child Events --> should habe the relation to its parent main event
The DB should look like:
Event 1 (Main Event) | uid: 1 | event: 0 | recurring_children: 3 (count)
Event copy 1 | uid: 2 |  event: 1 | recurring_children: 0
Event copy 2 | uid: 3 |  event: 1 | recurring_children: 0
Event copy 3 | uid: 4 |  event: 1 | recurring_children: 0
I tried several ways, but none without problems.
The following try sets the relation in the database, but creates more events (I guess, this loops and I have to build a condition to avoid filling the datamap with duplications):
public function processDatamap_afterDatabaseOperations(
$status,
$table,
$recordUid,
array $fields,
\TYPO3\CMS\Core\DataHandling\DataHandler $parentObject){
$event = BackendUtility::getRecord($table, $recordUid);
if ($status === 'update') {
/** #var \TYPO3\CMS\Core\DataHandling\DataHandler $tce */
$tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
$tce->start(array(), array());
$overrides = [
'is_recurring_parent' => 0,
'is_recurring_child' => 1,
'recurring_weekdays' => '',
'recurring_period_end' => '0000-00-00',
'event' => $recordUid,
'title' => 'FOOBAR'
];
if ($event['is_recurring_parent']) {
$foobar = $tce->copyRecord('tx_technoseumevents_domain_model_event', $event['uid'], $event['pid'], false, $overrides);
}
T3 version 8.7
Sorry, my first time I'm using the datahandler for complex tasks. Maybe someone has a hint for me...
The recursiveness happening in the first place, indicates a problem with your architecture that might be worth addressing before this problem as it would solve it at the root cause instead of treating symptoms. Normally, a record should not relate to itself - but that said, recursiveness can happen in other ways and it might not be possible to avoid in your use case.
Advise for addressing the problem:
Consider adding a condition that prevents your code from being called on tables other than the one table you need to operate on. This alone could actually be why your copies recurse so that's number one to handle.
If possible, and if you are not concerned with short-circuiting things like changing the title TCA field for a record to prepend "Copy of", or causing the copy to be hidden, you can switch to copyRecord_raw. It takes slightly different arguments (you may have to refactor to pass some arguments as part of the overrides array, compared to calling copyRecord). Doing that will prevent hooks from being called on the children you copy.
You are using a nested DataHandler instance - it might be preferable to call that method on the $dataHandler instance in your example. The DataHandler maintains a copy stack, but (parts of) it will only work if you do things in the same instance and only for some particular hooks (i.e. the pre-type and not-global hooks).
Lastly, there exists a runtime cache entry which contains an array of records which have been copied. Although the utility methods that access and store entries in this cache entry are not publicly accessible - so you cannot call them from your hook class - you can read the array of entries, manipulate it and put it back to prevent recursive processing of the same record. See the method isNestedElementCallRegistered in DataHandler (https://github.com/TYPO3/TYPO3.CMS/blob/v8.7.17/typo3/sysext/core/Classes/DataHandling/DataHandler.php#L9034).
Final note: the after-database-operations hook may be called at times when you do not expect it. For example, if you copy a record and also move it (as in: the copy comes from the UI, not programmatically) the array you receive in $fieldArray may not be the final one (for example, pid may be an unexpected value). Not being aware of this and the peculiarities above might also increase the danger of unintentionally causing recursive operations.
EDIT: If you are using bi-directional relations in TCA then removing one side may also improve the situation. For example, each "date" doesn't necessarily have to be aware of which event it is associated with. Something to consider.

passing parameters between forms

Hi everyone i'm trying to passing parameters between forms but i have this error:
frm-92101 there was a failure in the forms server during startup.
I wanna place a button on the EMPLOYEE form that when pressed, will call the EMPLOYEE/DEPENDENT form and automatically query the dependents for the employee being viewed on the EMPLOYEE form.
Icreated the EMPLOYEE Form and add a button.I assigned a trigger when bouton pressed :
DECLARE
pl_id ParamList;
BEGIN
pl_id := Get_Parameter_List('tmpdata');
IF NOT Id_Null(pl_id) THEN
Destroy_Parameter_List( pl_id );
END IF;
pl_id := Create_Parameter_List('tmpdata');
Add_Parameter(pl_id, 'EMPLOYEESSN', TEXT_PARAMETER, :SSN);
Run_Product(FORMS, 'empdepn', SYNCHRONOUS, RUNTIME,
FILESYSTEM, pl_id, NULL);
END;
After that i add a parameter into the EMPLOYEE/DEPENDENT
I created a PARAMETER called EMPLOYEESSN with the code bellow and add a parameter EMPLOYEESSN and add a WHEN-NEW-FORM-INSTANCE trigger :
DECLARE
blk_id Block;
BEGIN
-- Obtain the block ID of the EMPLOYEE block. This is the
-- Master block in the empdepn master/detail form.
blk_id := Find_Block('EMPLOYEE');
IF NOT Id_Null(blk_id) THEN
-- Check to make sure our parameter has a value. If this form
-- were executed by itself, then the parameter will be null.
-- If this form is called from EMPLOYEE then the parameter will
-- be passed along and assigned to: PARAMETER.employeessn
IF (:PARAMETER.employeessn is not null) THEN
-- Since we have a parameter, use it to alter the WHERE Clause
-- property so that it becomes WHERE ssn=:PARAMETER.employeessn
SET_BLOCK_PROPERTY(blk_id,DEFAULT_WHERE,'ssn=' || : PARAMETER.employeessn);
-- Navigate to the EMPLOYEE block and execute a query automatically
GO_BLOCK('EMPLOYEE');
EXECUTE_QUERY;
END IF;
END IF;
END;
Any idea please, thanks for help

Chat window with Gtk+

I'm writing some sort of chat in Ada using Gtk+ (technically GtkAda). And I have of problem with some Gtk. My window consists of an Entry, TextView and Button ("Send").
The hard part is in handler On_Button_Send_Clicked (procedure that deals with signal 'clicked' on button). I want to read text form Entry and place it in TextView, but how can I access TextView and Entry from a procedure that has only access to Button, as I connect the signal with a handler in this way:
package Handlers is new Gtk.Handlers.Callback
(Widget_Type => Gtk_Widget_Record);
procedure On_Button_Send_Clicked
(Object : access Gtk_Widget_Record'Class);
...
Handlers.Connect
(Button, "clicked", Handlers.To_Marshaller (On_Button_Send_Clicked'access);
My question is: are there any methods like Get_Gtk_Entry or Get_Text_View, which would be the simples way? Or is there another way, but still simple?
I have also come across a solution in which I declare a record:
type Widget_Collection_Record is new Glib.Object.GObject_Record with record
Terminal : Gtk.GEntry.Gtk_Entry;
Text_Field : Gtk.Text_View.Gtk_Text_View;
end record;
and make the callback this way:
package Widget_Collection_Cb is new Gtk.Handlers.Callback
(Widget_Type => Widget_Collection_Record);
procedure On_Button_Send_Clicked
(Object : access Widget_Collection_Record'Class);
But now I have another question: how do I connect a signal from a Button with a handler, since the widget Button is not a part of my Widget_Collection_Record?
I'm not sure whether I sound clear...
So please, if you know something that may solve my problem, please post - it could be C, C++, Python - I'll try to convert it to Ada ;D
And the summary of my problem is:
How can I write a handler to read from an Entry and write on a Text_View when a Button clicked?
Edit: Question closed. I'm aware that it's not clear what I asked for, and that's way I've chosen the way to pass record of User_Data to callback... and now my new problem is here
Usually I use this reference : http://www.univ-orleans.fr/sciences/info/ressources/webada/doc/gtkada/gtkada_rm/index.html
You didn't provide much informations about the organization of your project.
But if you have a simple procedure where you declare everything then :
procedure foo is
-- variables
E : GTk_GEntry;
T : Gtk_Text_View;
...
procedure On_Button_Send_Clicked (Object : access Gtk_Widget_Record'Class) is
begin
S : String := Get_Text (E);
B : Gtk_Text_Buffer := Get_Buffer (T);
begin
Set_Text (B, S);
...
end On_Button_Send_Clicked;
begin
...
Handlers.Connect
(Button, "clicked", Handlers.To_Marshaller (On_Button_Send_Clicked'access);
...
end foo