How to avoid duplication of event subscription? - c#

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;

Related

Passing events between threads run from separate class C#

I'm struggling to pass data between a thread started in a separate class from my main form. I believe (I could be wrong) that I should use an event. The problem I have is my subscribers are always null as I call the BluetoothScan class and start the thread before the event is subscribed to:
BluetoothScan bluetoothScan = new BluetoothScan(this);
bluetoothScan.BluetoothDeviceDiscovered += OnBluetoothDeviceDiscovered;
How do I subscribe to the event before starting the thread?
I have my Main Form:
using System;
using System.Windows.Forms;
//https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.invoke?view=net-5.0#System_Windows_Forms_Control_Invoke_System_Delegate_System_Object___
namespace YieldMonitor
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
}
private void BtnConnectBT_Click(object sender, EventArgs e)
{
//Start looking for the yield monitor device.
BluetoothScan bluetoothScan = new BluetoothScan(this);
bluetoothScan.BluetoothDeviceDiscovered += OnBluetoothDeviceDiscovered;
}
static void OnBluetoothDeviceDiscovered(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Message recieved from event");
}
}
}
My class that looks for bluetooth devices and if the right one is found should fire the event:
using InTheHand.Net.Sockets;
using System;
using System.Linq;
namespace YieldMonitor
{
class BluetoothScan
{
public event EventHandler BluetoothDeviceDiscovered;
public BluetoothScan(MainForm mainForm)
{
System.Diagnostics.Debug.WriteLine("Starting BluetoothScan Class");
Run();
}
public void Run()
{
System.Diagnostics.Debug.WriteLine("Running BluetoothScan Class");
string myDeviceName;
ulong myDeviceAddress;
BluetoothClient btClient = new BluetoothClient();
BluetoothDeviceInfo[] btDevices = btClient.DiscoverDevices().ToArray();
foreach (BluetoothDeviceInfo d in btDevices)
{
System.Diagnostics.Debug.WriteLine(d.DeviceName);
System.Diagnostics.Debug.WriteLine(d.DeviceAddress);
//have we found the device we are looking for?
if (d.DeviceName == "DSD TECH HC-05")
{
myDeviceName = d.DeviceName;
myDeviceAddress = d.DeviceAddress;
//Send out found adapter to the next stage
OnBluetoothScanned(EventArgs.Empty);
break;
}
}
}
protected virtual void OnBluetoothScanned(EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Running OnBlueToothScanned");
EventHandler handler = BluetoothDeviceDiscovered;
if (handler != null)// we have a subscriber to our event
{
System.Diagnostics.Debug.WriteLine("BluetoothScanned is Not empty");
handler(this, e);
}
else
{
System.Diagnostics.Debug.WriteLine("BluetoothScanned is Empty");
}
}
}
}
EDIT
I've found some nice solutions using Tasks where I need to update a label once a task is completed ie.
bool myDevicePaired = false;
var eventDevicePaired = new Progress<bool>(boDevicePaired => myDevicePaired = boDevicePaired);
await Task.Factory.StartNew(() => BluetoothPair.Run(myDeviceAddress, eventDevicePaired), TaskCreationOptions.LongRunning);
//Register the device is paired with the UI
if (myDevicePaired)
{
BtnConnectBT.Text = "Disconnect?";
}
Which is working well for Tasks that have an end that I am waiting for example waiting for a bluetooth device to connect.
But I'm beginning to pull my hair out with System.InvalidOperationException: 'Cross-thread operation not valid: Control 'tbInfo' accessed from a thread other than the thread it was created on.' error when trying to update a form text box.
Example:
in my MainForm Class:
I create what I've called an Event Reciever...
private void BluetoothSocketEventReciever(object sender, EventArgs e)
{
Debug.WriteLine("Event!!!"); //writes data to debug fine
tbInfo.AppendText("Event!!!!"); //causing error
}
I create a task to read from the device...
private void ReadDataFromDevice(UInt64 myDeviceAddress)
{
BluetoothSocket bluetoothSocket = new BluetoothSocket(myDeviceAddress);
bluetoothSocket.BluetoothDataRecieved += BluetoothSocketEventReciever;
Task.Factory.StartNew(() => bluetoothSocket.Run(), TaskCreationOptions.LongRunning);
}
In my BluetoothSocket class I have an endless while loop which will be reading data from a socket (hopefully) At the moment its just creating an empty EventArgs to trigger the Event every second:
namespace YieldMonitor
{
class BluetoothSocket
{
ulong myDeviceAddress;
public event EventHandler BluetoothDataRecieved;
public BluetoothSocket (ulong deviceAddress)
{
myDeviceAddress = deviceAddress;
}
public void Run()
{
System.Diagnostics.Debug.WriteLine("Were in BluetoothSocket ... Address: " + myDeviceAddress);
while (true)
{
Thread.Sleep(1000);
Debug.WriteLine("In BluetoothSocket - Address = " + myDeviceAddress);
OnBluetoothDataRecieved(EventArgs.Empty);
}
}
protected virtual void OnBluetoothDataRecieved(EventArgs e)
{
EventHandler handler = BluetoothDataRecieved;
if (handler != null)
{
handler(this, e);
} else
{
//No subscribers
}
}
}
}
I'm sure I'm missing something simple here but how can I pass the data from the endless loop to the text box on the main form?
EDIT
Think I've just sorted it.
private void BluetoothSocketEventReciever(object sender, EventArgs e)
{
Debug.WriteLine("Event!!!");
tbInfo.Invoke((Action)delegate
{
tbInfo.AppendText("Event!!!");
});
//tbInfo.AppendText("Event!!!!");
}
Is this the correct way to do it?
You can Pass the event handler as a parameter on the constructor
public event EventHandler BluetoothDeviceDiscovered;
public BluetoothScan(MainForm mainForm, EventHandler bluetoothDeviceDiscovered)
{
System.Diagnostics.Debug.WriteLine("Starting BluetoothScan Class");
BluetoothDeviceDiscovered += bluetoothDeviceDiscovered
Run();
}
Personally, i'm not so fun of calling method on constructor. It can be source of bugs or performance issues
Constructor
In class-based object-oriented programming, a constructor
(abbreviation: ctor) is a special type of subroutine called to create
an object. It prepares the new object for use, often accepting
arguments that the constructor uses to set required member variables.
You can pass eventhandler as parameter and call Run later

Parameter in event handler is not hitting while debugging

I have a eventhandler like this code below :
viewer.LocalReport.SubreportProcessing += new Microsoft.Reporting.WebForms.SubreportProcessingEventHandler(LocalReport_SubreportProcessing);
And this method as a parameter for event handler above :
private static void LocalReport_SubreportProcessing(object sender, SubreportProcessingEventArgs e) {
DateTime movementDate = Convert.ToDateTime(e.Parameters[0].Values[0]);
TourTransactionsController controller = new TourTransactionsController();
var movement = controller.Movements();
List<Movement> movementList = new List<Movement>();
movementList.Add(new Movement {
Destination = "TEST",
MovementDescription = "TEST",
DateTime = Convert.ToDateTime("2017-09-25")
});
e.DataSources.Clear();
e.DataSources.Add(new Microsoft.Reporting.WebForms.ReportDataSource() {
Name = "DSMovements",
Value = movementList
});
//throw new NotImplementedException();
}
Both of those method is written in a WEB API Controller. The eventhandler is hitting while debugging, but after I press F11 (step into) while debugging the LocalReport_SubreportProcessing method is not hitting. Why is LocalReport_SubreportProcessing method not hitting ?
Any help or answer is really appreciated.
Event's are not called when you register / add += them.
Events are called when the owning class invokes it.
public class EventTest
{
void SomeOperation()
{
//Do something
}
public void Run()
{
SomeOperation();
RunFinished?.Invoke(this, EventArgs.Empty); //Invoke the event, indicating that something has happened or finished
}
//The event itself
public event EventHandler RunFinished;
}
public class EventSubscriber
{
EventTest _ET = new EventTest();
public EventSubscriber()
{
_ET.RunFinished += ETRunFinished; //Register my method, called when the event occurs (is invoked)
}
public void DoSomething()
{
_ET.Run();
Console.WriteLine("Something completed.");
}
void ETRunFinished(object sender, EventArgs e)
{
Console.WriteLine("My event handler was executed.");
}
}
Your handler will be called when the event fires.
When registering += or -= unregistering (add / remove) they will NOT be invoked.
For a more detailed tutorial please refer to MSDN.

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 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);
}
}

delegates and events

I have created a very simple dummy program to understand Delegates and events. In my below program I am simple calling a method. When I call a method, five methods are automatically called with the help of delegates and events.
Kindly take a look at my program and do let me know where I am wrong or right as this is my first time using delegates and events.
using System;
namespace ConsoleApplication1
{
public delegate void MyFirstDelegate();
class Test
{
public event MyFirstDelegate myFirstDelegate;
public void Call()
{
Console.WriteLine("Welcome in Delegate world..");
if (myFirstDelegate != null)
{
myFirstDelegate();
}
}
}
class AttachedFunction
{
public void firstAttachMethod()
{
Console.WriteLine("ONE...");
}
public void SecondAttachMethod()
{
Console.WriteLine("TWO...");
}
public void thirdAttachMethod()
{
Console.WriteLine("THREE...");
}
public void fourthAttachMethod()
{
Console.WriteLine("FOUR...");
}
public void fifthAttachMethod()
{
Console.WriteLine("FIVE...");
}
}
class MyMain
{
public static void Main()
{
Test test = new Test();
AttachedFunction attachedFunction = new AttachedFunction();
test.myFirstDelegate += new MyFirstDelegate(attachedFunction.firstAttachMethod);
test.myFirstDelegate += new MyFirstDelegate(attachedFunction.SecondAttachMethod);
test.myFirstDelegate += new MyFirstDelegate(attachedFunction.thirdAttachMethod);
test.myFirstDelegate += new MyFirstDelegate(attachedFunction.fourthAttachMethod);
test.myFirstDelegate += new MyFirstDelegate(attachedFunction.fifthAttachMethod);
test.Call();
Console.ReadLine();
}
}
}
Events are implemented using Delegates. That said by convention events take the form of:
void EventHandler(Object sender, EventArgs args);
EventHandler is actually a delegate defined in .Net. EventArgs is a class in .Net that acts as a placeholder to pass additional information. If you have additional information you would create a class that derived from EventArgs and contained properties for the additional data; therefore you would create your own delegate like so:
void MyEventHandler(Object sender, MyEventArgs args);
Microsoft has a tutorial on events here and also describes defining and raising events here
This is a common pattern with dealing with events:
// define the delegate
public delegate void CustomEventHandler(object sender, CustomEventArgs e);
// define the event args
public class CustomEventArgs : EventArgs
{
public int SomeValue { get; set; }
public CustomEventArgs( int someValue )
{
this.SomeValue = someValue;
}
}
// Define the class that is raising events
public class SomeClass
{
// define the event
public event CustomEventHandler CustomEvent;
// method that raises the event - derived classes can override this
protected virtual void OnCustomEvent(CustomEventArgs e)
{
// do some stuff
// ...
// fire the event
if( CustomEvent != null )
CustomEvent(this, e);
}
public void SimulateEvent(int someValue)
{
// raise the event
CustomEventArgs args = new CustomEventArgs(someValue);
OnCustomEvent(args);
}
}
public class Main
{
public static void Main()
{
SomeClass c = new SomeClass();
c.CustomEvent += SomeMethod;
c.SimulateEvent(10); // will cause event
}
public static void SomeMethod(object sender, CustomEventArgs e)
{
Console.WriteLine(e.SomeValue);
}
}
Try putting the line
public delegate void MyFirstDelegate();
inside the Test class.
Also, use the Invoke function on the event instead, i.e.
myFirstDelegate.Invoke();

Categories

Resources