Is it possible to stop an ItemCommand or control event from bubbling up the control hierarchy? I have a control that contains child user controls that may or may not already have code that handles their ItemCommand events. What I would like to do is to allow the child control to decide to not pass the ItemCommand along to the parent control in the event it already processes the event in its code-behind.
Thanks,
Mike
In your user control class, override OnBubbleEvent(). If you return true, you will stop the "bubble up" to the parent controls.
protected override bool OnBubbleEvent(object source, EventArgs args)
{
//handled
return true;
//uncomment line below to bubble up (unhandled)
//return base.OnBubbleEvent(source, args);
}
Another somewhat neat thing to think about that I found while tinkering on this, which might be useful in some instances... you can change the command name that 'bubbles up' in the control heirachy as well. In your child user control, use OnCommand, rather than Onclick.
So, say you have a button in your user control, change the code from this:
<asp:button id="mySpecialButton"
onClick="mySpecialButton_OnClick" runat="server">
to this:
<asp:Button id="mySpecialButton"
CommandName="mySpecialCommand"
CommandArgument="myArgument"
OnCommand="mySpecialButton_Command"
runat="server"/>
then in the codebehind,
protected void mySpecialButton_Command(object sender, CommandEventArgs e)
{
RaiseBubbleEvent(this, new CommandEventArgs("Handled", e));
}
Thus, in your parent control's ItemCommand handler you will then get this new command name rather than the original command name from the child control, which you can do with as you see fit.
The Click event technically is not bubbling. That event is raised and handled by your code but the list control is also watching the Click event and raising an ItemCommand event. There's no way to prevent that because you can't even guarantee which event handler will be called first.
Do you have a CommandName associated with the button that you don't want raising that event? You should probably get rid of your Button_Click event entirely and do your command handling in the ItemCommand event, checking the event args for the CommandName and reacting appropriately.
In other words, use a CommandName that identifies what you want to happen, then in the ItemCommand event, only take action when you see a CommandName that you are responsible for handling.
Set e.Handled = true (if e is the name of your event).
Related
I have an ASP.NET WebForms page with several buttons added programmatically like this:
private void AddExportButton(Control control, Action clickAction) {
LinkButton exportButton = new LinkButton {
Text = "Export",
EnableViewState = false /*Buttons will be recreated on each Postback anyway.*/
};
exportButton.Click += (sender, e) => clickAction();
control.Controls.Add(exportButton);
}
Now this works, as long as the AddExportButton() method is called along the path from the OnLoad() or OnPreLoad() method. It does not fire the handler action however, when AddExportButton() called from the OnLoadComplete() method.
I would like to add/create the buttons also when another event handler (coming from a dropdown) gets called. This only happens after the OnLoad(), which will break my code.
Why is this, and how can I use anonymous methods as event handlers in this case?
See this nice cheat sheet about the ASP.NET Page LifeCycle by Léon Andrianarivony for more info about the order of the page/control creation.
In the page life cycle, the internal RaisePostBackEvent method (which raises the button's Click event) occurs between OnLoad and OnLoadComplete. If you wait until OnLoadComplete to add the LinkButton and hook up its Click event, then obviously the event won't be raised: it's too late.
(The fact that you're using an anonymous method is irrelevant.)
Can you add the export button in the .aspx but set its Visible property to false when you don't want it to appear?
I've a asp:TextBox and a submit button on my asp.net page. Once the button was clicked, the TextBos's value is posted back. I'm going to keep the the posted-back text value into session, so that other child controls can access to the value during their Page_Load. However, I always get NOTHING ("") in the Page_Load method, and I can read the text out in the button click handler. I know that the "button click event" happens after the Page_Load. So, I'm asking how can I "pre-fetch" the TextBox.text during Page_Load?
public partial class form_staffinfo : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e){
string s = staff_id.Text; //Reach this line first, but GET empty value. However, I want to keep it in the session during this moment.
}
protected void btn_submit_Click(object sender, EventArgs e) {
string s = staff_id.Text; //Reach this line afterward, value got.
}
}
-- EDITED --
<%# Control Language="C#" AutoEventWireup="true" CodeFile="form_staffinfo.ascx.cs" Inherits="form_staffinfo" %>
<asp:Label ID="Label1" runat="server" Text="Staff ID: "></asp:Label>
<asp:TextBox ID="staff_id" runat="server" ></asp:TextBox>
<asp:Button ID="btn_submit" runat="server" Text="Query" OnClick="btn_submit_Click" />
Since I can't get the TextBox's text in the Page_Load, so, I didn't include any code related to session for clear presentation.
Thank you!
William
None of the values of your server controls are available for consumption in the Page_Load. Those controls are assigned after the form is validated (which is after the form is loaded) and before the form's control's events fire (like button clicks, in your example). The values posted are in the Request.Form Collection. Look in the AllKeys property and you should see a key that ends in $staff_id if you use your example posted. There may be other characters in from of the key, depending upon if the control is nested in a master page or other control.
If you absolutely must have that value at page load, grab it from the Request.Form collection instead of the user control, but I would question the wisdom of capturing the value that early in the page lifecycle. You could conceivably capture the textbox's OnTextChanged Event if you needed to preserve the value in Session.
EDIT - Additional Explanation
if you were going to create a custom event for your user control, there are only a couple of steps to it.
Create a delegate. This is will be the common object for inter-control messaging.
public delegate void StaffIdChangedEvent(object sender, string staffId);
Declare an event using that delegate in the user control that is going to broadcast.
public event StaffIdChangedEvent StaffIdChanged;
In your user control, when you are ready to broadcast (say from the Staff_id textbox's OnTextChanged event), you just invoke the event [Its generally a best practice to check to see if the event is null]
this.StaffIdChangedEvent(this, "staff-id-value-here");
The final step is to wire the user control event up to an event handler (this prevents the null situation I mentioned above when trying to invoke the event). You could wire a handler into the hosting page.
this.form_staffinfo.StaffIdChangedEvent += this.some_method_on_page;
Just make sure the method on the page has the same method signature as the delegate used to declare the event.
Events also could be wired into each control that needs to know about them (look up multicast delegates), so you could do something like:
this.form_staffinfo.StaffIdChangedEvent += this.some_method_on_page;
this.form_staffinfo.StaffIdChangedEvent += this.some_control_on_the_page;
this.form_staffinfo.StaffIdChangedEvent += this.some_other_control_on_the_page;
In any event, I generally preferred to do this type of wiring in the page's OnInit method.
override protected void OnInit(EventArgs e)
{
base.OnInit(e);
InitializeComponent();
}
and just write your own InitializeComponent method to centralize any of this wiring you have to do.
There is something else that is setting the textbox value. Could you please check if you are overriding other event that occurs before Page_Load and modifying the textbox text property. Even, posting the code where you update session variable would be handy. From the code you have posted, it should work.
Do you have autoeventwireup disabled? I could be mistaken, but I think if it is disabled your Page_Load will not fire. If you want to leave it disabled, you can always override the OnLoad event...
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// do stuff
}
Inside repeater control HeaderTemplate i have some LinkButtons and Checkbox
I want to findout the object (Linkbutton or checkbox) that raises the event.
protected void Repeater1_ItemCommand(object source, RepeaterCommandEventArgs e)
{
switch(e.CommandSource)
{
case LinkButton:some work here;
case CheckBox :some work here;
}
}
When i write such code i received error as
A switch expression or case label must be a bool,
char, string, integral, enum, or corresponding nullable type
How to achieve this?
As the error messages states you could use switch with bool, char, string, integral, enum or corresponding nullable type. In your case you want to compare types. This could be achieved with an if statement:
if (e.CommandSource is LinkButton)
{
}
else if (e.CommandSource is CheckBox)
{
}
First, I pretty sure the checkbox won't ever fire the Repeater_ItemCommand event, as it is not a Button (or something that inheriets from Button) as only buttons create the ItemCommand event inside a Repeater. You can set the CheckBox's AutoPostBack property to true and handle it's OnClick event, though you'll have to be careful to be able to figure out which CheckBox fired the event b/c all the CheckBoxes will have the same event handler in your code behind files and they won't have some of the nice information inside the EventArgs that events raised by the Repeater will have.
In addition, checking the control type in the ItemCommand event handler seems both inefficent and limiting. As your code would break if there were ever mulltiple controls of the same type in the Repeater row that needed different processing. For controls that will actually raise the ItemCommand event, setting either the CommandName or the CommandArgument property of button will allow you to uniquely identify the control that raised the event, without taking the performance hit of the type check, plus it will be more maintainable.
Use this code within the ItemCommand event handler:
switch(e.CommandName)
{
case "LinkButtonCommandName1":
.......
break;
case "LinkButtonCommandName2":
.......
break;
}
I have an asp.net page with a list box on it. Multiple event handlers subscribe to its OnSelectedIndexChanged event.
When I change the SelectedIndex programmatically none of the events get fired.
Now a hack for this is to call each event handler, but this has already caused bugs since people didn't know they had to do this when adding a new event handler.
I can do this in a Winforms app and even when SelectedIndex is changed in code the events fire. Has anyone seen this before?
Take a look at the source code of the ListBox class and its base - ListControl. You will notice that the OnSelectedIndexChanged method is called from the RaisePostDataChangedEvent method. This means, that the SelectedIndexChanged event is only raised if the selected index was changed on the client side and the value stored in the ViewState does not equal data comes with the PostData. So, this event should not be raised if the SelectedIndex was changed in the server code.
As a workaround, I change data in database and complete events releated, then reload data with JavaScript.
Scenes: ASPxGridView control dtgRepair delete row, at the same time, ASPxComboBox value changed programmatically.
Server:
protected void dtgRepair_CustomCallback(object sender, ASPxGridViewCustomCallbackEventArgs e)
{
// update database here...
cmbMRType.SelectedIndex = 1;
dtgRepair.JSProperties["cpCallbackAction"] = "DeleteEntry";
}
Client:
function dtgRepair_EndCallback(s,e) {
if(cmbMRType.GetSelectedIndex() == 2 && dtgRepair.cpCallbackAction == "DeleteEntry" )
window.location.reload(true);
}
I have a GridView with dynamically created image buttons that should fire command events when clicked. The event handling basically works, except for the very first time a button is clicked. Then, the postback is processed, but the event is not fired.
I have tried to debug this, and it seems to me, that the code executed before and after the first click is exactly the same as for any other clicks. (With the exception that in the first click, the event handler is not called.)
There is some peculiarity in that: The buttons which fire the event are created dynamically through databinding, i.e. databinding must be carried out twice in the page lifecycle: Once on load, in order to make the buttons exist (otherwise, events could not be handled at all), and once before rendering in order to display the new data after the events have been processed.
I have read these posts but they wouldn't match my situation:
ASP.NET LinkButton OnClick Event Is Not Working On Home Page,
LinkButton not firing on production server,
ASP.NET Click() event doesn't fire on second postback
To the details:
The GridView contains image buttons in each row. The images of the buttons are databound. The rows are generated by GridView.DataBind(). To achieve this, I have used the TemplateField with a custom ItemTemplate implementation. The ItemTemplate's InstantiateIn method creates the ImageButton and assigns it the according event handler. Further, the image's DataBinding event is assigned a handler that retrieves the appropriate image based on the respective row's data.
The GridView is placed on a UserControl. The UserControl defines the event handlers for the GridView's events. The code roughly looks as follows:
private DataTable dataTable = new DataTable();
protected SPGridView grid;
protected override void OnLoad(EventArgs e)
{
DoDataBind(); // Creates the grid. This is essential in order for postback events to work.
}
protected override void Render(HtmlTextWriter writer)
{
DoDataBind();
base.Render(writer); // Renews the grid according to the latest changes
}
void ReadButton_Command(object sender, CommandEventArgs e)
{
ImageButton button = (ImageButton)sender;
GridViewRow viewRow = (GridViewRow)button.NamingContainer;
int rowIndex = viewRow.RowIndex;
// rowIndex is used to identify the row in which the button was clicked,
// since the control.ID is equal for all rows.
// [... some code to process the event ...]
}
private void DoDataBind()
{
// [... Some code to fill the dataTable ...]
grid.AutoGenerateColumns = false;
grid.Columns.Clear();
TemplateField templateField = new TemplateField();
templateField.HeaderText = "";
templateField.ItemTemplate = new MyItemTemplate(new CommandEventHandler(ReadButton_Command));
grid.Columns.Add(templateField);
grid.DataSource = this.dataTable.DefaultView;
grid.DataBind();
}
private class MyItemTemplate : ITemplate
{
private CommandEventHandler commandEventHandler;
public MyItemTemplate(CommandEventHandler commandEventHandler)
{
this.commandEventHandler = commandEventHandler;
}
public void InstantiateIn(Control container)
{
ImageButton imageButton = new ImageButton();
imageButton.ID = "btnRead";
imageButton.Command += commandEventHandler;
imageButton.DataBinding += new EventHandler(imageButton_DataBinding);
container.Controls.Add(imageButton);
}
void imageButton_DataBinding(object sender, EventArgs e)
{
// Code to get image URL
}
}
Just to repeat: At each lifecycle, first the OnLoad is executed, which generates the Grid with the ImageButtons. Then, the events are processed. Since the buttons are there, the events usually work. Afterwards, Render is called, which generates the Grid from scratch based upon the new data. This always works, except for the very first time the user clicks on an image button, although I have asserted that the grid and image buttons are also generated when the page is sent to the user for the first time.
Hope that someone can help me understand this or tell me a better solution for my situation.
A couple problems here. Number one, there is no IsPostBack check, which means you're databinding on every load... this is bound to cause some problems, including events not firing. Second, you are calling DoDataBind() twice on every load because you're calling it in OnLoad and Render. Why?
Bind the data ONCE... and then again in reaction to events (if needed).
Other issue... don't bind events to ImageButton in the template fields. This is generally not going to work. Use the ItemCommand event and CommandName/CommandArgument values.
Finally... one last question for you... have you done a comparison (windiff or other tool) on the HTML rendered by the entire page on the first load, and then subsequent loads? Are they EXACTLY the same? Or is there a slight difference... in a control name or PostBack reference?
Well I think the event dispatching happens after page load. In this case, its going to try to run against the controls created by your first data-binding attempt. This controls will have different IDs than when they are recreated later. I'd guess ASP.NET is trying to map the incoming events to a control, not finding a control, and then thats it.
I recommend taking captures of what is in the actual post.
ASP.NET is pretty crummy when it comes to event binding and dynamically created controls. Have fun.
Since in my opinion this is a partial answer, I re-post it this way:
If I use normal Buttons instead of ImageButtons (in the exact same place, i.e. still using MyItemTemplate but instantiating Button instead of ImageButton in "InstantiateIn", it works fine.
If I assert that DoDataBind() is always executed twice before sending the content to the client, it works fine with ImageButtons.
Still puzzled, but whatever...