Search code examples
oopdelphieventsfiremonkey

Delphi FMX: How to add an Event into an object (just like TEdit.OnChange)


Assume I have created a new FMX project with nothing more than a TButton and a TProgressBar on it. Now I added a 'Countertest.pas' file using [Shift] + [F11]. (see code below)

Now I have implemented a procedure into 'Unit1.pas' (the main app) to be triggered by a procedure inside the 'Countertest.pas' file to change the Value of the TProgressBar.

Inside 'Unit1.pas' I wrote this to be called from inside the 'Countertest.pas' file:

procedure TForm1.SomethingChanged(newPercentage:Integer);
begin
ProgressBar1.Value:=newPercentage;
showmessage('Congratulations, you have just reached '+IntToStr(newPercentage)+' Percent ;)');
end;

To simplify my problem, this is my stripped down 'Countertest.pas' file:

unit Countertest;

interface

uses FMX.Memo; // Will be used later

type
  TCountertest = Class
  private
    zahl: Integer;
  published
    constructor Create();
    destructor Destroy();
    procedure Counter();
    property Percentage: Integer read zahl;
  end;

implementation

constructor TCountertest.Create();
begin

end;

destructor TCountertest.Destroy();
begin

end;

procedure TCountertest.Counter();
begin
  for i := 0 to 1337 do
  Percentage:=0;
  begin
    zahl:=i;
    if Percentage<>round(i / 100) then 
    begin
        // Here I want to call a Procedure inside 'Unit1.pas' to change a Value of the TProgressBar (and some other components like TLabel.Text)
    end;
    Percentage:=round(i / 100);
  end;
end;

end.

As far as I know there is the possibility to use something like procedure(Sender: TObject) of object; and it seems to be the thing a want to use, however I don't have any idea of how to use this. My Intention is to write something similar to an OnChange Event used in a TEdit Control.

Of Course I could add 'Unit1.pas' into the Uses section of 'Countertest.pas' and then call the procedure directly, but as have to handle multiple Instances of TCountertest, I want to have it more like this:

procedure InstanceOfTCountertest.SomethingChanged(newPercentage:Integer);
begin
ProgressBar1.Value:=newPercentage;
showmessage('Congratulations, you have just reached a new Percent ;)');
end;

In the final app there are multiple Instances of TCountertest, so I have multiple Progressbars as well (and other GUI components such as TLabels). Maybe there are other ways to do this, so feel free to suggest anything, that could fit the purpose to show the Progress of those Instances.


Solution

  • Usually, components (like TButton for example) expose events as properties (eg.: TButton.OnCLick) and it's up to the parent or sibling components (the parent TForm in this case) to set the event handler. Let's say we want to change the Button1.OnClick event handler:

    // Setting the handler:
    
    procedure TForm1.Create(AOwner: TComponent);
    begin
        Button1.OnClick := Self.MyCustomClickHandler;
    end;
    
    // And the handler implementation:
    
    procedure TForm1.MyCustomClickHandler(Sender: TObject);
    begin
        // ...
    end;
    

    So, I imagine you want to have an event for your TCountertest like TCountertest.OnCount so that other components / forms can set a handler and act in response to a change in your counter's progress. Let's describe how to do it the Delphi way (untested code):

    First, your component should implement and expose the event:

    unit Countertest;
    
    interface
    
    type
        // Custom type for your event handler
        TCountEvent = procedure(Sender: TObject; Percentage: Integer) of object;
    
        TCountertest = Class
        private
            FPercentage: Integer;
    
            // Variable that holds the event handler set by the user
            FOnCount: TCountEvent;
    
            // Internal event trigger
            procedure DoCount();
    
        public
            constructor Create();
            destructor Destroy();
            procedure Counter();
    
        published
            property Percentage: Integer read FPercentage;
    
            // This is the property for your event handler assignment
            property OnCount: TCountEvent read FOnCount write FOnCount;
        end;
    
    implementation
    
    constructor TCountertest.Create();
    begin
        // Initialize event handler to nil
        FOnCount := nil;
    end;
    
    destructor TCountertest.Destroy();
    begin
    end;
    
    // Event trigger
    procedure TCounterTest.DoCount();
    begin
        // Check that the user assigned an event handler
        if Assigned(FOnCount) then
            FOnCount(Self, FPercentage);
    end;
    
    procedure TCountertest.Counter();
    begin
        // Your code (update FPercentage in your code)...
    
        // When you need to trigger the event:
        DoCount();
    
        // Rest of your code...
    end;
    
    end.
    

    Now we are ready to create and set event handlers to your instances of TCountertest:

    unit Unit1;
    
    // ...
    
    type
        TForm1 = class
        private
            // ...
    
            // Declare the handler
            procedure CounterCount(Sender: TObject; Percentage: Integer);
        public
            // ...
        end;
    
    implementation
    
    // ...
    
    // Implement the handler
    procedure TForm1.CounterCount(Sender: TObject; Percentage: Integer);
    begin
        ProgressBar1.Value := Percentage;
    end;
    
    // Set the event handlers for your counters
    procedure TForm1.Create(AOwner: TComponent);
    var
        Counter1, Counter2: TCountertest;
    begin
        // Create the counters
        Counter1 := TCountertest.Create();
        Counter2 := TCountertest.Create();
    
        // Set the event handlers
        Counter1.OnCount := Self.CounterCount;
        Counter2.OnCount := Self.CounterCount;
    end;
    
    end.
    

    I hope it helps. Feel free to ask if not.