I have a class that has an event that's suppose to fire everytime one of it's property changes.
public event EventHandler StructureChanged;
protected virtual void NotifyStructureChanged(EventArgs e)
{
if (StructureChanged != null)
{
StructureChanged(this, e);
}
}
I include NotifyStructureChanged(new EventArgs()); in my set statement in my properties.
whenever it calls the method the StructureChangedis always null. My class is a private member in a custom usercontrol and the class event is registered in the constructor of the usercontrol like so
_pt.StructureChanged += _pt_StructureChanged;
and handled here
void _pt_StructureChanged(object sender, EventArgs e)
{
UpdateControl();
}
What I have so far is a custom class with an event that's a private member of a custom user control. I register my class event in the custom usercontrol. Whenever the class property changes, I update my control to reflect the changes in the class.
What am I doing wrong here? I have a button on my usercontrol and am able to register that event, why can't I register my class event?
If StructureChanged is null than you attach event handler after event was fired (or you are detaching handler somewhere).
Also don't pass EventArgs - its just useless dummy parameter.
public event EventHandler StructureChanged;
protected virtual void OnStructureChanged()
{
if (StructureChanged != null)
StructureChanged(this, EventArgs.Empty);
}
And call this method in setter:
public Foo Bar
{
get { return _bar; }
set {
if (_bar == value)
return;
_bar = value;
OnStructureChanged();
}
}
Related
Here are 3 C# classes :
class StartClass
{
event StartEvent;
// some code
class MidClass
{
private StartClass _startClass;
public MidClass (StartClass startClass)
{
_startClass = startClass;
}
// some code
class EndClass
{
private MidClass _midClass;
public EndClass (MidClass midClass)
{
_midClass = midClass;
}
// some code
public void OnStartEvent ()
{
// code to be executed on StartEvent called
}
What's best way if you want to attach EndClass.OnStartEvent listener to StartClass.StartEvent event ?
I think the best way would be to create a MidClass.StartEvent property referencing StartClass.StartEvent event in order to be able to attach the listener in EndClass doing _midClass.StartEvent =+ OnStartEvent;. Am I right ? How to attach StartClass.StartEvent to MidClass.StartEvent ?
In C# an event is much like a property. It is a wrapper for a delegate. Usually we are using auto-implemented events. But we can expand them. Properties have get and set accessors. Events have add and remove accessors.
class MidClass
{
private readonly StartClass _startClass;
public MidClass (StartClass startClass)
{
_startClass = startClass;
}
public event EventHandler StartEvent
{
add => _startClass.StartEvent += value;
remove => _startClass.StartEvent -= value;
}
}
Here, we create a StartEvent in MidClass that is a wrapper for the corresponding event in StartClass. StartClass.StartEvent must be public.
The advantage of this approach is that an event handler subscribing to MidClass.StartEvent will directly be attached to StartClass.StartEvent, with no intermediate call occurring when the event is risen.
One word to naming. Methods named OnEventName are usually used to raise events, while methods named PublisherName_EventName are used for event handlers.
class StartClass
{
public event EventHandler StartEvent;
private virtual void OnStartEvent()
{
StartEvent?Invoke(this, EventArgs.Empty);
}
}
class EndClass
{
private MidClass _midClass;
public EndClass (MidClass midClass)
{
_midClass = midClass;
_midClass.StartEvent += MidClass_StartEvent;
}
private void MidClass_StartEvent (object sender, EventArgs e)
{
// Code to be executed when StartEvent is triggered
}
}
I think the best way would be to create a MidClass.StartEvent property referencing StartClass.StartEvent event in order to be able to attach the listener in EndClass doing _midClass.StartEvent =+ OnStartEvent;. Am I right?
If you want to keep the reference to StartClass a private implementation detail of MidClass: Yes, implementing a "proxy" StartEvent in MidClass is the right way to do it.
How to attach StartClass.StartEvent to MidClass.StartEvent ?
By attaching a listener to _startClass.StartEvent which just raises the corresponding event in MidClass:
class MidClass
{
public event EventHandler StartEvent;
private readonly StartClass _startClass;
public MidClass(StartClass startClass)
{
_startClass = startClass;
_startClass.StartEvent += (sender, e) => this.StartEvent?.Invoke(this, e);
}
...
}
Note that I also added the readonly modifier to _startClass: If the value of _startClass changes during the lifetime of MidClass, you need to detach your event handler from the old reference and attach it to the new reference.
I wanted to ask if this is Event possible in C#. I have not much worked with Events till now.
Say I have a class A which subscribed to a FormClosing Event of a form:
public class A
{
private void f_FormClosed(object sender, FormClosedEventArgs e)
{
//Now here a public Event should be called
}
}
Now there I want a public Event to be called. Let's say now I have another class B which has a certain method.
public class B
{
public void DoSomething()
{
}
}
Now what I want to do:
A Form gets closed so class A is getting notified. There, a public Event gets triggered (which is somewhere in a public class). I want to subscribe my method in class B to this Event so it gets called when that happens. Is this possible? And how is the syntax? I haven't found something useful till now.
Edit: I can't create an instance of class B directly from class A.
Its possible .
Create a new event in A.
Raise the event within the eventhandler f_FormClosed
Subscribe to this event in B.
Within the eventhandler in B call the method DoSomething
For the syntax part you could check MSDN
// A delegate type for hooking up change notifications.
public delegate void ChangedEventHandler(object sender, EventArgs e);
// A class that works just like ArrayList, but sends event
// notifications whenever the list changes.
public class ListWithChangedEvent: ArrayList
{
// An event that clients can use to be notified whenever the
// elements of the list change.
public event ChangedEventHandler Changed;
// Invoke the Changed event; called whenever list changes
protected virtual void OnChanged(EventArgs e)
{
if (Changed != null)
//you raise the event here.
Changed(this, e);
}
}
Now in your other class do something like this
class EventListener
{
private ListWithChangedEvent List;
public EventListener(ListWithChangedEvent list)
{
List = list;
// Add "ListChanged" to the Changed event on "List".
//This is how we subscribe to the event created in ListWithChangedEvent class
List.Changed += new ChangedEventHandler(ListChanged);
}
// This will be called whenever the list changes.
private void ListChanged(object sender, EventArgs e)
{
Console.WriteLine("This is called when the event fires.");
}
}
Issue
I have created an event in the class LoginVM which looks like the following:
public class LoginVM : INotifyPropertyChanged
{
public event EventHandler<string> PasswordSet;
}
Also in this class I have a piece of code which fires this event:
public class LoginVM : INotifyPropertyChanged
{
public event EventHandler<string> PasswordSet;
private void PopulateLatestServer()
{
try
{
string SERVER_ID = Registry.GetValue(#"HKEY_CURRENT_USER\SOFTWARE\PODIA", "LATESTSERVER", null).ToString();
BDO_SERVERS latestserver = SavedServers.Where(a => a.Server_ID == SERVER_ID).FirstOrDefault();
setServerURL(latestserver.ServerURL, false);
Username = latestserver.Username;
PasswordSet(this, latestserver.Password);
}
catch (Exception)
{
Global.WriteLog("Could not find last logged in server.", EventLogEntryType.Warning);
}
}
}
I have another class which is called LoginV and in there I create an instance of the class and subscribe to the event:
public partial class LoginV : MetroWindow
{
public LoginV()
{
InitializeComponent();
LoginVM _loginVM = new LoginVM();
this.DataContext = _loginVM;
_loginVM.PasswordSet += new EventHandler<string> (_loginVM_PasswordSet);
}
private void _loginVM_PasswordSet(object sender, string e)
{
passwordBox.Password = e;
}
As you can probably tell I am trying to trigger an event from the ViewModel to the View but every time I trigger the event from the ViewModel, PasswordSet is null and errors.
An event is null when there's no listener to the event.
private void RaisePasswordSet(String pass) {
YourEventArgs args = new YourEventArgs(pass);
if(PasswordSet != null) PasswordSet(this, args);
}
Your issue is that when you try to raise the event no one listen to it yet.
It's a good idea to initialize the password in the constructor for LoginVM as you did. That's when initialization ought to happen. Ordinarily, you'd set a property and the binding in the XAML would take care of updating the control. No need for an event on the VM. But this is a password box, so you can't bind it, and the event you wrote is The Right Thing.
But in your implementation, that leaves you with this sequence of events:
Create VM
VM raises PasswordSet in its constructor -- without checking to see if there are any handlers.
View assigns VM to DataContext
View adds handler to PasswordSet event
And you get an exception at step 2, because you didn't check for handlers.
Here's what you do.
In the VM or anywhere, always use this pattern for raising events:
C# <= 5:
protected void OnPasswordSet(String e)
{
var handler = PasswordSet;
if (handler != null)
{
handler(this, e);
}
}
C#6
protected void OnPasswordSet(String e) => PasswordSet?.Invoke(this, e);
Either:
private void PopulateLatestServer()
{
try
{
string SERVER_ID = Registry.GetValue(#"HKEY_CURRENT_USER\SOFTWARE\PODIA", "LATESTSERVER", null).ToString();
BDO_SERVERS latestserver = SavedServers.Where(a => a.Server_ID == SERVER_ID).FirstOrDefault();
setServerURL(latestserver.ServerURL, false);
Username = latestserver.Username;
OnPasswordSet(latestserver.Password);
}
catch (Exception)
{
Global.WriteLog("Could not find last logged in server.", EventLogEntryType.Warning);
}
}
Can't crash now. Or at least not the same way as last time.
Problem number two: How do you update the view initially?
Easy: Take whatever's in the view's PasswordSet handler, move it into a protected method, and call that in both places. This looks a little verbose since it's only a one-liner, but it's nice to have things rolled into neatly labeled units. If that code were more complicated, you'd absolutely want not to be copying and pasting it. If it gets more complicated a year from now, you won't have to waste any time re-parsing your the old code.
public partial class LoginV : MetroWindow
{
public LoginV()
{
InitializeComponent();
LoginVM _loginVM = new LoginVM();
this.DataContext = _loginVM;
_loginVM.PasswordSet += new EventHandler<string> (_loginVM_PasswordSet);
UpdatePassword();
}
protected void UpdatePassword()
{
passwordBox.Password = e;
}
private void _loginVM_PasswordSet(object sender, string e)
{
UpdatePassword();
}
Option number two: Keep OnPasswordSet() as shown above, but instead of having the view manually update the password in the constructor, have the LoginVM require a PasswordSet handler as a parameter. This isn't the way I would do it; constructor parameters like this get on my nerves. But that may just be an irrational prejudice on my part. This way makes more clear the fact that the owner needs to handle that event to use the class, and "provide a suitable event handler" becomes the only thing the consumer needs to do in order to use the thing. The less a consumer needs to know about your class's internals, the better, for obvious reasons. Platonically ideal design would be when programmers who don't think at all can make casual glib assumptions about your class, and not end up on Stack Overflow begging somebody to read the documentation to them out loud. We'll never get there, though.
public class LoginVM : INotifyPropertyChanged
{
public LoginVM(EventHandler<string> passwordSetHandler)
{
if (passwordSetHandler != null)
{
PasswordSet += passwordSetHandler;
}
PopulateLatestServer();
}
// If the consumer doesn't want to handle it right way, don't force the issue.
public LoginVM()
{
PopulateLatestServer();
}
A third option is to set up explicit add/removes for the event, and raise the event when the handler comes in:
public class LoginVM : INotifyPropertyChanged
{
private event EventHandler<string> _passwordSet;
public event EventHandler<string> PasswordSet
{
add
{
_passwordSet += value;
// ...or else save latestServer in a private field, so here you can call
// OnPasswordSet(_latestServer.Password) -- but since it's a password,
// best not to keep it hanging around.
PopulateLatestServer();
}
remove { _passwordSet -= value; }
}
I have UserControl similar to ListView. I want to create event machining delete item from ListView.
I doing so. But I do not know how to go on.
public partial class ImagesSetEditor : UserControl
{
public delegate void ImageRemovedEventHandler(object sender, ImagesSetEditor e);
public event ImageRemovedEventHandler ImageRemovedEvent;
You do not need to create a new delegate for conforming with the event based pattern. Create a simple event in your control as such:
public event EventHandler ImageRemoved;
if you need any custom arugments passed on, create a class that derives from EventArgs as such:
public class ImageRemovedEventArgs : EventArgs
{
public int Index; //for example
}
and then declare the event as such:
public event EventHandler<ImageRemovedEventArgs> ImageRemoved;
You would then fire the event as such:
if (ImageRemoved != null) ImageRemoved(this, new ImageRemovedEventArgs() { Index = yourValue });
It is important to check that ImageRemoved != null, because it will throw an exception if the event has no subscribers.
I am creating a C# WinForms user control. There will be times when the user control will need data from the form it's contained in. How do I go about having the user control tell the form containing it that it needs some data?
Thanks!
You can subscribe the form to an event raised on the UserControl.
Your archiecture dictates where and when you need to subscribe to the data event. We can't answer that without knowing a little more about how your whether you are adding the control at runtime or design time. Each case will require a little derivation. From the perspective of adding your control at runtime, you could do something similar to the following:
// Your sample control
public class MyUserControl : Control
{
public event EventHandler<EventArgs> INeedData;
public Data Data {get; set;}
private class DoSomething()
{
if(INeedData!=null) INeedData(this,null);
}
}
...
// Your Form, in the case that the control isn't already added.
var _myUserControl = new MyUserControl();
private void Form1_Load(object sender, EventArgs e)
{
_myUserControl.INeedData += new EventHandler<EventArgs>(MyUserControl_INeedData);
this.Controls.Add(myUserControl);
}
void MyUserControl_INeedData(object sender, EventArgs e)
{
_myUserControl.Data = SomeData;
}
Create a custom event in the user control and have the form hook into it. If you need custom event arguments, you can create those too.
In user control:
//Define your Custom Event argument
public class MyEventArgs : EventArgs
{
//Define some fields of your custom event argument
private int m_SomeValue = 0;
//Define some properties of your custom event argument
public int SomeValue
{
get { return m_SomeValue; }
set { m_SomeValue = value; }
}
}
//Define the event handler data type
public delegate void MyEventHandler(object sender, MyEventArgs e);
//Define the object which holds the outside event handlers
public event MyEventHandler SomeEvent;
//Define the function which will generate the event
public virtual void OnSomeEvent(MyEventArgs e)
{
if (SomeEvent != null)
SomeEvent(this, e);
}
.
. //Then later in the control
.
{
//We need new data
//Create the event arguments
MyEventArgs newEvent = new MyEventArgs();
//Set the values
newEvent.SomeValue = 17;
//Call the event generating function
OnSomeEvent(newEvent);
}
In your form just use something like:
myControl.SomeEvent += new MyEventHandler(handlerName);
Since your event is public, you should see it in the Properties window of your control as well.
You can fancy up the event using Metadata attributes, but I leave it up to you to discover these.
Create an event on the user control where the event args are editable. Let the form attach a handler to that event, which updates those fields. Use those fields in the OnEvent method.
[untested]
eg.
public delegate void NeedsUserDataEventHandler(object sender, NeedsUserDataEventArgs args);
public class NeedsUserDataEventArgs : EventArgs
{
public UserData UserData { get; set; }
}
// In Control
public event NeedsUserDataEventHandler NeedsUserData;
public void OnNeedsUserData(NeedsUserDataEventArgs args)
{
NeedsUserDataEventHandler handler = NeedsUserData;
if (handler != null) handler(this, args);
// store your data somewhere here
}
// In Form
public override void OnLoad(object sender, EventArgs args)
{
control.NeedsUserData += ControlNeedsUserData;
}
public override void OnClosed(object sender, EventArgs args)
{
control.NeedsUserData -= ControlNeedsUserData;
}
public void ControlNeedsUserData (object sender, NeedsUserDataEventArgs args)
{
args.UserData = // set whatever here
}
Seems a bit vague to me, but:
Make it an event in the containing WinForm, so that every time some data is ready all the subscribers can be notified in a one-to-many model; or make it an event in the subscribed control, in a one-to-one model, in which it calls the container's method that retrieves such data?
It's dependent on when that data needs to be pushed to the UserControl. Are there events taking place on the Form that will drive the need to move data within the UserControl? If so...simply grab your instance at that point and push the data down to the UserControl via a public property.
If this is a case where events are not being used or the Form in some fashion or another "receives the data" then exposing an event on the Form such as...
public event DataHandler ReceivedData;
...and allow the UserControl or any other container to register for the event and receive the data via your custom event args. Pushing the event into the UserControl and forcing the Form to latch onto the UserControl seems backwards since the Form is the initiator of the data.