I need to pass data with an event. Currently, when receiving more data (via comport), the event will fire but the previous event (&data) is not handled yet, so the data gets overwritten.
How can I handle the event &data in a safe way? I have multiple events like this (15x), so I am not sure if using a queue for the data is the best way or pass data along with the event (like S.O. item 4215845).
Example (this example is with a string, but I also use arrays, bools etc):
At the 'sender' side (class1):
public event EventHandler evDiaStringMessage = delegate { };
private void evDiaStringMessageEvent()
{
evDiaStringMessage(this, new EventArgs());
}
private static string _DiaString;
public string DiaString
{
get { return _DiaString; }
set { _DiaString = value; }
}
DiaString contains the data and gets overwritten when evDiaStringMessage is fired too soon.
At the 'receiver / GUI' side (class2):
dia.evDiaStringMessage += new EventHandler(dia_evDiaStringMessage);
private delegate void dia_evDiaStringMessageCallBack(object sender, EventArgs e);
void dia_evDiaStringMessage(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new dia_evDiaStringMessageCallBack(dia_evDiaStringMessage), new object[] { sender, e});
}
else
{
frmcomm.CommTextBox("Receiver message: " + dia.DiaString + "\r\n", Color.Red);
}
}
dia.DiaString does not contain the expected data (previous data), but all events are 'received'.
Your help is very much appreciated! Even more with an example!
Edit:
I changed the code to:
At the 'sender' side (class1):
public event EventHandler<DiaStringEventArgs> evDiaStringMessage ;
public class DiaStringEventArgs : EventArgs
{
public string DiaString { get; set; }
}
private void evDiaStringMessageEvent(DiaStringEventArgs e)
{
EventHandler<DiaStringEventArgs> handler = evDiaStringMessage;
if (handler != null)
handler(this, e);
}
...
private void PrepareDataAndFireEvent()
{
DiaStringEventArgs args = new DiaStringEventArgs();
args.DiaString = ByteToString(data);
evDiaStringMessageEvent(args);
}
At the 'receiver / GUI' side (class2):
dia.evDiaStringMessage += new EventHandler<ifDiA10.DiaStringEventArgs>(dia_evDiaStringMessage);
private delegate void dia_evDiaStringMessageCallBack(object sender, ifDiA10.DiaStringEventArgs e);
void dia_evDiaStringMessage(object sender, ifDiA10.DiaStringEventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new dia_evDiaStringMessageCallBack(dia_evDiaStringMessage), new object[] { sender, e});
}
else
{
frmcomm.CommTextBox("Receiver message: " + e.DiaString + "\r\n", Color.Red);
}
}
You can store your data in a custom EventArgs class:
public class ReceivedDataEventArgs : EventArgs
{
// Add the properties you require
}
The event is defined like so:
public event EventHandler<ReceivedDataEventArgs> ReceivedData;
Your handler will take in an instance your class instead of the EventArgs object, and hence you'll have the correct data.
You should pass the value of dia.DiaString when the event is raised rather than reading it back when the event is handled.
You can do this by extending the EventArgs class and creating custom properties.
If you need an example let me know.
Related
I'm fairly new to C# and I'm trying to create a Hangman game in WinForms, I've got the game functionality working, but I'm trying to create a form where the user selects a category and then the word to guess is from the category selected.
I've got a HangEventArgs like below:
public class HangEventArgs : EventArgs
{
public Category WordCategory { get; set; }
}
and a class for the data (I'm hoping to expand it to add more features in the future).
public enum Category
{
// Categories are stores here
}
public class HangData
{
public Category WordCategory { get; protected set; }
public HangData(Category askWhat)
{
WordCategory = askWhat;
}
}
And a class where the words are stored
public static class WordsToGuess
{
public static string[] Capitals =
{
"London",
"Paris" // more words here
}; // more categories here
Finally I have my button click event for all the categories, I've created my own Button as to not use the default EventArgs.
private void bCategory_Click(object sender, HangEventArgs e)
{
MainGame mg = new MainGame(new HangData(e.WordCategory));
mg.ShowDialog();
}
I've been trying to use event handlers like so
public event EventHandler<HangEventArgs>(object sender, HangEventArgs e);
But I'm not sure the proper way to implement this into my code.
If I use
bCapitals.Click += new EventHandler(bCategory_Click);
I get a no overload matches delegate error and I'm stuck on how to fix it. Thanks for the help in advance.
Create your category button like this:
public class CategoryButton : Button
{
protected override void OnClick(EventArgs e)
{
// Just discard the `e` argument and pass your own argument.
base.OnClick(new HangEventArgs { WordCategory = Category.Cities });
}
}
Subscribe the event with:
categoryButton1.Click += CategoryButton1_Click;
Use like this
private void CategoryButton1_Click(object sender, EventArgs e)
{
if (e is HangEventArgs hangEventArgs) {
MessageBox.Show(hangEventArgs.WordCategory.ToString());
}
}
Note that the click mechanism still works as expected. You don't need to fire the event yourself.
Of course you could create your own event; however, then, it must have a different name like HangClick and you must fire it yourself.
public class CategoryButton : Button
{
public event EventHandler<HangEventArgs> HangClick;
protected virtual void OnHangClick(HangEventArgs e)
{
HangClick?.Invoke(this, e);
}
protected override void OnClick(EventArgs e)
{
OnHangClick(new HangEventArgs { WordCategory = Category.Cities });
// Optionally, if you want to preserve the standard click event behaviour:
base.OnClick(e);
}
}
Subscribe with:
categoryButton1.HangClick += CategoryButton1_HangClick;
Use like this:
private void CategoryButton1_HangClick(object sender, HangEventArgs e)
{
MessageBox.Show(e.WordCategory.ToString());
}
I need to Raise an Event from another Class - i know that this is not possible - but I need a workaround for this.
For now Im doing the following
This is the class, which have to raise the event
internal class DataTransfer
{
public delegate void EventHandler(object sender, OnReaderCommonEventArgs e);
public event EventHandler _OnSerialNumber;
public event EventHandler _OnReaderType
Task DataHandler()
{
//Recieving-Data and Stuff
_OnSerialNumber(this, new OnReaderCommonEventArgs { SerialNumber = RFIDParser.ParseSerialNumber(data) });
_OnReaderType(this, new OnReaderCommonEventArgs { ReaderType = RFIDParser.ParseReaderType(data) });
}
}
And in the Main-Class, which will be used by the user. So the user can only subscribe to the event from this class-object:
public partial class PUR_100U
{
public delegate void EventHandler(object sender, OnReaderCommonEventArgs e);
public event EventHandler OnSerialNumber;
public event EventHandler OnReaderType;
public PUR_100U(int portnumber)
{
dataTransfer = new DataTransfer(portnumber, GetIdentifierList());
dataTransfer._OnSerialNumber += dataTransfer__OnSerialNumber;
dataTransfer._OnReaderType += dataTransfer__OnReaderType;
}
void dataTransfer__OnSerialNumber(object sender, OnReaderCommonEventArgs e)
{
if (OnSerialNumber != null) { OnSerialNumber(this, new OnReaderCommonEventArgs { SerialNumber = e.SerialNumber }); }
}
void dataTransfer__OnReaderType(object sender, OnReaderCommonEventArgs e)
{
if (OnReaderType != null) { OnReaderType(this, new OnReaderCommonEventArgs { ReaderType = e.ReaderType }); }
}
}
Example of user-usage:
rfid = new PUR_100U(20);
rfid.OnSerialNumber += rfid_OnSerialNumber;
rfid.OnReaderType += rfid_OnReaderType;
Is there a better way of doing this?
I need to Raise an Event from another Class - i know that this is not possible - but I need a workaround for this.
That is rather trivial:
class Foo
{
public event EventHandler Fooed; //note, name is not OnFoo
public void FireFooed() => Fooed?.Invoke(this, new EventArgs);
}
And now, fire the event at will:
var foo = new Foo();
foo.FireFooed();
The question is, why do you want to do this? It seems like a really bad idea. Fooed should fire only and only if the preconditions inside Foo for it to fire are met; if you need Fooed to fire, then make the preconditions happen!
Firing Fooed at will if the conditions aren't met will break all other listeners, don't do that.
I got an enum:
public enum sprog{
dansk,
svensk,
norsk
}
In a method I would raise an event and use the enum to carry information:
public delegate void BrugerSprogChanged(Object sender, Sprog sprog);
class clazz
{
public event BrugerSprogChanged brugerSprogChanced;
public clazz(){}
private void comboBoxDokumentSprog_SelectedIndexChanged(object sender, EventArgs e)
{
Sprog sprog = FindSprog((string)((ComboBox)sender).SelectedItem);
dokumentSprogChanged(this, sprog); // <- here we have the problem
}
}
When the code shall raise the event I get an exception when dokumentSprogChanged(this, sprg) is called:
*"NullReferenceException was unhandled by user code
Object reference not set to an instance of an object"
"this" and "sprog" are not null.
Any suggestions?
The easy way is to drop the unem and use an integer/string instead, but then I end up with some ugly code.
Normally to call an event you have to check if it's handler is not null:
var handler = dokumentSprogChanged; // take a local reference
if (handler != null)
{
dokumentSprogChanged(this, sprog);
}
This way you can raise it safely.
EDIT
You need to register the event like this:
public event BrugerSprogChanged brugerSprogChanced;
....
brugerSprogChanced += class_brugerSprogChanced;
....
void class_brugerSprogChanced(object sender, EventArgs e)
{
// handle there
}
Try this:
class clazz
{
public event BrugerSprogChanged brugerSprogChanced;
public clazz(){}
private void comboBoxDokumentSprog_SelectedIndexChanged(object sender, EventArgs e)
{
Sprog sprog = FindSprog((string)((ComboBox)sender).SelectedItem);
if (dokumentSprogChanged != null)
{
dokumentSprogChanged(this, sprog); // <- here we have the problem
}
}
}
I am just learning about events, delegates and subscribers. I've spent the last 2 days researching and wrapping my brain around it all. I am unable to access the information being passed in my EventArgs e value. I have a saved project that wants to be opened. The state of the necessary forms are deserialized into a dictionary. A loop is hit that raises the UnpackRequest passing the key/value with it.
ProjectManager.cs file:
public delegate void EventHandler<TArgs>(object sender, TArgs args) where TArgs : EventArgs;
public event EventHandler<UnpackEventArgs> UnpackRequest;
Then farther down:
ProjectManager.cs file:
//Raise a UnpackEvent //took out virtual
protected void RaiseUnpackRequest(string key, object value)
{
if (UnpackRequest != null) //something has been added to the list?
{
UnpackEventArgs e = new UnpackEventArgs(key, value);
UnpackRequest(this, e);
}
}
Then within the open method, after dictionary gets populated with states of each form:
ProjectManager.cs file:
foreach (var pair in dictPackState) {
string _key = pair.Key;
dictUnpackedState[_key] = dictPackState[_key];
RaiseUnpackRequest(pair.Key, pair.Value); //raises the event.
}
I have a class for the Event:
public class UnpackEventArgs : EventArgs
{
private string strKey;
private object objValue;
public UnpackEventArgs(string key, object value)
{
this.strKey = key;
this.objValue = value;
}
//Public property to read the key/value ..and get them out
public string Key
{
get { return strKey; }
}
public object Value
{
get { return objValue; }
}
}
I am still working on the code to add subscribers and how the project components get re-constituted in the individual forms. But the part I'm trying to work on now is in the MainForm.cs where I handle the Unpacked Request using the arguements getting passed. My e contains the key values and the key represents where to send the value (which is the form object).
private void HandleUnpackRequest(object sender, EventArgs e)
{
//reflection happens here. turn key into object
//why can't i get e.key ???
object holder = e; //holder = VBTools.UnpackEventArgs key... value...all info
//object goToUnpack = (e.key).GetType();
//goToUnpack.unpackState(e.value);
}
I think I included all the necessary parts to get any help?! THANKS!
Change this:
private void HandleUnpackRequest(object sender, EventArgs e)
To this:
private void HandleUnpackRequest(object sender, UnpackEventArgs e)
Remember your event handler declaration:
public event EventHandler<UnpackEventArgs> UnpackRequest;
Well, i'm working in a asp.net 3.5 site.
I have set an Observer like this:
public delegate void ActionNotification();
protected Dictionary<string, List<ActionNotification>> Observers
{
get
{
Dictionary<string, List<ActionNotification>> _observers = Session["Observers"] as Dictionary<string, List<ActionNotification>>;
if (_observers == null)
{
_observers = new Dictionary<string, List<ActionNotification>>();
Observers = _observers;
}
return _observers;
}
set
{
Session["Observers"] = value;
}
}
public void Attach(string actionName, ActionNotification observer)
{
if (!Observers.ContainsKey(actionName))
{
Observers.Add(actionName, new List<ActionNotification>());
}
Observers[actionName].Add(observer);
}
public void Detach(string actionName, ActionNotification observer)
{
if (Observers.ContainsKey(actionName))
{
Observers[actionName].Remove(observer);
}
}
public void DetachAll(string actionName)
{
if (Observers.ContainsKey(actionName))
{
Observers.Remove(actionName);
}
}
public void Notify(string action)
{
if (Observers.ContainsKey(action))
{
foreach (ActionNotification o in Observers[action])
{
o.Invoke();
}
}
}
I use the observer like this:
//Esta es llamada al notify con cierto action
protected void btnNext_Click(object sender, ImageClickEventArgs e)
{
Notify("Next");
}
//Y este es el register del Listener
Attach("Next", new ActionNotification(NextButton_Click));
If before the o.Invoke(); for example i change the page title to "Hello".
And inside the "NextButton_Click" I set it to "Goodbye", after the NextButton_Click finish, the Title goes back to "Hello"...
Any idea why?
I think problem is that the "Page" in your NextButton_Click event is not the same page as the page you set the title to "Hello" on. Because you are passing around events in the session when the event is raised the object is acts on is no longer in scope. You can recreate it with the following code (which is using EventHandlers, but they are basically the same as what you have outlined in your code)
protected void Page_Load(object sender, EventArgs e)
{
this.Page.Title = "test";
//Store it in your session...seems like a weird thing to do given how your page should be stateless, so I would think about what you are
//trying to do a bit more carefully. You don't want to call an event handler such as test below from another page in your asp.net app.
Dictionary<string, EventHandler> myEvents = null;
if (Session["Invokers"] == null)
{
myEvents = new Dictionary<string, EventHandler>();
Session["Invokers"] = myEvents;
}
else
{
myEvents = Session["Invokers"] as Dictionary<string, EventHandler>;
}
//If the event handler key is not in there then add it
if (myEvents.ContainsKey("buttonClickOnPageDefault") == false)
{
//Subscribe to event (i.e. add your method to the invokation list
this.TestEvent += new EventHandler(test);
myEvents.Add("buttonClickOnPageDefault", this.TestEvent);
}
else
{
//if it does contain this key then you may already be subscribed to event, so unsubscribe in case and then resubscribe...you could
//probably do this more elegantly by looking at the vales in the GetInvokationList method on the eventHandler
//Wire up the event
this.TestEvent -= new EventHandler(test);
this.TestEvent += new EventHandler(test);
}
//Resave the dictionary.
Session["Invokers"] = myEvents;
}
void test(object o, EventArgs e)
{
this.Page.Title = "testEvent";
}
public event EventHandler TestEvent;
protected void Button1_Click(object sender, EventArgs e)
{
if (Session["Invokers"] != null)
{
Dictionary<string, EventHandler> myEvents = (Dictionary<string, EventHandler>)Session["Invokers"];
if (myEvents.ContainsKey("buttonClickOnPageDefault"))
{
EventHandler ev = myEvents["buttonClickOnPageDefault"];
ev(null, EventArgs.Empty);
}
}
}
If you put the above code in an asp.net page it will never change page title, but if you put a breakpoint in the Test method you will see it being hit. The reason is that its being hit in a different page (and that page is out of scope and may not be garbage collected as your event still has a reference to it, so this could cause a memory leak...be careful with it!). Really you probably shouldn't be using your events this way (at least not to act on a page...maybe it has some utility for domain objects). Note that the following will work (as its acting on the same page)
protected void Page_Load(object sender, EventArgs e)
{
this.Page.Title = "test";
//Store it in your session...seems like a weird thing to do given how your page should be stateless, so I would think about what you are
//trying to do a bit more carefully. You don't want to call an event handler such as test below from another page in your asp.net app.
this.TestEvent += new EventHandler(test);
Session["Invoker"] = this.TestEvent;
}
void test(object o, EventArgs e)
{
this.Page.Title = "testEvent";
}
public event EventHandler TestEvent;
protected void Button1_Click(object sender, EventArgs e)
{
if (Session["Invoker"] != null)
{
EventHandler ev = (EventHandler)Session["Invoker"];
ev(null, EventArgs.Empty);
}
}
Hope that gives you some pointers to where your problem might be.