EventHandler bubbling up - c#

I want to bubble up a message through classes. I used events and did this:
public class TopLevel{
public event EventHandler<string> Message;
public MiddleLevel mid;
public TopLevel()
{
mid.Message += (s, e) => { Message(s, e) };
}
}
public class MiddleLevel{
public event EventHandler<string> Message;
public BottomLevel bottom;
public MiddleLevel()
{
bottom.Message += (s, e) => { Message(s, e) };
}
}
public class BootomLevel{
public event EventHandler<string> Message;
public void DoSomething()
{
Message?.Invoke(this, "I did it.");
}
}
public class Handler{
public void HandleEvent(TopLevel top)
{
top.Message += PrintMessage;
}
public void PrintMessage(object sender, string message)
{
Console.WrteLine(message);
}
}
Also tried changing constructor to lambda expressions like this:
public class TopLevel{
public event EventHandler<string> Message;
public MiddleLevel mid;
public TopLevel()
{
mid.Message += (s, e) => { Message?.Invoke(s, e); };
}
}
public class MiddleLevel{
public event EventHandler<string> Message;
public BottomLevel bottom;
public MiddleLevel()
{
bottom.Message += (s, e) => { Message?.Invoke(s, e); };
}
}
public class BootomLevel{
public event EventHandler<string> Message;
public void DoSomething()
{
Message?.Invoke(this, "I did it.");
}
}
public class Handler{
public void HandleEvent(TopLevel top)
{
top.Message += PrintMessage;
}
public void PrintMessage(object sender, string message)
{
Console.WrteLine(message);
}
}
Codes above doesn't print any message. Even if I handle event in MiddleLevel class, I still get no message. I assume it is because message call is made in constructor (Even though linq quarries update themselves)? If I handle event in Handle class straight from BottomLevel class, it obviously - works. But I need to bubble the message up, I can't think of any other way to to this, because of how classes are constructed. Is it even possible to do what I have in mind with a standard Eventhandler class? If so than how? Should I just make an event class myself as in one of the sites i refered?
I refered to these sites:
What is the preferred way to bubble events?
https://www.carlosble.com/2016/04/event-bubbling-in-c/

Updated answer:
If you want 'Handler' to be triggered you will have to make sure that 'BottomLevel' falls within the hierarchy of the 'TopLevel' class being passed to the handler, this can be done via dependency injection (DI).
If 'BottomLevel' instantiates it's own classes (no DI) then it will not know about 'Handler', so handler will never be triggered.
If you comment out the DI setup and un-comment the 'BottomLevel' instantiation you can see the different behaviors.
class Program
{
static void Main(string[] args)
{
//setup the classes (dependency injection)
TopLevel topLevel = new TopLevel();
MiddleLevel middleLevel = new MiddleLevel(topLevel);
BottomLevel bottomLevel = new BottomLevel(middleLevel);
//set up the handler
Handler h = new Handler(topLevel);
//using this will not link to 'Handler' as there is no relation between this bottom and top
//BottomLevel bottomLevel = new BottomLevel();
//trigger the bottom class
bottomLevel.TriggerBottom();
//or
bottomLevel.DoSomething(null, "call from main");
Console.ReadLine();
}
}
public class Handler
{
TopLevel _topLevel;
public Handler(TopLevel topLevel)
{
if (topLevel != null)
_topLevel = topLevel;
_topLevel.Message += _topLevel_Message;
}
private void _topLevel_Message(object sender, string e)
{
Console.WriteLine($"handler triggered : {e}");
}
}
public class TopLevel
{
public event EventHandler<string> Message;
public TopLevel()
{ }
public void TriggerTop()
{
Message?.Invoke(this, "origin top");
}
public void DoSomething(object sender, string e)
{
Console.WriteLine($"Do something at top : {e}");
Message?.Invoke(this, e);
}
}
public class MiddleLevel
{
TopLevel _TopLevel;
public event EventHandler<string> Message;
public MiddleLevel(TopLevel topLevel) : this()
{
_TopLevel = topLevel;
}
public MiddleLevel()
{
if (_TopLevel == null)
_TopLevel = new TopLevel();
//subscribe this message to bottom message event method
Message += (s, e) => { _TopLevel.DoSomething(s, e); };
}
public void TriggerMiddle()
{
Message?.Invoke(this, "origin middle");
}
public void DoSomething(object sender, string e)
{
Console.WriteLine($"do something in middle : {e}");
//invoke the event(s)
Message?.Invoke(sender, e);
}
}
public class BottomLevel
{
MiddleLevel _MidLevel;
public event EventHandler<string> Message;
public BottomLevel(MiddleLevel midLevel) : this()
{
_MidLevel = midLevel;
}
public BottomLevel()
{
if (_MidLevel == null)
_MidLevel = new MiddleLevel();
////here you assign it
Message += (s, e) => { _MidLevel.DoSomething(s, e); };
}
public void TriggerBottom()
{
DoSomething(this, "origin bottom");
}
public void DoSomething(object sender, string e)
{
Console.WriteLine($"do something at bottom : {e}");
Message?.Invoke(sender, e);
}
}

Related

Update a form in parallel with an installation script?

I currently have an installation "framework" that does specific things. What I need now to do is be able to call my form in parallel with my script. Something like this:
InstallationForm f = new InstallationForm();
Application.Run(f);
InstallSoftware(f);
private static void InstallSoftware(InstallationForm f) {
f.WriteToTextbox("Starting installation...");
Utils.Execute(#"C:\temp\setup.msi", #"-s C:\temp\instructions.xml");
...
f.WriteToTextbox("Installation finished");
The current way I can do this is by adding the Form.Shown handler in InstallSoftware, but that seems really messy. Is there anyway I can do this better?
Your code will not work, because Application.Run(f) returns not until the form was closed.
You may use a simplified Model/View/Controller pattern. Create an InstallationFormController class that has several events, e.g. for textual notifications to be written to your textbox. The InstallationForm registers on these events in it's OnLoad() method and then calls InstallationFormController.Initialize(). That method starts your installation (on a worker thread/task). That installation callback method fires several text events.
InstallationForm f = new InstallationForm(new InstallationFormController());
Application.Run(f);
internal class InstallationFormController
{
public event EventHandler<DataEventArgsT<string>> NotificationTextChanged;
public InstallationFormController()
{
}
public void Initialize()
{
Task.Factory.StartNew(DoInstallation);
}
private void DoInstallation()
{
...
OnNotificationTextChanged(new DataEventArgsT<string>("Installation finished"));
}
private void OnNotificationTextChanged(DataEventArgsT<string> e)
{
if(NotificationTextChanged != null)
NotificationTextChanged(this, e);
}
}
public class DataEventArgsT<T> : EventArgs
{
...
public T Data { get; set; }
}
internal class InstallationForm : Form
{
private readonly InstallationFormController _controller;
public InstallationForm()
{
InitializeComponent();
}
public InstallationForm(InstallationFormController controller) : this()
{
if(controller == null)
throw new ArgumentNullException("controller")
_controller = controller;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_controller.NotificationTextChanged += Controller_NotificationTextChanged;
_controller.Initialize();
}
protected virtual void Controller_NotificationTextChanged(object sender, DataEventArgsT<string> e)
{
if(this.InvokeRequired)
{ // call this method on UI thread!!!
var callback = new EventHandler<DataEventArgsT<string>>(Controller_NotificationTextChanged);
this.Invoke(callback, new object[] {sender, e});
}
else
{
_myTextBox.Text = e.Data;
}
}
...
}

How to avoid duplication of event subscription?

I have 3 classes namely Login, Barcode, and the Main.
Login class just contains the authentication of the users.
Barcode class has the following snippet code:
class Barcode
{
public delegate void BarcodeReadHandler(object sender, BarcodeEventArgs e);
public event BarcodeReadHandler BarcodeReadOut;
public Barcode()
{
//.. some codes for getting data on the scanner
BarcodeEventArgs args = new BarcodeEventArgs(scannedData);
BarcodeReadOut(this, args);
}
}
While in Main class, the subsciption of the Barcode event is done:
public partial class Main : Form
{
private Barcode barcode = null;
public Main()
{
barcode.BarcodeReadOut += new barcode.BarcodeReadHandler(getBarcodeStr);
}
//This is called before log-out.
public void removeInstance()
{
barcode.BarcodeReadOut -= new barcode.BarcodeReadHandler(getBarcodeStr);
}
private void getBarcodeStr(object sender, BarcodeEventArgs e)
{
//some code
}
}
The duplication of event subscription happens when I try to logout and login again.
When I tried to debug, BarcodeReadOut is called twice.
In logout, the removeInstance() is called and the Main form is Close() and Dispose() before opening the login screen.
Can someone help me on how can I avoid the duplication of the said events?
I also have done this before registering the event but nothing happens:
public Main()
{
barcode.BarcodeReadOut -= new barcode.BarcodeReadHandler(getBarcodeStr);
barcode.BarcodeReadOut += new barcode.BarcodeReadHandler(getBarcodeStr);
}
You should add and remove the handler as follows:
public partial class Main : Form
{
private Barcode barcode = null;
public Main()
{
barcode.BarcodeReadOut += getBarcodeStr;
}
//This is called before log-out.
public void removeInstance()
{
barcode.BarcodeReadOut -= getBarcodeStr;
}
private void getBarcodeStr(object sender, BarcodeEventArgs e)
{
//some code
}
}
Also: You don't need to define a custom delegate, you can use the generic EventHandler:
public event EventHandler<BarcodeEventArgs> BarcodeReadOut;
It would be good to move all your logic that works with Barcode to a separate class. And it might be good to add a custom event that notifies other classes (a Form class in your case) that event has occurred :
class Barcode
{
public delegate void BarcodeReadHandler(object sender, BarcodeEventArgs e);
public event BarcodeReadHandler BarcodeReadOut;
public Barcode()
{
//.. some codes for getting data on the scanner
BarcodeEventArgs args = new BarcodeEventArgs(scannedData);
BarcodeReadOut(this, args);
}
}
class BarcodeWorker
{
private Barcode barcode = null;
private BarcodeReadHandler handler;
public event BarcodeEventArgs scanComplete;
BarcodeWorker(Barcode barcode)
{
if(barcode == null) this.barcode = barcode;
}
public AddEventHandler()
{
if(handler != null) return;
handler = new BarcodeReadHandler(getBarcodeStr);
barcode.BarcodeReadOut += handler;
}
//This is called before log-out.
public void RemoveEventHandler()
{
barcode.BarcodeReadOut -= handler;
handler = null;
}
private void getBarcodeStr(object sender, BarcodeEventArgs e)
{
scanComplete(sender, e);
}
}
And use it like this:
BarcodeWorker barcode = new BarcodeWorker();
barcode.scanComplete += // your delegate with event handler or with anonymous method here;

How to make an event which gets triggered in another class

In my code for the PluginManager the event PluginEvent gets triggered after
a plugin has been added. But I want to get the event also triggered in the test class.
Somehow I cant solve this problem. The event only gets triggered in the PluginManager class. I read some articles how to create events and so on, but I got even more confused
PluginManager class
public class PluginEventArgs
{
public PluginEventArgs(string s) { Text = s; }
public String Text { get; private set; } // readonly
}
public class PluginManager
{
// Declare the delegate (if using non-generic pattern).
public delegate void PluginEventHandler(object sender, PluginEventArgs e);
// Declare the event.
public event PluginEventHandler PluginEvent;
protected virtual void RaiseSampleEvent(string message)
{
if (PluginEvent != null)
PluginEvent(this, new PluginEventArgs(message));
}
public PluginManager()
{
PluginEvent += PluginManager_PluginEvent;
SomeMethod();
}
void PluginManager_PluginEvent(object sender, PluginEventArgs e)
{
//This event gets triggered =)
}
public void SomeMethod()
{
//Code
RaiseSampleEvent("Name of the Plugin");
//Code
}
}
My test class:
class test
{
public test()
{
PluginManager pluginMg = new PluginManager();
pluginMg.PluginEvent += pluginMg_PluginEvent;
}
//I want this event to get triggered when a new plugin has been found
void pluginMg_PluginEvent(object sender, PluginEventArgs e)
{
MessageBox.Show(e.Text);
}
}
How can I manage to get the event triggered in the test class?
Thanks for any advise!
You're actually doing things right except for one logical Mistake.
In your test class you're creating the PluginManager by using the constructor. The constructor of PluginManager first subscribes to the event and then raises it.
AFTERWARDS you're subscribing to that event.
The simple Problem is that when you are raising the event your test class has not subscribed yet. When you raise that event again everything should work out just fine.
Another thing is that I would use the generic EventHandler class instead of creating your own delegates. This keeps your code cleaner and everyone knows that this is meant to be an event at first glance.
Just inherit PlugInEventArgs from EventArgs and then use EventHandler.
In your PluginManager class you shouldn't subscribe to your own event PluginEvent, you should subscribe to an external event or just raise the PluginEvent.
Let me give you an example:
public class PluginEventArgs
{
public PluginEventArgs(string s) { Text = s; }
public String Text { get; private set; } // readonly
}
public class OtherClass
{
public event PluginEventHandler PluginEvent;
private void RaiseEvent()
{
if (null != PluginEvent)
PluginEvent(this, new PluginEventArgs("some message"));
}
}
public delegate void PluginEventHandler(object sender, PluginEventArgs e);
public class PluginManager
{
public event PluginEventHandler PluginEvent;
private OtherClass otherClass;
protected virtual void RaiseSampleEvent(string message)
{
if (PluginEvent != null)
PluginEvent(this, new PluginEventArgs(message));
}
public PluginManager(OtherClass otherClass)
{
this.otherClass = otherClass;
this.otherClass.PluginEvent += otherClass_PluginEvent;
SomeMethod();
}
void otherClass_PluginEvent(object sender, PluginEventArgs e)
{
if (PluginEvent != null)
PluginEvent(sender, e); // this way the original sender and args are transferred.
}
public void SomeMethod()
{
//Code
RaiseSampleEvent("Name of the Plugin");
//Code
}
}
class test
{
public test()
{
OtherClass otherClass = new OtherClass();
PluginManager pluginMg = new PluginManager(otherClass);
pluginMg.PluginEvent += pluginMg_PluginEvent;
}
//I want this event to get triggered when a new plugin has been found
void pluginMg_PluginEvent(object sender, PluginEventArgs e)
{
MessageBox.Show(e.Text);
}
}

Passing additional parameters to event Action;

Definittion of ProgressChanged:
// Summary:
// Event called whenever the progress of the upload changes.
public event Action<IUploadProgress> ProgressChanged;
public void insertFile(String filePath)
{
//.. some code
insertRequest.ProgressChanged += Upload_ProgressChanged;
}
public void Upload_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
//.. I need filePath from insertFile() here!
}
How to pass additional paramtres to Upload_ProgressChanged ?
I did the following:
public void insertFile(String filePath)
{
//.. some code
ProgressChangedEventArgs args = new ProgressChangedEventArgs()
{
path = filePath
};
insertRequest.ProgressChanged += Upload_ProgressChanged;
}
static void Upload_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
public class ProgressChangedEventArgs : EventArgs
{
public string path { get; set; }
}
And I have mistake Can not implicitly convert type 'void' to 'System.Action<Google.Apis.Upload.IUploadProgress>'
Instead of using an event you can capture the variable inside a closure
insertRequest.ProgressChanges += progress => { /* Do something with filePath here */ };
Firstly, define the EventArgs class - this will let you have whatever information you like...
public class ProgressChgEventArgs : System.EventArgs
{
public string Name { get;set; }
public int InstanceId { get;set; }
public ProgressChgEventArgs(string name, int id)
{
Name = name;
InstanceId = id;
}
}
Next, create the event that consumes these arguments:
public event EventHandler<ProgressChgEventArgs> ProgressChanged;
Then, have an 'On....' method that invokes the handlers
public void OnProgressChanged(ProgressChgEventArgs e)
{
var handler = new EventHandler<ProgressChgEventArgs>();
if (handler != null)
handler(this, e);
}
Now, at the relevant point in your code (presumably when the progress changes!) you call OnProgressChanged(), passing in an appropriate instance of the ProgressChgEventArgs:
private void Progress(string caller, int callerId)
{
var arguments = new ProgressChgEventArgs(caller, callerId);
OnProgressChanged(arguments);
}

Using Event to apply TellDontAsk pattern

I have tried to raise event in cSharp to notify code change in my application in order to have tellDontAsk scenario.
I have simple class that implement from event class
public class SimpleTellDontAsk : ISomeEvent
{
public void doSomething(string text, EventHandlerArgs args)
{
Console.WriteLine("Email this message {0}", text);
//sending message with email
args.IsDo = true;
RaiseEvent(this, args);
}
public event EventHandler RaiseEvent;
}
I define my event class like below
public interface ISomeEvent
{
event EventHandler RaiseEvent;
}
public class SomeEvent : ISomeEvent
{
public event EventHandler RaiseEvent;
}
public class EventHandlerArgs : EventArgs
{
public bool IsDo { get; set; }
}
I try to use my code with Nunit test
[TestFixture]
public class TestSimpleTellDontAsk
{
[Test]
public void Given_Text_When_doSomething_Then_ShouldPubliushArgs()
{
var tellDontAsk = new SimpleTellDontAsk();
var senderEventHandlerArgs = new EventHandlerArgs();
tellDontAsk.doSomething("test message", senderEventHandlerArgs);
Assert.IsTrue(senderEventHandlerArgs.IsDo);
}
}
When I run the test it comes up with null reference exception
System.NullReferenceException : Object reference not set to an instance of an object.
I believe something is missing but could not figure out , any idea ?
RaiseEvent isn't attached
bool eventFired = false;
var tellDontAsk = new SimpleTellDontAsk();
tellDontAsk.RaiseEvent += (o, e) =>
{
if (e.IsDo)
eventFired = true;
};
tellDontAsk.doSomething("test message");
Assert.IsTrue(eventFired);
Also if you want to use your own EventArgs "EventHandlerArgs" you should go for the Generic EventHandler.
The eventargs shouldnt be provided in the parameters to the method, they should be created when we need to raise the event.
public class SimpleTellDontAsk : ISomeEvent
{
public void doSomething(string text)
{
Console.WriteLine("Email this message {0}", text);
//sending message with email
if (RaiseEvent != null) //Check if we have listeners
{
EventHandlerArgs args = new EventHandlerArgs();
args.IsDo = true;
RaiseEvent(this, args);
}
}
public event EventHandler<EventHandlerArgs> RaiseEvent;
}
RaiseEvent does not have handler attached , modified the code for attaching default handler , i am still not clear how it is implementing tell don't ask principle , you are just checking the IsDo property , what is the use of ISomeEvent?
public interface ISomeEvent
{
event Action<ISomeEvent, EventHandlerArgs> RaiseEvent;
}
public class SomeEvent : ISomeEvent
{
public event Action<ISomeEvent, EventHandlerArgs> RaiseEvent;
}
public class EventHandlerArgs : EventArgs
{
public bool IsDo { get; set; }
}
public class SimpleTellDontAsk : ISomeEvent
{
public SimpleTellDontAsk()
{
RaiseEvent = (s,e) => { };
}
public void doSomething(string text, EventHandlerArgs args)
{
Console.WriteLine("Email this message {0}", text);
//sending message with email
args.IsDo = true;
RaiseEvent(this, args);
}
public event Action<ISomeEvent, EventHandlerArgs> RaiseEvent;
}

Categories

Resources