I am trying to raise an event in Matlab. It is working, but not as i expected. What is going on is that the source object is passed as parameter do the event handler and i can't access my object properties inside the event handler.
Here is my class:
classdef MyClass < handle
properties
Name = 'a';
end
events
Changed
end
methods
function obj = MyClass(current)
if exist('current','var')
addlistener(current,'Changed',#ChangedHandle);
end
end
function obj = Change(obj,value)
notify(obj,'Changed');
end
function obj = ChangedHandle(obj,value)
disp(obj.Name);
end
end
end
These are the command lines to reproduce whats going on:
a = MyClass();
b = MyClass(a);
b.Name = 'b';
a.Change(3);
This is returning "a" and i want it to return "b".
Regards
The listener callback you specify receives details of the object raising the event. The fact that the listener has been created during the creation of a different object is not an intrinsic property of the listener – unless you effectively embed this in the design of your callback.
In your example, #ChangedHandle only works as a callback because it happens to be a method of the class current belongs to. If a and b belonged to different classes the problem would be more obvious: if ChangedHandle was a method only of a, it wouldn't know anything about the Name property of b. And if only a method of b, the listener bound to a would have only the function handle without any reference to the instance b of the class to which ChangeHandle belongs.
As described in the listener callback documentation you can use a method of the specific object instance current as an event listener using the syntax #current.ChangedHandle.
This method then receives callback arguments in the format callbackMethod(obj,src,evnt), so you need to add an argument to the definition of ChangedHandle. The first argument obj will be the instance referenced by current when the listener is created, so the line disp(obj.Name) will then produce the intended result without modification.
The reference to a received by the callback in the example will still be passed to the new callback – this is a fundamental behaviour of listener callbacks – but it will now be accessible from the second argument src.
In your addlistener code you are not telling the listener which object you want to call the method on, so what Matlab does it will apply the listener to the current object since thats all that was passed into the function, you can check this by changing the code to:
addlistener(current,'Changed',#(h,evt)current.ChangedHandle);
if you want it to run the code for the other class b (obj), then you use:
addlistener(current,'Changed',#(h,evt)obj.ChangedHandle);
Related
In matlab, I have a class that ties up a shared resource during construction and releases it when deleted. (In my specific case, the shared resource is a port I can interact with it via http://localhost:1234)
It looks like this:
classdef myClass < handle
methods
function myClass()
OpenExecutableUsingPort1234();
end
function delete(this)
CloseExecutableUsingPort1234();
end
end
end
when I use it like this:
instance = myClass();
clear instance;
everything works fine. I open and close it without a problem. But when I use it like this:
instance = myClass();
instance = myClass(); % can't access the port during construction, because it's in use.
I can't start the executable because the port is in use.
The order of events is this:
a first copy of myClass is constructed. there is no problem with ports
the copy is assigned to the 'instance' variable.
a second copy of myClass is constructed. It can't start the executable because the port is in use
the new copy is assigned to the 'instance' variable
the first copy no longer has any references to it, and calls its delete method. This frees up the port.
What kind of workarounds are possible?
Ideally, I'd like step 5 to just know it needs to run early:
a first copy of myClass is constructed. there is no problem with ports
the copy is assigned to the 'instance' variable.
5. the first copy no longer has any references to it, and calls its delete method. This frees up the port.
a second copy of myClass is constructed. the port is free too!
the new copy is assigned to the 'instance' variable
You have to do a manual clear if you want the old instance to go away before the new instance starts creation.
If you do this:
instance = myClass();
instance = myClass();
The second line calls the myClass constructor before the instance variable gets reassigned, triggering GC on the first instance. It doesn't change instance until the second constructor returns, and it has a new value to put in it. (E.g. if the myClass constructor threw an error, instance should remain unchanged.)
Just stick a clear in there:
instance = myClass;
clear instance
instance = myClass;
(This behavior isn't guaranteed by the Matlab language specification as far as I can tell, but it's how things currently work.)
Example:
classdef Deletable < handle
properties
id
end
methods
function this = Deletable
persistent nextId
if isempty(nextId)
nextId = 1;
end
this.id = nextId;
nextId = nextId + 1;
fprintf('Hello! id=%d\n', this.id);
end
function delete(this)
fprintf('Bye bye! id=%d\n', this.id);
end
end
end
function without_clear
instance = Deletable;
instance = Deletable;
end
function with_clear
instance = Deletable;
clear instance
instance = Deletable;
end
Behavior:
>> without_clear
Hello! id=5
Hello! id=6
Bye bye! id=5
Bye bye! id=6
>>
>> with_clear
Hello! id=7
Bye bye! id=7
Hello! id=8
Bye bye! id=8
>>
If you really wanted it to work differently, you could write your class so that there can only be one active instance. Hold on to that in a persistent variable in the class definition. When a new instance is created, have it blow away the actual connection in the prior instance. I think that's kinda gross, though.
Or just work with a single Singleton instance of your class, and have its properties be mutable so you can alter its state instead of creating a new instance.
I started to look into Glib signals. In my scenario I want to connect several instances of a class to a signal and if one of those instances calls g_signal_emit(...), the provided callback function should be called for all instances of that class. Let me try to explain this with an example.
What I tried is to create two instance of a class A (A0 and A1).
Class A registers a very simple signal in its _class_init function.
file_signals[0] = g_signal_newv("a_signal",
G_TYPE_FROM_CLASS(ACLASS),
G_SIGNAL_ACTION,
NULL /* closure */,
NULL /* accumulator */,
NULL /* accumulator data */,
NULL /* C marshaller */,
G_TYPE_NONE /* return_type */,
0 /* n_params */,
NULL); /* argument types*/
Then I connect a simple callback function (cb_a) for both instances A0 and A1 to the signal "a_signal". (For example in the _instance_init function)
g_signal_connect(A0, "a_signal", (GCallback)cb_a, NULL);
g_signal_connect(A1, "a_signal", (GCallback)cb_a, NULL); ,
where cb_a is a function defined in A.c:
static void cb_a(void)
{
printf("cb_a called!\");
}
What happens now is that if I invoke
g_signal_emit(A0, file_signals[0], 0);
is that only the callback associated to A0 is invoked. I couldn´t figure out how to call the function for each instance of A with e.g. a single call to g_signal_emit(...)?
Any help is appreciated. Thanks.
That’s not how GObject signals work. A signal is a one-to-many notification from a single class instance to all the connected callbacks for that class instance. If you want to emit the A::a_signal signal from several instances of the class, you need to call g_signal_emit() for each of those instances.
Note that g_signal_emit() is only meant to be called from within the implementation of a class, not from code outside that class.
One approach to your problem is to have a separate class (let’s call it Signaller) with a signal on it, to store a reference to a single instance of Signaller within A0 and A1, and to connect a_cb to that instance within each of A0 and A1. To notify all instances of the A class, emit Signaller::a_signal on the single Signaller instance.
However, without more concrete information about the particular problem you’re trying to solve, it’s hard to say what the right architecture is. My suggestion above may not be the most appropriate solution.
Suppose I have two classes, a < matlab.mixin.Copyable and b < handle.
Here's a:
classdef a < matlab.mixin.Copyable
properties
hListener
end
methods
function obj = a(b)
obj.newListener(b);
end
function newListener(obj, b)
obj.hListener = addlistener(b, 'bEvent', #(o,e) disp('Received bEvent'));
end
function sobj = saveobj(obj)
sobj = copy(obj);
delete(sobj.hListener);
sobj.hListener = [];
end
end
end
and b:
classdef b < handle
events
bEvent
end
methods
function obj = b
end
function raiseEvent(obj)
notify(obj, 'bEvent');
end
end
end
I use saveobj in a to delete the listener upon save. I will manually re-instantiate a listener when I load the classes later.
a inherits from matlab.mixin.Copyable so I can do a copy during the saveobj operation - this way I can make a copy of the original object and change it, without affecting the original, and then save that to a MAT-file. (Supposedly - this is where my question is going.)
Now I run the following at the command line:
>> bob = b; alice = a(bob);
>> bob.raiseEvent
Received bEvent
Everything works. Now, let's save:
>> save ab alice bob
and try to raise the event again:
>> bob.raiseEvent
% NOTHING HAPPENS!
Turns out the listener is gone!
>> alice.hListener
handle to deleted listener
What is going on here? Why is the listener object shared between sobj and obj in the saveobj method?
In researching this question I discovered the answer. I figured I'd add to the body of knowledge around these parts.
Per the documentation for matlab.mixin.Copyable,
Given a call to the matlab.mixin.Copyable copy method of the form:
B = copy(A);
Under the following conditions, produces the described results:
A has dynamic properties — copy does not copy dynamic properties. You can implement dynamic-property copying in the subclass if needed.
A has no non-Dependent properties — copy creates a new object with no property values without calling the class constructor to avoid introducing side effects.
A contains deleted handles — copy creates deleted handles of the same class in the output array.
A has attached listeners — copy does not copy listeners. (emphasis added)
A contains objects of enumeration classes — Enumeration classes cannot subclass matlab.mixin.Copyable.
A delete method calls copy — copy creates a legitimate copy, obeying all the behaviors that apply in any other usage.
What I took this to mean originally was that a copy call would skip over a property whose value is a listener handle. Apparently what it means is that rather than skipping the property altogether, copy makes a reference to the original listener object, i.e., it copies the handle to the listener but not the listener itself.
Of course, when you load the copied object and it has that reference to listener, it'll complain:
Warning: Cannot load an object of class 'listener':
No matching constructor signature found.
Warning: During load:
An invalid default object has been detected while loading a heterogeneous array of
class event.listener. An empty array of class event.listener will be returned.
The easiest thing to do, then, is to modify saveobj:
function sobj = saveobj(obj)
sobj = copy(obj);
sobj.hListener = [];
end
Here, I don't explicitly call delete on the listener, as this would delete the actual object. Instead, I just clear the reference to that object.
Context
I created two similar PrimarySettings and SecondarySettings handle classes in matlab to store settings of my own and was willing to group both of them in a bigger AllSettings class for convenience.
All classes have a PropertyChanged event to monitor for settings modifications.
The code for all classes can be visualized from here.
Problem
When I test PrimarySettings and SecondarySettings classes to check if their are effectively calling their delete method when all instances are no longer referenced in the code I have no issue:
>> ps = PrimarySettings();
>> clear ps;
Destroying PrimarySettings.
>> ss = secondarySettings();
>> clear ss;
Destroying SecondarySettings.
The same when I test for proper event notifications:
>> as = AllSettings();
>> as.PrimarySettings.Titi = 12;
Property 'PrimarySettings.Titi' modified.
The problem is only with the AllSettings class for which delete method is never called at all:
>> as = AllSettings();
>> clear as;
!!!!!! here delete method is not called !!!!!
The object is still in memory while it is no longer referenced. This can be tested like this:
>> clear classes
Warning: Objects of 'SecondarySettings' class exist. Cannot clear this class or any of its superclasses.
Warning: Objects of 'PrimarySettings' class exist. Cannot clear this class or any of its superclasses.
Warning: Objects of 'AllSettings' class exist. Cannot clear this class or any of its superclasses.
Investigations
The problem seems linked to the way I am attaching the PropertyChanged event (the listener is probably still alive).
Unfortunately I truly don't understand why. Indeed, the code for attaching to events is very similar in all the classes I created.
PrimarySettings and SecondarySettings:
for idx = 1:propCount;
listener = addlistener(obj, meta.PropertyList(idx).Name, 'PostSet', #obj.onPropertyChanged);
listener.Recursive = true;
end
AllSettings:
for idx = 1:propCount;
propName = meta.PropertyList(idx).Name;
listener = addlistener(obj, propName, 'PostSet', #obj.onPropertyChanged);
listener.Recursive = true;
listener = addlistener(obj.(propName), 'PropertyChanged', #obj.onSubPropertyChanged);
listener.Recursive = true;
end
NB: If I comment the second listener (i.e. onSubPropertyChanged) then the AllSettings class works as expected!
Question
What's wrong with the deletion of AllSettings objects ?
If you need a specific diagnosis of the issue, I'm afraid you'll need to post more code, so we can trace through what's going on exactly.
But I would expect that the issue is that one of your listeners is storing a reference to another of your objects in a way that you're not expecting, so it's not getting destroyed when you call clear.
To help you diagnose things further, note that there are two ways to create listeners:
Using addlistener, which ties the listener's lifecycle to the object that is the source of the event.
Constructing the listener directly using event.listener, where the listener exists only while it is in scope, and is not tied to the event-generating object's existence.
For more information, see the section "Ways to create listeners" in this documentation page on Events and Listeners — Syntax and Techniques.
Hope that helps!
I have read where an event is triggered on another thread from the one that created the controls on a Windows Form.
Therefore, the event handler can't directly update the controls (like changing a button's color).
I read the explainations about Invoke or BeginInvoke being needed.
My question: Why can't an event handler just be passed 'this' as an agrument.
'this' being the form whose controls have buttons that want THEIR COLORS CHANGED !! ;)
I can swear I've seen instances where a delegate can take a 'this' - but maybe not...
There's nothing stopping an event handler on another thread just going in and screwing around with the internal state of the button.
However, it causes bad things to happen - as an example, what would happen if you changed a property of a control while something else was also trying to write to it?
Only one thread should be screwing around with the internal state of an object at a time - if you call methods directly on that object from another thread, you can't guarantee that something else isn't doing the same.
Invoke gets around this by not calling it directly - instead it says to the thread that 'owns' the object "Hey, could you call this method on that object when you've got a moment?", thus ensuring that the method is only called when the object is in a consistent state.
If you are handling an event with an instance method in the form, you already have a "this" parameter. Say something like this:
Public Class MyForm
Inherits Form
Private port As New SerialPort()
Private Sub RegisterHandlers()
AddHandler port.DataReceived, AddressOf ProcessData
End Sub
Private Sub ProcessData(ByVal sender As Object, ByVal e As EventArgs)
If Me.InvokeRequired Then
'marshal to required thread
Exit Sub
End If
'do stuff on the form thread
End Sub
End Class