views:

959

answers:

5

How do I pass along an event between classes?

I know that sounds ridiculous (and it is) but I've been stumped on this for the past little while. Search didn't turn up a similar question so I figured I would pose it.

Here are the objects involved:

WinForm -> Speaker -> Tweeter
                   -> Woofer

[Speaker, Tweeter, Woofer] all declare a "SpeakToMe" event that sends a simple string message. The events are declared using the standard pattern:

public delegate void SpeakToMeHandler(object sender, SpeakToMeEventArgs e);
public event SpeakToMeHandler SpeakToMe;
protected virtual void OnSpeakToMe(string message)
{
   if (SpeakToMe != null) SpeakToMe(this, new SpeakToMeEventArgs(DateTime.Now.ToString() + " - " + message));
}

SpeakToMeEventArgs is a simple class inheriting from EventArgs & containing a string property (Message).

On their own, each of these events works fine. E.g., I set a button in the form to create, subscribe, and fire the event for [Speaker, Tweeter, Woofer]. Each reports back properly.

The problem is when Speaker creates a [Tweeter, Woofer] and subscribes to their events.

What I want is for [Tweeter, Woofer] to fire their event, Speaker to consume it and fire it's own event. I thought this should be very straight forward:

void tweeter_SpeakToMe(object sender, SpeakToMeEventArgs e)
{
   Console.Out.WriteLine("the tweeter is speaking: " + e.Message);
   this.OnSpeakToMe("tweeter rockin' out [" + e.Message + "]");
}

Stepping through this function (in Speaker), Console.Out.WriteLine works. Continuing to step through OnSpeakToMe, shows that the delegate is null.

Speaker's SpeakToMe event is subscribed to by the form. I understood that this should prevent the event's delegate from being null.

I'm sure this is an easy one, what am I missing?

Btw, in case you're curious as to why I'm looking for this. [Speaker, Tweeter, Woofer] are my demo stand-ins for a really long data processing operation. The form runs several of these concurrently and requires progress updates from each class.

As always, any and all help is greatly appreciated!

Update: Thanks for all of the feedback everyone. I really appreciate the help! I picked up a couple of good tips (@David Basarab & @Brian) and a few different ideas on how to structure things. Again, much appreciated!

+2  A: 

The only way the delegate will be null is if the form is actually NOT subscribed to the event. Did you create a new instance of speaker at some point? If you had subscribed one instance, and then created a new instance using the same variable, the events for the new instance will not be hooked up.

Jon B
+3  A: 

If I understood what you wanted in the basic sense. Is to have the Tweeter and Woofer fire an event that the Speaker is subscribed too then fire its own.

Here is my code that has this output

OUTPUT

OnSpeak Message = OnSpeakToMeHander Orginal Message: Fired By Tweeter

OnSpeak Message = OnSpeakToMeHander Orginal Message: Fired By Woofer

class Program
{

    static void Main(string[] args)
    {
        Console.Clear();

        try
        {
            Speaker speaker = new Speaker();
            speaker.speakerEvent += new SpeakToMeHandler(Program.OnSpeak);

            // Cause events to be fied
            speaker.Tweeter.CauseEvent();
            speaker.Woofer.CauseEvent();

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: {0}", ex.Message);
            Console.WriteLine("Stacktrace: {0}", ex.StackTrace);
        }
    }

    public static void OnSpeak(object sendere, SpeakToMeEventArgs e)
    {
        Console.WriteLine("OnSpeak Message = {0}", e.Message);
    }

}

public delegate void SpeakToMeHandler(object sender, SpeakToMeEventArgs e);

public class SpeakToMeEventArgs : EventArgs
{
    public string Message { get; set; }
}

public class Speaker
{
    public event SpeakToMeHandler speakerEvent;

    public Tweeter Tweeter { get; set; }
    public Woofer Woofer { get; set; }

    public void OnSpeakToMeHander(object sender, SpeakToMeEventArgs e)
    {
        if (this.speakerEvent != null)
        {
            SpeakToMeEventArgs args = new SpeakToMeEventArgs
                {
                    Message = string.Format("OnSpeakToMeHander Orginal Message: {0}", e.Message)
                };

            this.speakerEvent(this, args);
        }
    }

    public Speaker()
    {
        this.Tweeter = new Tweeter();
        this.Woofer = new Woofer();

        Tweeter.tweeterEvent += new SpeakToMeHandler(this.OnSpeakToMeHander);
        Woofer.wooferEvent += new SpeakToMeHandler(this.OnSpeakToMeHander);
    }
}

public class Tweeter
{
    public event SpeakToMeHandler tweeterEvent;

    public void CauseEvent()
    {
        SpeakToMeEventArgs args = new SpeakToMeEventArgs()
            {
                Message = "Fired By Tweeter"
            };

        if (this.tweeterEvent != null)
        {
            this.tweeterEvent(this, args);
        }
    }
}

public class Woofer
{
    public event SpeakToMeHandler wooferEvent;

    public void CauseEvent()
    {
        SpeakToMeEventArgs args = new SpeakToMeEventArgs()
            {
                Message = "Fired By Woofer"
            };

        if (this.wooferEvent != null)
        {
            this.wooferEvent(this, args);
        }
    }
}
David Basarab
Thanks for the answer David. Your structure was very similar to what I had in place. The primary difference was the unified function to raise the event.Still wasn't able to pin down 100% where my error was but your structure works like a charm. I must've overlooked one of the pieces...it's been one of those days.Thanks again!
Mark
A: 

Rather than passing the event, why not assign an additional handler for the event? You aren't limited to just one method.

//Handler class
public class Speaker {
    public delegate void HandleMessage(string message);
    public event HandleMessage OnMessage;
    public void SendMessage(string message) {
        if (OnMessage != null) { OnMessage(message); }
    }
}

//then used like...
Speaker handler = new Speaker();
handler.OnMessage += (message) => { Console.WriteLine("Woofer: {0}", message); };
handler.OnMessage += (message) => { Console.WriteLine("Tweeter: {0}", message); };
handler.SendMessage("Test Message");
Hugoware
As I understood the question the signal flow is in the opposite direction? Speaker is listening to Tweeter and Woofer, and the form is listening to Speaker. Maybe I misread it though...
Fredrik Mörk
Possibly, but it seems to me that you could still organize your code so that event handlers take care of themselves instead of passing one event to another... Not sure though :)
Hugoware
@HBoss thanks for the insight. Definitely an interesting approach. Unfortunately Fredrik has it right, for this particular case the flow is in the opposite direction.
Mark
A: 

You can use the long form of event declaration to pass the event to an internal object.

public class Speaker
{
    public Speaker()
    {
        this.MyTweeter = new Tweeter();
    }

    public Tweeter MyTweeter { get; private set; }

    public event SpeakToMeHandler SpeakToMe
    {
        add { MyTweeter.SpeakToMe += value; }
        remove { MyTweeter.SpeakToMe -= value; }
    }
}
Cameron MacFarland
+4  A: 

Eric Lippert warns against if (SpeakToMe != null) code. While it might not be an issue in your case (i.e. if you never remove events), you should get into the habit of using this instead:

var tmp = SpeakToMe;
if (tmp!=null) tmp(/*arguments*/);
Brian
Ah, thankyou for that link. I've been looking for that for ages.
Cameron MacFarland
Mark