Events and Listeners

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Written against MATLAB R2026b. Events and listeners have existed since R2008a (addlistener and notify); the listener method needs R2017b. Octave 11.3.0 was tried on the examples, and its classdef does not support this feature the way MATLAB does (see the note near the end), so every MATLAB snippet below is labelled "not run here". The behaviour described is taken from the R2026b documentation pages cited under each example.

An event is a notice that an object broadcasts when something happens, and a listener is an object that runs a function, the callback, when that notice arrives. The object that raises the event does not know who is listening. This is the observer pattern, and it is how a progress bar can follow a long simulation without the simulation containing any code about progress bars. In the heatflow scenario (The heatflow scenario) the heatflow.Simulation class raises StepCompleted every time step finishes, and a display function listens. Events belong to handle classes, so read Value and Handle Classes first if the difference between a copy and a reference is not yet clear. The class syntax itself is on classdef Basics.

The pieces at a glance

Piece What it does

events block

Inside classdef, names the events the class can raise. Only a class derived from handle may define events.

notify(obj,"EventName")

Raises the event on obj. Every listener registered for that event on that object runs.

event.EventData

The base class of the second callback argument. Subclass it to carry your own data, and pass the instance as the third argument of notify.

addlistener(obj,"EventName",@callback)

Registers a callback. The listener lives as long as the source object.

listener(obj,"EventName",@callback)

Registers a callback, but the listener lives as long as you keep a reference to it.

delete(lh), lh.Enabled

Permanently remove a listener, or switch it off and on again.

PreSet, PostSet, PreGet, PostGet

Predefined events for a property that has the SetObservable or GetObservable attribute.

ObjectBeingDestroyed

Predefined event that every handle object raises before its destructor runs.

The documentation states the rules in one place: only handle classes can define events and listeners, events are named in an events block, notify broadcasts to every registered listener, and a listener callback must define at least two input arguments, the source object and the event data.

Defining and raising an event

The events block lists names, one per line, like the other class blocks. Any method (or function) that has the right to notify calls notify. Here is the shape of the heatflow.Simulation class, reduced to what matters for events (the shared API of this section has the properties Plate, Boundary, Dt and Time):

classdef Simulation < handle
    properties
        Plate heatflow.Plate
        Boundary heatflow.BoundaryKind = heatflow.BoundaryKind.Dirichlet
        Dt (1,1) double {mustBePositive} = 0.1
        Time (1,1) double = 0
    end
    events
        StepCompleted
    end
    methods
        function step(obj,n)
            arguments
                obj
                n (1,1) double {mustBeInteger,mustBePositive} = 1
            end
            for k = 1:n
                % ... advance the explicit diffusion scheme by Dt ...
                obj.Time = obj.Time + obj.Dt;
            end
            notify(obj,"StepCompleted");
        end
    end
end

Not run here (the diffusion update is elided, and the class needs heatflow.Plate and heatflow.BoundaryKind from the other pages). The documentation example of the same shape defines an event StateChange and a method triggerEvent that calls notify(obj,'StateChange'). The event name is a character vector or a string scalar. Notifying with no listener registered should simply do nothing (the reference page does not spell this out), so a class does not need to check whether anyone is listening.

Two details of the pattern are easy to overlook. First, notify runs the callbacks before it returns, in the same thread, so a slow listener slows the notifier; the call is synchronous. Second, raise the event after the object is in a consistent state (here after Time is updated), because listeners read the object from inside the callback.

Listening: addlistener

el = addlistener(hSource,EventName,callback) registers callback, a function handle, for the named event. The callback receives the source object first and the event data second:

function showProgress(src,evt)
    fprintf("t = %.2f s after %s\n",src.Time,evt.EventName);
end

sim = heatflow.Simulation;
sim.Plate = heatflow.Plate(20,20,0.01,1e-4);
lh = addlistener(sim,"StepCompleted",@showProgress);

sim.step(5);      % showProgress runs once, when step calls notify

Not run here. The documentation lists three forms of the callback handle: @functionName for an ordinary function, @obj.methodName for a method of a particular object, and @ClassName.methodName for a static method. Using a method of a display object as the callback is the usual way to wire a user interface to a model:

view = heatflow.ProgressView;               % some handle object that draws a bar
lh = addlistener(sim,"StepCompleted",@view.update);

Not run here (heatflow.ProgressView is an illustration, not part of the shared API). In the second form view.update is called as update(view,src,evt), so the method signature is function update(obj,src,evt).

If hSource is an array of handle objects, the listener responds to the named event on any element. The addlistener(___) form, with no output, creates a listener nobody can hold a variable for; that is fine as long as you never need to disable or delete it before the source dies.

What happens when the event fires

sequenceDiagram participant U as Caller participant S as Simulation sim participant L as Listener lh participant C as showProgress U->>S: step(5) S->>S: advance Time five times S->>L: notify StepCompleted L->>C: showProgress(sim, evt) C-->>L: returns L-->>S: all listeners done S-->>U: step returns

Figure: one call to step raises one StepCompleted event; each listener callback runs to completion before step returns.

Event data: subclassing event.EventData

The second callback argument is an event.EventData object. The base class carries the source and the event name; to hand extra information to the listeners, subclass it, add properties, and pass an instance as the third argument of notify. The data argument of notify must be a subclass of event.EventData.

classdef StepEventData < event.EventData
    properties
        StepsTaken (1,1) double
        MaxTemperature (1,1) double
    end
    methods
        function obj = StepEventData(steps,tmax)
            obj.StepsTaken = steps;
            obj.MaxTemperature = tmax;
        end
    end
end

Raise the event with the data from inside step:

notify(obj,"StepCompleted",heatflow.StepEventData(n,obj.Plate.maxTemperature));

And read it in the callback:

function showProgress(~,evt)
    fprintf("%d steps, hottest point %.1f C\n",evt.StepsTaken,evt.MaxTemperature);
end

Not run here. If a notifier calls notify without data, the callback still receives an event.EventData object, so showProgress(src,evt) is valid for either form, but reading evt.StepsTaken then fails. Put the event data class in the same namespace folder as the notifier (here +heatflow, see Namespaces and Class Folders) so the two travel together.

Listener lifecycle: addlistener against listener

There are two ways to create a listener, and they differ in who owns its life.

Aspect addlistener listener

Lifetime

Tied to the event source. When the source object is destroyed, MATLAB destroys the listener.

Not tied to the source. The listener is active while the listener object stays in scope and is not deleted.

You must

Nothing, unless you want to stop early (delete(lh)).

Keep the returned object in a variable or property, or it can be deleted and the callback stops firing.

Clearing the variable

Does not delete the listener, because the source still references it.

Can delete the listener if nothing else references it.

Best for

A listener that belongs with the source and should vanish with it.

A source and listeners defined in different components that you add, remove or change independently.

Introduced

R2008a

R2017b

The documentation adds that a listener made with addlistener can outlive its source if another variable still references it or if it is tied to other event sources. The listener method is the better choice for a view that should control its own subscription: store the listener in a property of the view, and the subscription then ends when the view object is deleted.

classdef ProgressView < handle
    properties (Access = private)
        Listener
    end
    methods
        function obj = ProgressView(sim)
            obj.Listener = listener(sim,"StepCompleted",@obj.update);
        end
        function update(obj,src,evt) %#ok<INUSD>
            fprintf("t = %.2f\n",src.Time);
        end
    end
end

Not run here. Because the listener is a property of the view, deleting the view deletes the listener, and sim carries on without a dangling callback. With addlistener and a method handle @obj.update the source would hold a reference to the view and keep it alive until the source dies or you delete the listener, which is the usual cause of "the old window is still reacting".

Switching listeners off and removing them

The listener object returned by addlistener or listener has an Enabled property:

lh.Enabled = false;   % callbacks stop firing; the listener still exists
sim.step(1);          % no output
lh.Enabled = true;    % back on
delete(lh)            % permanently removed

Not run here. Use Enabled for a temporary pause, for example while a batch of steps runs and you want one update at the end; use delete when the listener is no longer needed.

Property events: PostSet and friends

Besides your own events, MATLAB defines four events for properties: PreSet and PostSet around an assignment, PreGet and PostGet around a read. To use them, declare the property with the SetObservable attribute (for PreSet and PostSet) or GetObservable (for PreGet and PostGet), and use the four-argument form of the listener call, which names the property:

classdef Simulation < handle
    properties (SetObservable)
        Dt (1,1) double {mustBePositive} = 0.1
    end
end

sim = heatflow.Simulation;
lh = addlistener(sim,"Dt","PostSet",@(src,evt) disp("Dt changed"));
sim.Dt = 0.05;    % the callback runs after the value has been stored

Not run here. addlistener(hSource,PropertyName,EventName,callback) is the documented signature, and the same form exists for listener. The PostSet callback runs after the new value is stored, and PreSet runs before the property’s set method is called. A property event does not tell you why the value changed, only that it did, and it costs a callback on every assignment, so prefer an explicit event such as StepCompleted for the notifications that matter to your design. A property set listener is the right tool when you do not own the class that holds the property, since you can observe it without editing it (the property still has to be declared SetObservable by its author).

Every handle object also raises ObjectBeingDestroyed before its destructor runs, which is the hook for cleaning up resources that a listener owns.

What Octave does with this

Octave 11.3.0 accepts the events block in a classdef, but its addlistener function is the one for graphics objects, not for class events. A small test, a handle class Sim with an events block and a method calling notify(obj,'StepCompleted'), followed by addlistener(s,'StepCompleted',@(src,evt) ...), stopped at the addlistener call (Octave 11.3.0 output, MATLAB would run the callback):

error: addlistener: invalid handle H

So Octave cannot be used to try the examples on this page. Test events in MATLAB, or hold the logic in plain function handles stored in a cell array when you need something that runs in both.

Where the books are outdated

Books written for releases before R2008a do not mention events at all, and the usual advice in them is to poll a property in a loop or to pass a callback function handle into the object and call it by hand. That still works, but it ties the object to one caller; events let any number of listeners subscribe without the object knowing. Books from the R2008a to R2016 period describe addlistener only and say that a listener lives until the source is destroyed; the listener method (R2017b) was added later for the case where that coupling is unwanted. Some texts also show addlistener called as addlistener(obj,'Prop','PostSet',@fcn) and treat it as the only way to be told about a property change; it still works, but a dedicated event is the clearer design.