click button from parent page to cause usercontrol's gridview change to different pageindex - asp.net

I have a button, a usercontrol which contains a gridview in a page.
How do I click the button in the page and cause its usercontrol's gridview to change to a certain pageindex?

Provide a public method in your UserControl (f.e. GotoPage(int index)). Then you can change it from your page by MyControl.GotoPage(111);.
GotoPage could be implemented as:
public void GotoPage(int index)
{
//side-note: you can call BindGrid(e.NewPageIndex) also from GridView's PageIndexChanging
BindGrid(index);
}
and BindGrid could be implemented as:
private void BindGrid(int newPageIndex)
{
// set it's DataSource
var pageIndex = newPageIndex < 0 || newPageIndex >= GridView1.PageCount ? 0 : newPageIndex;
GridView1.PageIndex = pageIndex;
// DataBind it
}

Related

RaisePostBackEvent called on wrong control

I have spent two days with another colleague investigating this. I was surprised as most solutions discussing this problem either have the wrong solution or a solution that works, I think, for the wrong reasons.
We have a custom button control that needs to raise a ServerClick event when it is pressed. Here is the summarised code:
public class MyButton : WebControl, IPostBackEventHandler
{
protected HtmlGenericControl _Button;
protected string _OnClick = "";
protected string _Name;
public event EventHandler ServerClick;
// etc...
public MyButton()
{
Width = Unit.Pixel(100);
_Button = new HtmlGenericControl("button");
Controls.Add(_Button);
}
protected override void Render(HtmlTextWriter writer)
{
_Button.Attributes.Add("id", string.IsNullOrEmpty(_Name) ? base.ID : _Name);
_Button.Attributes.Add("name", _Name);
// etc...
_OnClick = Page.ClientScript.GetPostBackEventReference(this, "");
_Button.Attributes.Add("onClick", _OnClick);
// etc...
ID = String.Empty;
Name = String.Empty;
AccessKey = String.Empty;
TabIndex = -1;
Width = Unit.Empty;
base.Render(writer);
}
protected virtual void OnServerClick()
{
if (this.ServerClick != null)
{
this.ServerClick(this, new EventArgs());
}
}
public void RaisePostBackEvent(string eventArgument)
{
this.OnServerClick();
}
}
On the browser end the code uses two of these buttons
<form>
<!-- etc ... -->
<div class="row actionBar">
<PGSC:MyButton Name="btnAccept" ID="btnAccept" LabelID="3244" TabIndex="70" runat="server" OnServerClick="AcceptClickHandler"/>
<PGSC:MyButton Name="btnClose" ID="btnClose" LabelID="349" OnClick="window.returnValue=frmMMD.hdnMmdId.value;window.close();" TabIndex="80" runat="server" />
</div>
</form>
The Problem:
The event is not raised on the accept button. Debugging reveals that RaisePostBackEvent is called but on the Close button, which does not have a ServerClick handler attached, hence nothing happens. No event handlers get called.
Notes:
The problem is not seen if there is only one MyButton on the page.
If the buttons are reordered such that the accept button is the last on the page, it starts working.
Moving the buttons outside of the form tag causes events to work as expected, and the accept buttons event handler is called correctly.
Implementing IPostBackDataHandler and calling RaisePostBackEvent() from IPostBackDataHandler::RaisePostDataChangedEvent() causes the event to be raised correctly on the accept button when inside the form tag.
Calling RegisterRequiresRaiseEvent(btnAccept) during PageLoad routes events correctly to the accept button.
The Question:
What is the correct solution from the ones that work above? Or is there another solution? We need it to work such that multiple buttons on the page can raise independent click events, without regard to their order or position on the page.
My Thoughts:
This problem seems to be discussed here: http://forums.asp.net/t/1074998.aspx?ASP+NET+RaisePostbackEvent+Issues
One is lead to believe that calling __doPostback() with the correct __EVENTTARGET should automatically route the event correctly to the button, but this is not happening in reality. It only happens if we also implement IPostBackDataHandler. Many solutions on the web seem to point to __doPostback, UniqueID etc as the culprit when actually implementing IPostBackDataHandler is what seemingly fixes the issue.
The Control implements IPostBackEventHandler but not IPostBackDataHandler. I think this is correct because the control does not need to raise any data driven events. So implementing IPostBackDataHandler to get it working seems like a hack.
Using RegisterRequiresRaiseEvent is unintuitive and besides will not work if multiple buttons on the page would like to raise events.
I wonder, how does an asp:Button do it?
I've simulated a situation.
Hope it helps.
There is the MyButton WebServerControl class:
[DefaultProperty("Text")]
[ToolboxData("<{0}:MyButton runat=server></{0}:MyButton>")]
public class MyButton : WebControl, IPostBackEventHandler
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text
{
get
{
String s = (String)ViewState["Text"];
return ((s == null) ? String.Empty : s);
}
set
{
ViewState["Text"] = value;
}
}
protected HtmlGenericControl _Button;
protected string _OnClick = "";
protected string _Name;
public event EventHandler ServerClick;
// etc...
public MyButton()
{
Width = Unit.Pixel(100);
_Button = new HtmlGenericControl("button");
Controls.Add(_Button);
}
protected override void Render(HtmlTextWriter writer)
{
_Button.Attributes.Add("id", string.IsNullOrEmpty(_Name) ? base.ID : _Name);
_Button.Attributes.Add("name", _Name);
// etc...
_OnClick = Page.ClientScript.GetPostBackEventReference(this, "");
_Button.Attributes.Add("onClick", _OnClick);
// etc...
ID = String.Empty;
//Name = String.Empty;
AccessKey = String.Empty;
TabIndex = -1;
Width = Unit.Empty;
base.Render(writer);
}
protected virtual void OnServerClick()
{
if (this.ServerClick != null)
{
this.ServerClick(this, new EventArgs());
}
}
public void RaisePostBackEvent(string eventArgument)
{
this.OnServerClick();
}
}
I then used my web server control in a project, let's say this is the default.aspx:
<div><cc1:MyButton ID="btnAccept" runat="server" TabIndex="70" OnServerClick="AcceptClickHandler" />
<cc1:MyButton ID="btnClose" Text="Close" Width="256px" LabelID="349" runat="server" TabIndex="80" /></div><div>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
</div>
And in default.aspx.cs I've implemented simply the event:
protected void AcceptClickHandler(object sender, EventArgs e)
{
Label1.Text = DateTime.Now.ToLongTimeString();
}
The AcceptClickHandler fires only when clicking on the Accept button and not
on the Close button.
Sorry if I didnt get the problem right.

Setting parent page controls visibility from Child user control page

I have a parent page Page1 which has button1. Page1 has a usercontrol uc1. uc1 has an update panel inside which a grid grid1 is present. I am trying to set Page1.button1's visibility to false, depending on the row command event(there are some if conditions in the row command event) of uc1.grid1. I am setting Page1.button1's visibility in the following way:
Create a IsButton1Visible property in uc1. Set the property in UC1.Grid1.RowCommand to false, on page1 PreRender event, access IsButton1Visible and set Page1.button1 visibility.
Even though in quick watch Page1.button1 visibility is set to false at the line of assignment, when I see the UI, it is still visible. I don't know what I am doing wrong. Or the way that I am getting hold of button1 and its visibility is not correct.
In general can we set a Parent page's control's property from a user control during the user control event?
If you use the event-driven model approach
Delegate/EventArgs code:
public class ButtonVisiblityEventArgs : EventArgs
{
public ButtonVisiblityEventArgs(bool visible)
{
this.Visiblity = visible;
}
public bool Visiblity { get; private set; }
}
public delegate void UpdateParentButtonVisibilityEventHandler(object sender, ButtonVisiblityEventArgs args);
User control code:
public event UpdateParentButtonVisibilityEventHandler RaiseUpdateParentButtonVisibilityEvent;
private void RequestParentButtonVisibilityChange(bool setVisible)
{
if (RaiseUpdateParentButtonVisibilityEvent != null)
{
RaiseUpdateParentButtonVisibilityEvent(this, new ButtonVisiblityEventArgs(setVisible));
}
}
And in your command handler, just call:
RequestParentButtonVisibilityChange(false);
whenever you want to hide the button. On your page:
protected void Page_Load(object sender, EventArgs e)
{
this.RaiseUpdateParentButtonVisibilityEvent += new UpdateParentButtonVisibilityEventHandler(uc_RaiseUpdatecurrentDisplayPanelRequestEvent);
}
private void uc_RaiseUpdatecurrentDisplayPanelRequestEvent(object sender, ButtonVisiblityEventArgs args)
{
button1.Visible = args.Visiblity;
}
If the problem you are having is that your button lives outside of the update panel, you can do the following. Page codebhind:
protected void Page_Load(object sender, EventArgs e)
{
string hideScript = string.Format("function updateButtonVisibility( visibility ) {{ var button = $('#{0}'); if (visibility) {{ button.show(); }} else {{ button.hide(); }} }}", this.button1.ClientID);
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "updateButtonVisibility", hideScript, true);
}
And in your user control command handler:
bool shouldButtonBeVisible = false; //update this appropriately in your logic
ScriptManager.RegisterStartupScript(this, this.GetType(), "upUpdateButtonVisibility", "updateButtonVisibility(" + shouldButtonBeVisible ? "true" : "false" + ");", true);
Please note that this creates a TIGHT dependency between your UC and the page. It requires that any page that consumes this control has registered this script. There are ways to get around this (such as setting a function script callback to call, detecting if that javascript function exists, etc), but this should at least get you moving.
If there is something specific on the page after your update panel finishes that you could key off, it might be better to register an end request handler
$(function() { Sys.WebForms.PageRequestManager.getInstance().add_endRequest(updatePanelEndRequestHandler); } );
function updatePanelEndRequestHandler() {
var shouldBeVisible = $('.MyClassThatSaysIShouldntAllowMoreButtons').length > 0; //do some checking on the grid
updateButtonVisibility(shouldBeVisible);
}
you can put your user controls inside panels on your parent pages and change the visibility.
e.g.
<asp:Panel runat="server" ID="pnlQuote">
...
</asp:Panel>
<asp:Panel runat="server" ID="pnlContact">
<uc1:ContactForm runat="server" ID="ContactForm " />
</asp:Panel>
From the child control you can make a button click event which does something like this
protected void btnBackToQuote_Click(object sender, EventArgs e)
{
Panel pnlQuote = this.Parent.FindControl("pnlQuote") as Panel;
Panel pnlContact = this.Parent.FindControl("pnlContact") as Panel;
pnlQuote .Visible = true;
pnlContact.Visible = false;
}

Can't change modes with dynamic FormView templates

I have an ASP.NET page with a FormView data-bound to an ObjectDataSource that uses dynamically generated templates to render the UI based on layout information from the application's database. I've been able to get the templates to render correctly and everything seems fine until I click one of the buttons to change modes - nothing changes.
My code is based on the explanations provided in the following articles/posts:
http://www.codeproject.com/KB/aspnet/DynamicFormview.aspx
http://msdn.microsoft.com/en-us/library/ms227423.aspx
http://msdn.microsoft.com/en-us/library/y0h809ak(vs.71).aspx
http://forums.asp.net/p/1379371/2911424.aspx#2911424?Data+Binding+with+Dynamically+Created+controls+not+working+both+ways
In a nutshell, in the Page.OnInit method I assign an instance of my templates to the FormView EditItemTemplate, EmptyDataTemplate, InsertItemTemplate and ItemTemplate properties (a different instance for each property with the appropriate controls, layout, etc for that template). I see that the InstantiateIn method of the template corresponding to the default mode is called, the control hierarchy is created correctly and the UI rendered as expected.
I have a set of button controls in each of my templates that enable the mode switches. So, for instance, in the ItemTemplate, I have a button with CommandName="New". I expect that clicking this button will cause the FormView to change into the Insert mode. Instead, I get the postback and InstantiateIn is called on my ItemTemplate. The handlers I've attached to the FormView's ModeChanging and ModeChanged events do not fire.
When I step through the control hierarchy, I see the same object model as the page I created in markup - with one exception. I am using the HtmlTable, HtmlTableRow and HtmlTableCell controls to construct the layout whereas the markup uses <table>, <tr> and <td> elements.
Any thoughts on what I'm missing? I'd really like to get this working with the automatic binding (through event bubbling) to change modes rather than have to manually create and code the buttons and their actions.
Here is the code used to generate the template:
public class FormViewTemplate : INamingContainer, ITemplate
{
private Boolean _childControlsCreated;
private Panel _panel;
public FormViewTemplate(TemplateMode mode) { Mode = mode; }
public TemplateMode Mode { get; private set; }
private void CreateChildControls()
{
_panel = new Panel();
_panel.Controls.Add(CreateButtons());
switch (Mode)
{
case TemplateMode.Edit:
_panel.Controls.Add(new LiteralControl("Edit Mode"));
break;
case TemplateMode.Empty:
_panel.Controls.Add(new LiteralControl("Empty Mode"));
break;
case TemplateMode.Insert:
_panel.Controls.Add(new LiteralControl("Insert Mode"));
break;
case TemplateMode.ReadOnly:
_panel.Controls.Add(new LiteralControl("Read-Only Mode"));
break;
}
}
private Panel CreateButtons()
{
var panel = new Panel();
var table = new HtmlTable()
{
Border = 0,
CellPadding = 2,
CellSpacing = 0
};
panel.Controls.Add(table);
var tr = new HtmlTableRow();
table.Rows.Add(tr);
var td = new HtmlTableCell();
tr.Cells.Add(td);
var addButton = new ASPxButton()
{
CommandName = "New",
Enabled = (Mode == TemplateMode.ReadOnly),
ID = "AddButton",
Text = "Add"
};
td.Controls.Add(addButton);
return panel;
}
private void EnsureChildControls()
{
if (!_childControlsCreated)
{
CreateChildControls();
_childControlsCreated = true;
}
}
void ITemplate.InstantiateIn(Control container)
{
EnsureChildControls();
container.Controls.Add(_panel);
}
}
(Note that the template is cached so the control hierarchy is only built once.)

Highlighting all user controls in an ASP.NET page

I want to highlight all user controls used in a page by adding a border around it. We are doing this to facilitate debugging. I could override RenderControl method in user control base class to do that easily but we have lot of user controls(legacy) that are not using base class.
I then took a different approach. I tried traversing page controls in PreRender method of page base class, which is used by all pages, and add a div with border to all user controls. But I found that it is not possible to add or remove from control collection if it contains code blocks (i.e. <% ... %>).
Any suggestions?
thanks
Here is how you can highlight an active input control that has the focus. You need to handle the onfocus and onblur client-side events of the input controls.and apply or remove the css style to the control by setting the controls className attribute
Add this to your css file:
.highlight
{
background-color: #fefbd2; /*highlight with yellow*/
color: #000080; /*make text blue*/
}
In your App_Code directory create a helper class like
public static class Helpers
{
/// <summary>
/// Adds the onfocus and onblur attributes to all input controls found in the specified parent,
/// to change their apperance with the control has the focus
/// </summary>
public static void SetInputControlsHighlight(Control container, string className, bool onlyTextBoxes)
{
foreach (Control ctl in container.Controls)
{
if ((onlyTextBoxes && ctl is TextBox) ||
(!onlyTextBoxes && (ctl is TextBox || ctl is DropDownList ||
ctl is ListBox || ctl is CheckBox || ctl is RadioButton ||
ctl is RadioButtonList || ctl is CheckBoxList)))
{
WebControl wctl = ctl as WebControl;
wctl.Attributes.Add("onfocus", string.Format("this.className = '{0}';", className));
wctl.Attributes.Add("onblur", "this.className = '';");
}
else
{
if (ctl.Controls.Count > 0)
SetInputControlsHighlight(ctl, className, onlyTextBoxes);
}
}
}
}
Then just override the OnLoad method of any page.
protected override void OnLoad(EventArgs e)
{
Helpers.SetInputControlsHighlight(this, "highlight", false);
base.OnLoad(e);
}

DropDownList OnSelectedIndexChange to 0th index w/out ViewState

I did follow the article TRULLY Understanding ViewState (great article btw) and populating my drop down list is working great. I've even setup a OnSelectedIndexChange event which fires almost as great.
The problem I've found is the SelectedIndexChanged event won't fire when selecting the 0th index. It does all other times however.
Here's some code:
<asp:DropDownList runat="server" ID="DropDownList1" EnableViewState="false"
AutoPostBack="True" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" />
protected override void OnInit(EventArgs e)
{
this.DropDownList1.DataTextField = "Text";
this.DropDownList1.DataValueField = "Value";
this.DropDownList1.DataSource = fillQueueDropDown();
this.DropDownList1.DataBind();
base.OnInit(e);
}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
OnSelectedQueueChanged(e);
}
public void OnSelectedQueueChanged(EventArgs e)
{
// Do stuff.
}
public event EventHandler queueNamesChangedEvent;
public void OnSelectedQueueChanged(EventArgs e)
{
if (queueNamesChangedEvent != null)
queueNamesChangedEvent(this, e);
}
I suppose I can do some type of check in the Page_Load method:
if(ViewState["selectedIndexChangedFlag"] != 1)
// raise OnSelectedChange event
Or is there something I can setup in the OnInit() method where I'm rebinding this data everytime that i can do?
See, my custom EventHander raises an event which is caught by a the parent page in which this control resides, so that the parent could take some action using the newly selected value. And this is currently working for all cases where the selected index > 0.
I create a property in this control which contains the most recently selected index, in which case my parent page can action on this property value on every Page_Load... dunno.
Open to suggestions. Or how to force this SelectedIndexChanged event to fire for that 0th index selection.
The problem is that you are loading the data each time and this is resetting the selected index. Imagine this is your dropdown:
zero [selected]
one
two
Then in the client you change the selected index:
zero
one [selected]
two
This populates the hidden input __EVENTARGUMENT with your new index (1) and the hidden input __EVENTTARGET with the id of your dropdown. Now the server-side code kicks in and reloads your data:
zero [selected]
one
two
"zero" is the selected value because that is the default when the data is loaded. Then ASP.NET looks for __EVENTTARGET and __EVENTARGUMENT in the Request and finds your dropdown's id and finds the new index (1). Now your dropdown looks like this:
zero
one [selected]
two
Since the index has changed, the dropdown raises its SelectedIndexChanged event indicating that the index has changed. Obviously this is the part that is working, now lets see why selecting the first item in the list does not raise the event.
Now lets say that we still have the dropdown in the state it was just in (with "one" being selected and the selected index of 1). What happens when we select the first item in the list on the client?
__EVENTTARGET and __EVENTARGUMENT are populated with the id of the dropdown and the new index (0). Then the server loads the data into the dropdown and the dropdown now looks like this again:
zero [selected]
one
two
Notice that since you reloaded the data before the events fired the index is already set to 0 because that is the default. Now when your event fires and the dropdown's selected index is set to 0, the dropdown does not see this as a change since the selected index (as far as it knows) has not changed.
Here is how to fix the problem:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!Page.IsPostBack)
{
this.DropDownList1.DataTextField = "Text";
this.DropDownList1.DataValueField = "Value";
this.DropDownList1.DataSource = fillQueueDropDown();
this.DropDownList1.DataBind();
}
}
What this will do is only load the data into the dropdown if the page is not a postback. This means that ViewState will maintain the data for you as well as the selected index so that when you post back the dropdown will compare the new index to the index you saw in the client.
My goal with disabling the ViewState on this drop down list is to minimize the size of the ViewState for the page.
The problem I had with only doing the if(!Page.IsPostBack){...DataBind()...}, is that when you select an item for the first time, and the page reloads, my drop down list becomes empty.
What I ended up doing was creating another Property on this control, LastIndex. When the OnSelectedIndexChanged event fires, I update the LastIndex value. In the Page_Load, I compare the Current and Last index values, if they're different, then fire a Index changed event.
public int SelectedValue{
get { return this.DropDownList1.SelectedItem.Value; }
}
public int LastIndex{
get { return this.ViewState["lastIndex"] == null ? -1 : (int)this.ViewState["lastIndex"]; }
set { this.ViewState["lastIndex"] = value; }
}
protected override void OnInit(EventArgs e){
base.OnInit(e);
this.DropDownList1.DataTextField = "Text";
this.DropDownList1.DataValueField = "Value";
this.DropDownList1.DataSource = fillQueueDropDown();
this.DropDownList1.DataBind();
}
protected void Page_Load(object sender, EventArgs e){
if (this.LastIndex != this.SelectedValue)
this.OnSelectedQueueChanged(new EventArgs());
}
private ListItemCollection fillQueueDropDown(){...}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e){
OnSelectedQueueChanged(e);
this.LastIndex = this.SelectedValue;
}
public event EventHandler queueNamesChangedEvent;
public void OnSelectedQueueChanged(EventArgs e){
if (queueNamesChangedEvent != null)
queueNamesChangedEvent(this, e);
}
You are right though. The data is re-loaded and re-bound in the OnInit phase. Then the ViewState is restored (and when the 0th index is restored), when we finally get to the Events phase, the control doesn't detect the change.
Not sure this is the most elegant route, but it's working good so far.
Then i found this in the msdn docs for IPostBackDataHandler:
public virtual bool LoadPostData(string postDataKey,
NameValueCollection postCollection) {
String presentValue = Text;
String postedValue = postCollection[postDataKey];
if (presentValue == null || !presentValue.Equals(postedValue)) {
Text = postedValue;
return true;
}
return false;
}
Since the present value is the same as the changed-to value, the event isn't fired.

Resources