Changing Handlers

Hey guys,

I have an object that I hold in a module so it is universally accessible in my app. This object has some events that I have handled by the use of AddHandler directives in a container control in my app. That’s been fine until now as I’ve only needed to use those events in that container control. I’ve just realized that in some cases, I want to temporarily change one of the event handlers to a method in a different container control.

I could easily use implicit calls to remove the handler from the first container control instance and add it to a second. However, I want to make it more OOP and not have to rely on the implicit path.

But I can’t Add a handler when one is already handled. I can’t remove a handler without having the path to the handler method. So how do I do this? Would an interface be a better way than raising events?

Thanks,

Jon

[quote=171497:@Jon Ogden]Hey guys,

I have an object that I hold in a module so it is universally accessible in my app. This object has some events that I have handled by the use of AddHandler directives in a container control in my app. That’s been fine until now as I’ve only needed to use those events in that container control. I’ve just realized that in some cases, I want to temporarily change one of the event handlers to a method in a different container control.

I could easily use implicit calls to remove the handler from the first container control instance and add it to a second. However, I want to make it more OOP and not have to rely on the implicit path.

But I can’t Add a handler when one is already handled. I can’t remove a handler without having the path to the handler method. So how do I do this? Would an interface be a better way than raising events?
[/quote]

You could call different methods from within the handler according to properties of the container.

I use delegates instead of events whenever I want flexible dispatching like that.

Yeah. But isn’t that still some “implicit” in the calling? But the handler method that is currently handling the event is in a different container control. So the items/controls, I need to access in this one instance aren’t accessible. So that’s the problem. I still need to get the event handler to each of the pages.

I wish that issuing a second AddHandler call would simply replace the first one.

That’s what I was thinking but I’ve never used delegates so I’m not sure where to begin. So here’s what I have…

The object has a serial control in it and there’s a DataAvailable event. I have a “local” DataAvailable event in the object itself where I do some work but then I also raise the event so that I can do other things in a ContainerControl or Window, etc.

So how would I use a delegate in this case instead of the event handler?

First you need to define a delegate type. This is the signature methods must have to be stored in a variable of the delegate.

Private Delegate delMyDataAvailable(sender As Serial)

So here, I prefix delegate names with “del” out of habit, and the sender parameter is to match your current methods used with AddHandler, but it’s not necessary.

Then you need a property to store the delegating method

Private Property dataAvailableCallback As delMyDataAvailable

And a method to set that property

Sub setDataAvailableHandler(theCallback As delMyDataAvailable) dataAvailableCallback = theCallback End Sub

Finally, when you want to call it, instead of RaiseEvent it’s like this

if dataAvailableCallback <> nil then dataAvailableCallback.Invoke(self) end

Now to set the handler, instead of

AddHandler MyObj.DataAvailable, AddressOf myHandler

it’s

MyObj.setDataAvailableHandler(AddressOf myHandler)

Setting a new handler will replace the old one in that property, or you can remove a handler with

MyObj.setDataAvailableHandler(nil)

Argh, only when I want to edit is it disabled :stuck_out_tongue:

Re-reading your setup I think this is what you have…

[code]Class MyClass

EventDefinition MyDataAvailable()

Private Property theSerial As Serial

Private Sub handleSerialDataAvailable(sender As Serial)
RaiseEvent MyDataAvailable()
End Sub

Sub Constructor()
theSerial = new serial
AddHandler theSerial.DataAvailable, AddressOf handleSerialDataAvailable
End Sub

End Class[/code]

So your handler is from your class, not the Serial, so the delegate param would be different. Anyways, refactoring would look like this

[code]Class MyClass

Private Delegate delMyDataAvailable(sender As MyClass)

Private Property theSerial As Serial
Private Property dataAvailableCallback As delMyDataAvailable

Private Sub handleSerialDataAvailable(sender As Serial)
if dataAvailableCallback <> nil then dataAvailableCallback.Invoke(self)
End Sub

Sub setDataAvailableHandler(theCallback As delMyDataAvailable)
dataAvailableCallback = theCallback
End Sub

Sub Constructor()
theSerial = new serial
AddHandler theSerial.DataAvailable, AddressOf handleSerialDataAvailable
End Sub

End Class[/code]

Note that the delegate definition is private. This allows it to be used as a parameter type in setDataAvailableHandler yet the passed in value is still type checked at compile time, but otherwise it’s not exposed. i.e…

MyObj.setDataAvailableHandler(AddressOf properMethod) //fine

MyObj.setDataAvailableHandler(AddressOf wrongSignatureMethod) //type mismatch

I don’t know, that was something I didn’t expect when starting with delegates. I thought the type had to be public for outside access to know whats proper, but magically the compiler knows what to do :slight_smile:

I would recommend using a class interface and setting a reference to the container in the serial object. Add a class interface named SerialHandler to your project. Give it one method HandleSerial(data as string). Assign that interface to the containers you want to share the control. Subclass the serial object, if you haven’t already and add a property, Handler as SerialHandler. Implement the DataAvailable event in the subclass

if Handler <> Nil then Handler.HandleSerial(me.ReadAll)

Set the Handler to whichever container you wish to receive the data. Done.

Edit: and of course, implement the HandleSerial method in each container.

Yeah, I assumed a situation about how your handlers flow. Implementing with an interface would be very similar to a delegate: define type, add property of type, add setter and invoke. An interface is a little cleaner and direct code-wise I think, you don’t have any AddressOfs.

A restriction with using an interface, and this is where my assumption was, is that it defines a single point of access. When I’m dispatching things like this I often want to wire multiple callbacks of the same type to specific handlers. Like a Controller class might wire Action callbacks from 3 different buttons to 3 individual handlers. Using an interface each button would be calling back to the same, single method that’s implementing the interface. This can be resolved the way AddHandler does, by adding a reference to the sender in the callback.

It’s not having to use a sender parameter and specific handlers is why I prefer to use delegates, but if you don’t need that kind of switching/wiring then definitely go with an Interface.

Tim also points out that you can send the callback directly from your Serial class which is less hopping.

Will and Tim,

Thanks. I have considered both concepts - using the delegate and using the class interface. I think Will is correct - the class interface is going to a single point. I have a single class instance of the object that I’m talking about. Interfaces work well when you have multiple instances of a class.

I’m going to mess around with adding the delegates here tonight. I’ll post by what I get… :slight_smile:

I think you have it backwards.

From your description, it sounds like the single point is the object with the serial control and you have multiple containers and windows that you want to respond to the event. It’s those containers and windows that would implement the interface and register themselves with the serial/object as the handler.

Interfaces work best when you have multiple instances of different classes. It allows you to treat them as if they were the same class.

[quote=171528:@Tim Hare]I think you have it backwards.

From your description, it sounds like the single point is the object with the serial control and you have multiple containers and windows that you want to respond to the event. It’s those containers and windows that would implement the interface and register themselves with the serial/object as the handler.
[/quote]

Yes - single object but need to have it respond different in different places.

[quote]
Interfaces work best when you have multiple instances of different classes. It allows you to treat them as if they were the same class.[/quote]

Right. And I have one instance of once class here. I think the delegate is what I really want. So far I have it working with one method in the first container control…

Will - your concept works beautifully!

I am able to switch which method is handling my object’s DataAvailable event depending on what I am doing. This is exactly what I wanted to be able to do - have a way to change the handler for an event without having to unhook the previous handler. Using the delegates as you described, I am able to do it dynamically on the fly. Perfect. I had a feeling this was the way to do it - had just never done it before…