I am building server control with custom events and as example used this MSDN reference.But events are never fired while postback - clickEventDelegate is always null:
if (clickEventDelegate != null)
{
clickEventDelegate(this, e);
}
Here is how I add delegate:
if (!Page.IsPostBack)
{
.....
MYCONTROL.LeftClick+= FUNCTION;
}
Any ideas?
You did not post enough of your code to find out why your event is null. But I am gonna list the standard pattern.
This is how you define an argument class for your custom event:
public class MyEventArgs : EventArgs
{
private string MyField { get; private set; }
public MyEventArgs(string myField)
{
MyField = myField;
}
}
This is how you define a custom event:
public event EventHandler<MyEventArgs> MyEvent;
This is how you write an event raiser in the same class as the event:
protected virtual void OnMyEvent(MyEventArgs e)
{
EventHandler<MyEventArgs> myEvent = MyEvent; // for thread safety
if (myEvent != null)
{
myEvent(this, e);
}
}
This is how you raise the event in the class it is defined in (or a derived class):
OnMyEvent(new MyEventArgs("Test"));
This is how you react to the event in a derived class:
protected override void OnMyEvent(MyEventArgs e)
{
// TODO: Here you react to your event.
base.OnMyEvent(e);
}
This is how you register on the event in a foreign class:
otherClass.MyEvent += delegate(object sender, EventArgs e)
{
// TODO: Here you react to your event.
};
The other answer just explains how events work but is completely ignoring the main point of the question: Delegate is null during a postback
The answer was quite simple. Just subscribe to your event every times even during Postback, so you just had to remove your condition if (!Page.IsPostBack)
MYCONTROL.LeftClick+= FUNCTION;
I know this is a very old question and there is no way you are still searching for the answer. But when I read
You did not post enough of your code to find out why your event is
null
from the previous answer I couldn't let this wrong statement like this.
EDIT:
To add more explanations, every time the server returns the final page to be rendered in the browser it then looses the state of this particular connection. Imagine if the server should keep in memory all connections with their associated states, you have maybe thousands of users a day, and also when to "clean" this memory?
So no that won't be doable that's why they created the ViewState so the browser can send previous information to the server. However this ViewState can save values of some variables or objects but doesn't save the binding between an event and its delegate(s).
Back to concrete examples. If you do this
<asp:Button runat="server" ID="ButtonTest" OnClick="buttonTest_Click" Text="test" />
The subscription to the event is made internally by ASP.NET on a very early stage in the life cycle before you reach the page's Page_Load. You can read about the ASP.NET life cycle here
If you assign the event handler like this
<asp:Button runat="server" ID="ButtonTest" Text="test" />
and on your page's server side:
protected void Page_Load(object sender, EventArgs e)
{
ButtonTest.Click += ButtonTest_Click;
}
It will work the same except that the subscription is made a bit later.
But if instead you decorate it in a condition
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ButtonTest.Click += ButtonTest_Click;
}
}
This won't work because the event handler has not been assigned. Remember that your server forgot the state of this particular connection and has to initialize everything at each HTTP POST request (PostBack).
This is exactly like in the sample code of your question, so as you can see events are working the same on native ASP.NET controls or custom ones you declared.
Related
I am working on a UserControl for selecting special files, in this control there is a TreeView which gets populated with nodes when user selects some file. Also user can remove the files from this treeview!
I am using this control in a wizard form. In this wizard form there is a button named buttonNext and this button is disabled by default.
How can I create an event for the treeview in the usercontrol that when it gets populated it notify the next button in wizard form to get enabled and if user removes all files from that treeview it notify the button to get disabled again.
P.S: Selecting files (browser dialog and stuff like that) are all done within this usercontrol, so in my wizard form I have no access to the things that is going on in this component, but only I set the TreeView itself as public so I can read its nodes in my wizard form.
I know how to subscribe to events but never created any event myself :(
Declare events on your CustomControl:
public event EventHandler DataPopulated;
public event EventHandler DataRemoved;
Common practice is creating protected virtual methods (for possible overriding them in descendant classes), named On<EventName> which will verify that event has attached handlers and raise event, passing required arguments:
protected virtual void OnDataPopulated()
{
if (DataPopulated != null)
DataPopulated(this, EventArgs.Empty);
}
NOTE: If you need to pass some data to event handlers, then use generic EventHandler<DataPopulatedEventArgs> delegate as event type, where DataPopulatedEventArgs is a class, inherited from EventArgs.
Then just call this method just after your data was populated:
treeView.Nodes = GetNodes();
OnDataPopulated();
Then just subscribe to this event and enable your next button:
private void CustomControl_DataPopulated(object sender, EventArgs e)
{
buttonNext.Enabled = true;
}
Who is the one populating the TreeView? The one loading the data on it could enable the Next button when it has finished the loading. Am I missing something?
By the way, you create an event like this:
public event EventHandler<EventArgs> YouEventName;
And you call it like a method:
this.YourEventName(this,EventArgs.Emtpy);
Best practices say that you should create a method to call it like this:
protected virtual void OnYourEventName()
{
if (this.YourEventName != null)
{
this.YourEventName(this, EventArgs.Empty);
}
}
Check out this MSDN article for a complete tutorial on how to create and fire events.
http://msdn.microsoft.com/en-us/library/aa645739(v=vs.71).aspx
You can just propogate the event of the Treeview.
You can add this to your custom control, and it will have a SelectedNodeChanged event.
public event EventHandler SelectedNodeChanged
{
add { tree.SelectedNodeChanged += value; }
remove { tree.SelectedNodeChanged-= value; }
}
Creating a new event
public event EventHandler<EventArgs> myEvent;
You then invoke it from some method
this.myEvent(sender, e);
The actual event would look something like this:
protected void MyEvent(object sender, EventArgs e)
{
//Your code here
}
Your code can be like this:
public delegate void ChangedEventHandler(object sender, EventArgs e);
class TreeViewEx : TreeView
{
...
public event ChangedEventHandler Changed;
protected virtual void OnChanged(EventArgs e)
{
if (Changed != null)
Changed(this, e);
}
}
and it usage
TreeViewEx tree = ...
tree.Changed += new EventHandler(TreeChanged);
// This will be called whenever the tree changes:
private void TreeChanged(object sender, EventArgs e)
{
Console.WriteLine("This is called when the event fires.");
}
I just want to ensure that the code below will not be causing the ChildWindow Login in this case to never be collected by the GC. Just to clarify the sample this comes from a silverlight page that is inherited by all other pages therefore the virtual void pageloaded method.
public class MyPage : Page
{
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
_user = App.AuthenticatedUser;
if (!_user.IsValid)
{
Login loginWindow = new Login(_user);
loginWindow.Closed += new EventHandler(PageLoaded);
loginWindow.Show();
}
else
PageLoaded(this, e);
}
//to be overridden by the pages extending this page control
protected virtual void PageLoaded(object sender, EventArgs e) { }
Thanks for your help.
This is fine. The loginWindows's Close event knows of your handler, not the other way around, so the form will not have any ties preventing the GC from picking it up.
Unregistering event handlers becomes important if the object your event is defined on will persist in the application for a long time (and you do not want the event handler association to persist for the same duration).
How can I notify an outside "source" of the changes I make using a delegate.
Basically I have a form, I fill in that form and click a button that saves
my filled in data into a DB table as an XML. I want to be able to notify that the changes to the form have been made using a delegate that another "entity" can invoke.
public void Changes_Made()
{
//yay. Changes made.
}
protected void okButton_Click(object sender, EventArgs e)
{
//...
//save data
//...
Changes_Made();
}
Practical scenario is: as i save my preferences, the grid that shows my data will refresh and use the preferences set when i click the ok_button. Does this make any sense?
You can raise a event for notifying the changes.
public ctor() // Method where you want to hook the event, can be constructor or any thing else
{
//Hook to event
obj.ChangesMade += Changes_Made;
// Here obj is the object of type in which you have okButton_Click
// and ChangesMade event declaration
}
public void Changes_Made()
{
//yay. Changes made. update grid
}
//declare event
public event EventHandler ChangesMade();
protected void okButton_Click(object sender, EventArgs e)
{
//...
//save data
//...
//raise event
if(ChangesMade != null)
ChangesMade(this, new EventArgs());
}
that's what C# events are for.
If I understand you correctly, the grid, and the save button are on the same page. If that's the case, simply call PopulateGridData();, or something like that directly after you saved the changes.
I have a UserControl which contains 3 labels. I want to add an event for it, which occurs when the text of one of the labels changed.
I am using Visual Studio 2010
First, you need to declare the event within your class (alongside your methods and constructors):
public event EventHandler LabelsTextChanged;
Then you need to create a method to handle the individual labels' TextChanged events.
private void HandleLabelTextChanged(object sender, EventArgs e)
{
// we'll explain this in a minute
this.OnLabelsTextChanged(EventArgs.Empty);
}
Somewhere, probably in your control's constructor, you need to subscribe to the label's TextChanged events.
myLabel1.TextChanged += this.HandleLabelTextChanged;
myLabel2.TextChanged += this.HandleLabelTextChanged;
myLabel3.TextChanged += this.HandleLabelTextChanged;
Now for the HandleLabelsTextChanged method. We could raise LabelsTextChanged directly; however, the .NET framework design guidelines say that is it a best practice to create an OnEventName protected virtual method to raise the event for us. That way, inheriting classes can "handle" the event by overriding the OnEventName method, which turns out to have a little better performance than subscribing to the event. Even if you think you will never override the OnEventName method, it is a good idea to get in the habit of doing it anyway, as it simplifies the event raising process.
Here's our OnLabelsTextChanged:
protected virtual void OnLabelsTextChanged(EventArgs e)
{
EventHandler handler = this.LabelsTextChanged;
if (handler != null)
{
handler(this, e);
}
}
We have to check for null because an event without subscribers is null. If we attempted to raise a null event, we would get a NullReferenceException. Note that we copy the event's EventHandler to a local variable before checking it for null and raising the event. If we had instead done it like this:
if (this.LabelsTextChanged != null)
{
this.LabelsTextChanged(this, e);
}
We would have a race condition between the nullity check and the event raising. If it just so happened that the subscribers to the event unsubscribed themselves just before we raised the event but after we checked for null, an exception would be thrown. You won't normally encounter this issue, but it is best to get in the habit of writing it the safe way.
Edit: Here is how the public event EventHandler LabelsTextChanged; line should be placed:
namespace YourNamespace
{
class MyUserControl : UserControl
{
// it needs to be here:
public event EventHandler LabelsTextChanged;
...
}
}
Here are the framework design guidelines on event design for further reading.
First you should declare an event in your usercontrol for example:
public event EventHandler TextOfLabelChanged;
then you have to call the call back function that is bound to your event(if there's any) in runtime.You can do this by handling the TextChanged event of a label like this:
public void LabelTextChanged(object sender,EventArgs e)
{
if(TextOfLabelChanged!=null)
TextOfLabelChanged(sender,e);
}
You can have your own EventArgs object if you like.
somewhere in your code you should bound your label TextChanged event to this method like this:
_myLabel.TextChanged+=LabelTextChanged;
public delegate void TextChangedEventHandler(object sender, EventArgs e);
public event TextChangedEventHandler LabelTextChanged;
// ...
protected void MyTextBox_TextChanged(object sender, EventArgs e)
{
if (LabelTextChanged != null) {
LabelTextChanged(this, e);
}
}
compile error, which says: "Expected class, delegate, enum, interface, or struct" on the second line it seems to have a problem with "event...
These 2 lines need to be INSIDE the class declaration.
public delegate void TextChangedEventHandler(object sender, EventArgs e);
public event TextChangedEventHandler LabelTextChanged;
There is a very simple way to do that!
On the UserControl Form :
change properties to public to access everywhere
on the main form , where you are using UserControl:
.5: in the using region add using userControl1=UserControl.userControl1
1.Add 'Laod' event to your UserControl :
this.userControl1.Load += new System.EventHandler(this.userControl1_Load);
2.In the userControl1_Load :
private void userControl1_Load(object sender, EventArgs e)
{
(sender as UserControl1).label1.TextChanged += label1_TextChanged;
//add a 'TextChanged' event to the label1 of UserControl1
OR use direct cast:
((UserControl1) sender).label1.TextChanged += label1_TextChanged;
}
3.In th label1_TextChanged:
private void label1_TextChanged(object sender, EventArgs e)
{
//do whatever you want
}
You must be declaring the event and delegate within the Namespace. Try to bring the code within the class Scope. It will run fine.
I have a control that handles commenting. In this control, I have set a delegate event handler for sending an email.
I then have various types of controls, e.g. blog, articles etc, each of which may or may not have the commenting control added (which is done dynamically with me not knowing the id's), i.e. the commenting control is added outside this control. Each of these controls handles it's emailing differently(hence the event).
What I'm trying to determine, is how to assign the event in the parent control. At the moment, I'm having to recursively search through all the controls on the page until I find the comment control, and set it that way. Example below explains:
COMMENTING CONTROL
public delegate void Commenting_OnSendEmail();
public partial class Commenting : UserControl
{
public Commenting_OnSendEmail OnComment_SendEmail();
private void Page_Load(object sender, EventArgs e)
{
if(OnComment_SendEmail != null)
{
OnComment_SendEmail();
}
}
}
PARENT CONTROL
public partial class Blog : UserControl
{
private void Page_Load(object sender, EventArgs e)
{
Commenting comControl = (Commenting)this.FindControl<Commenting>(this);
if(comControl != null)
{
comCtrol.OnComment_SendEmail += new Commenting_OnSendMail(Blog_Comment_OnSendEmail);
}
}
}
Is there an easier way?
EDIT:
The reason I ask is that if I search from this.Page as the initial control, I am worried about time taken to search down the control tree to find it. Each different type of page would be different in how many control it would have. On some testing, it returns back quite quickly the result.
You could override the AddedControl event of your Blog class and check if the added control is instance of type Commenting. Something like:
public partial class Blog : UserControl {
protected override void AddedControl(Control control, int index) {
base.AddedControl(control, index);
Commenting commentingControl = control as Commenting;
if (commentingControl == null) return;
commentingControl.OnComment_SendEmail += new Commenting_OnSendMail(Blog_Comment_OnSendEmail);
}
}
Of course, you can put this code on a base class of all your "commentable" user controls and have an abstract method to actually handle the event.
Just one thing: the AddControl event happens AFTER the Page_Load, so be careful.
Cheers,
André