Event with multiple subscribers. Cancel invocation of other handlers.

P

Peter M.

Hi all,

If an event has multiple subscribers, is it possible to cancel the
invocation of event handlers from an event handler?

Or to be more specific:

I'm subscribing to the ColumnChanging event of a datatable, from two
seperate classes. I wish to do some data validating. Due to the nature of
the data validation being done, I seperated this code in two classes.

When the value changes, as expected, both event handlers are being invoked.
However, if the first validation rejects the data, the second handler really
shouldn't be invoked anymore. I would like to cancel the remaining handler
invocations.

Is this (easily) possible?

Thanks.
 
N

Nicholas Paldino [.NET/C# MVP]

Peter,

It's not easy, but it is possible.

What you want to do is derive your EventArgs class from CancelEventArgs.
This exposes a Cancel property which can be set to true to indicate that the
action should be cancelled.

Then, what you have to do is get the invocation list for your field
where the event is stored. You can do this by calling GetInvocationList.

Then, you would loop through that list, calling the delegate. If the
Cancel property is set to true, then you stop invocation.

Of course, this means your event handlers have to set the Cancel
property to indicate that there is a problem, or the validation failed.

Hope this helps.
 
P

Peter M.

Thanks for your answer. Your answer pretty much confirms what I was afraid
of, that an easy clean solution is not readily availabe.

I think I will give my design some more thought and put validation code in
one place.

Nicholas Paldino said:
Peter,

It's not easy, but it is possible.

What you want to do is derive your EventArgs class from
CancelEventArgs. This exposes a Cancel property which can be set to true
to indicate that the action should be cancelled.

Then, what you have to do is get the invocation list for your field
where the event is stored. You can do this by calling GetInvocationList.

Then, you would loop through that list, calling the delegate. If the
Cancel property is set to true, then you stop invocation.

Of course, this means your event handlers have to set the Cancel
property to indicate that there is a problem, or the validation failed.

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Peter M. said:
Hi all,

If an event has multiple subscribers, is it possible to cancel the
invocation of event handlers from an event handler?

Or to be more specific:

I'm subscribing to the ColumnChanging event of a datatable, from two
seperate classes. I wish to do some data validating. Due to the nature of
the data validation being done, I seperated this code in two classes.

When the value changes, as expected, both event handlers are being
invoked. However, if the first validation rejects the data, the second
handler really shouldn't be invoked anymore. I would like to cancel the
remaining handler invocations.

Is this (easily) possible?

Thanks.
 
M

Marc Gravell

To be fair, this adds about 2 lines to the code that fires the event - one
"foreach", and one "if"... and could probably be wrapped into a helper
function for even more re-use in about 2 minutes... to me that qualifies as
easy, *reasonably* clean, and readily available.

Marc

Peter M. said:
Thanks for your answer. Your answer pretty much confirms what I was afraid
of, that an easy clean solution is not readily availabe.

I think I will give my design some more thought and put validation code in
one place.

Nicholas Paldino said:
Peter,

It's not easy, but it is possible.

What you want to do is derive your EventArgs class from
CancelEventArgs. This exposes a Cancel property which can be set to true
to indicate that the action should be cancelled.

Then, what you have to do is get the invocation list for your field
where the event is stored. You can do this by calling GetInvocationList.

Then, you would loop through that list, calling the delegate. If the
Cancel property is set to true, then you stop invocation.

Of course, this means your event handlers have to set the Cancel
property to indicate that there is a problem, or the validation failed.

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Peter M. said:
Hi all,

If an event has multiple subscribers, is it possible to cancel the
invocation of event handlers from an event handler?

Or to be more specific:

I'm subscribing to the ColumnChanging event of a datatable, from two
seperate classes. I wish to do some data validating. Due to the nature
of the data validation being done, I seperated this code in two classes.

When the value changes, as expected, both event handlers are being
invoked. However, if the first validation rejects the data, the second
handler really shouldn't be invoked anymore. I would like to cancel the
remaining handler invocations.

Is this (easily) possible?

Thanks.
 
M

Marc Gravell

Apols - I misread the event details; since the datatable's event isn't owned
by your code, this would indeed be quite tricky. However, you could (maybe)
subscribe with a forwarding mechanism? i.e. you have one instance (of a
proxy class) subscribe to the datatable's events, which then enumerates
through it's own subscribers (exposing the original args as a param of the
new CancelEventArgs based args), stopping if/when necessary

Perhaps...

Marc

Marc Gravell said:
To be fair, this adds about 2 lines to the code that fires the event - one
"foreach", and one "if"... and could probably be wrapped into a helper
function for even more re-use in about 2 minutes... to me that qualifies
as easy, *reasonably* clean, and readily available.

Marc

Peter M. said:
Thanks for your answer. Your answer pretty much confirms what I was
afraid of, that an easy clean solution is not readily availabe.

I think I will give my design some more thought and put validation code
in one place.

Nicholas Paldino said:
Peter,

It's not easy, but it is possible.

What you want to do is derive your EventArgs class from
CancelEventArgs. This exposes a Cancel property which can be set to true
to indicate that the action should be cancelled.

Then, what you have to do is get the invocation list for your field
where the event is stored. You can do this by calling
GetInvocationList.

Then, you would loop through that list, calling the delegate. If the
Cancel property is set to true, then you stop invocation.

Of course, this means your event handlers have to set the Cancel
property to indicate that there is a problem, or the validation failed.

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)


Hi all,

If an event has multiple subscribers, is it possible to cancel the
invocation of event handlers from an event handler?

Or to be more specific:

I'm subscribing to the ColumnChanging event of a datatable, from two
seperate classes. I wish to do some data validating. Due to the nature
of the data validation being done, I seperated this code in two
classes.

When the value changes, as expected, both event handlers are being
invoked. However, if the first validation rejects the data, the second
handler really shouldn't be invoked anymore. I would like to cancel the
remaining handler invocations.

Is this (easily) possible?

Thanks.
 
M

Marc Gravell

To show what I meant: here, EventProxy will only work with EventArgs type
args, and Handler will only match the standard event-handler signature, but
this code suffices to short-circuit the invocation of events where we don't
own the original code (i.e. we can't change it to a simple CancelEventArgs
type implementation). In the demo, note that if you press a, only "1" & "2"
are fired; if you use Ctrl, only "1" is fired, else they all do.

This might save you from refactoring? Of course, you still need to indicate
to the original args what happened...

Marc

public static void Main() {
using (Form form = new Form()) {
TextBox tb = new TextBox();
form.Controls.Add(tb);
EventProxy<KeyEventArgs> ep = new
EventProxy<KeyEventArgs>();
ep.Event += Handler1;
ep.Event += Handler2;
ep.Event += Handler3;
tb.KeyDown += ep.Handler;
form.ShowDialog();
}
}

static void Handler1(object sender,
EventProxyEventArgs<KeyEventArgs> e) {
System.Diagnostics.Debug.WriteLine("Handler1");
if (e.InnerEventArgs.Control)
e.Cancel = true;
}
static void Handler2(object sender,
EventProxyEventArgs<KeyEventArgs> e) {
System.Diagnostics.Debug.WriteLine("Handler2");
if (e.InnerEventArgs.KeyData == Keys.A)
e.Cancel = true;
}
static void Handler3(object sender,
EventProxyEventArgs<KeyEventArgs> e) {
System.Diagnostics.Debug.WriteLine("Handler3");
}
}

public class EventProxyEventArgs<T> : CancelEventArgs where T :
EventArgs {
public readonly T InnerEventArgs; // cheap for demo
public EventProxyEventArgs(T innerEventArgs) {
InnerEventArgs = innerEventArgs;
}
}
public class EventProxy<T> where T : EventArgs {
public event EventHandler<EventProxyEventArgs<T>> Event;
public void Handler(object sender, T eventArgs) {
if (Event != null) {
EventProxyEventArgs<T> args = new
EventProxyEventArgs<T>(eventArgs);
foreach(EventHandler<EventProxyEventArgs<T>> subscriber in
Event.GetInvocationList()) {
subscriber(sender, args); // using original sender;
could use "this" and pass original in args
if (args.Cancel) break;
}
}

}
}
 
P

Peter M.

Marc,

Thanks for your answer.

I 'll try your code and see if this could be of any help.

I appreciate the effort.

peter.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top