I am working on an application that has a GridView item on an ASP.net page which is dynamically generated and does a partial post-back as items are updated within the grid-view. This partial post-back is causing the tab indices to be lost or at the very least ignored as the tab order appears to restart. The grid view itself already has the pre-render that is being caught to calculate the new values from the modified items in the grid-view. Is there a way to get what element had the focus of the page prior to the pre-render call? The sender object is the grid-view itself.
You can try using this function, which will return the control that caused the postback. With this, you should be able to reselect it, or find the next tab index.
private Control GetControlThatCausedPostBack(Page page)
{
//initialize a control and set it to null
Control ctrl = null;
//get the event target name and find the control
string ctrlName = Page.Request.Params.Get("__EVENTTARGET");
if (!String.IsNullOrEmpty(ctrlName))
ctrl = page.FindControl(ctrlName);
//return the control to the calling method
return ctrl;
}
Here's an instance where I had dynamically generated inputs that updated totals via AJAX on change. I used this code to determine the next tab index, based on the tab index of the control that caused the postback. Obviously, this code is tailored to my usage, but with some adjustments I think it could work for you as well.
int currentTabIndex = 1;
WebControl postBackCtrl = (WebControl)GetControlThatCausedPostBack(Page);
foreach (PlaceHolder plcHolderCtrl in pnlWorkOrderActuals.Controls.OfType<PlaceHolder>())
{
foreach (GuardActualHours entryCtrl in plcHolderCtrl.Controls.OfType<GuardActualHours>())
{
foreach (Control childCtrl in entryCtrl.Controls.OfType<Panel>())
{
if (childCtrl.Visible)
{
foreach (RadDateInput dateInput in childCtrl.Controls.OfType<RadDateInput>())
{
dateInput.TabIndex = (short)currentTabIndex;
if (postBackCtrl != null)
{
if (dateInput.TabIndex == postBackCtrl.TabIndex + 1)
dateInput.Focus();
}
currentTabIndex++;
}
}
}
}
}
Related
I have a Placeholder and I have a dynamically created panel in the placeholder, I also have some dynamically added radio buttons in the panel, now I can usefindControl() to find the radio buttons if they are direct children of the placeholder.
I've literally spent the whole of yesterday trying to find them when they are the child elements of the Panel. How is there a way to do this?
Here's my code below:
PlaceHolder1.Controls.Add(myPanel); //add the panel to the placeholderenter code here
myPanel.Controls.Add(myRadioButton); //add the radiobutton to the panel
You should make method that recursively searches for a control using it's Id. That mean that the method will search for a control inside of (in your case) placeholder. If method finds control, it will return it. If not, it will go search every placeholder's subcontrol, going "deeper". And then, if nothing is found, it will search one more level down, in every placeholder subcontrols' subcontrol etc.)
private Control FindControl(string ctlToFindId, Control parentControl)
{
foreach (Control ctl in parentControl.Controls)
{
if (ctl.Id == ctlToFindId)
return ctl;
}
if (ctl.Controls != null)
{
var c = FindControl(ctlToFindId, ctl);
if (c != null) return c;
}
return null;
}
and then use it like this:
Control ctlToFind = FindControl(myRadioButton.Id, Placeholder1);
if (ctlToFind != null)
{
//your radibutton is found, do your stuff here
}
else
{
// not found :(
}
Finding Controls recursive is an option, but it also has a couple of down-sides.
If you know the ID's of all the controls you can just use FindControl
RadioButtonList myRadioButton = PlaceHolder1.FindControl("Panel1").FindControl("RadioButtonList1") as RadioButtonList;
Label1.Text = myRadioButton.SelectedValue;
But you will need to give your dynamically added controls an ID.
Panel myPanel = new Panel();
myPanel.ID = "Panel1";
RadioButtonList myRadioButton = new RadioButtonList();
myRadioButton.ID = "RadioButtonList1";
PlaceHolder1.Controls.Add(myPanel);
myPanel.Controls.Add(myRadioButton);
I am trying to create ASP.NET server control (pure code, without ascx template - because control must be completly contained in .dll and it must not rely on external .ascx files), and I have a problem with dynamically adding items to repeater.
I want to add item to repeater in reaction to SelectedIndexChanged event, but when i do second DataBind() in that event, i lose data from ViewModel (for example, textboxes contains default data instead of text entered by user).
Simplified version of my code (in large portion borrowed from MS composite control example - http://msdn.microsoft.com/en-us/library/3257x3ea%28v=vs.100%29.aspx):
[ToolboxData("<{0}:FilterControl runat=server />")]
public class FilterControl : CompositeControl, IPostBackDataHandler
{
private List<FilteringProperty> elements = new List<FilteringProperty>();
private DropDownList filteringElementsDropDownList;
private Repeater usedFiltersRepeater;
[Bindable(true), DefaultValue(null), Description("Active filters")]
public List<FilteringProperty> UsedElements
{
get
{
EnsureChildControls();
if (ViewState["UsedElements"] == null)
{
ViewState["UsedElements"] = new List<FilteringProperty>();
}
return (List<FilteringProperty>)ViewState["UsedElements"];
}
set
{
EnsureChildControls();
ViewState["UsedElements"] = value;
}
}
protected override void RecreateChildControls()
{
EnsureChildControls();
}
protected override void CreateChildControls()
{
Controls.Clear();
filteringElementsDropDownList = new DropDownList { AutoPostBack = true };
usedFiltersRepeater = new Repeater();
foreach (var element in elements)
{
filteringElementsDropDownList.Items.Add(new ListItem(element.DisplayName));
}
filteringElementsDropDownList.SelectedIndexChanged += (sender, e) =>
{
string selectedText = filteringElementsDropDownList.SelectedValue;
FilteringProperty condition = elements.First(x => x.DisplayName == selectedText);
var toRemove = filteringElementsDropDownList.Items.Cast<ListItem>().FirstOrDefault(x => x.Text == condition.DisplayName);
if (toRemove != null)
{
filteringElementsDropDownList.Items.Remove(toRemove);
}
UsedElements.Add(condition);
// ======> A <========
};
usedFiltersRepeater.ItemDataBound += (sender, args) =>
{
FilteringProperty dataItem = (FilteringProperty)args.Item.DataItem;
Control template = args.Item.Controls[0];
TextBox control = (TextBox)template.FindControl("conditionControl");
control.Text = dataItem.DisplayName;
// ======> C <========
};
usedFiltersRepeater.ItemTemplate = // item template
usedFiltersRepeater.DataSource = UsedElements;
usedFiltersRepeater.DataBind();
// ======> B <========
Controls.Add(filteringElementsDropDownList);
Controls.Add(usedFiltersRepeater);
}
}
I marked important portions of code with (A), (B) and (C)
The problem is, (A) is executed after DataBinding (B and C), so changes in UsedElements are not visible until next postback.
It is possible to add usedFiltersRepeater.DataBind(); after (A), but than all controls are recreated without data from viewstate (i.e empty)
Is there a way to dynamically change repeater after databinding, such that data of contained controls is preserved?
Tl;dr - i have a DropDownList and I want to add editable items to Repeater on SelectedIndexChanged (without losing viewstate).
I finally solved my problem.
My solution is rather dirty, but it seems to work fine.
Instead of simple databinding:
I get state from all controls in repeater and save it in temporary variable (state for each control includes everything, such as selected index for dropdownlists) using my function GetState()
modify this state in any way i want
restore full state using my function SetState()
For example:
FilterState state = GetState();
state.Conditions.Add(new ConditionState { Item = condition });
SetState(state);
I have 2 server control
One create Items
the other one create a List of items.
So i have a public Item with a viewstate in the first
in the page when i add the server control name (Server control name 1) to a panel it render (with a createChildControls) and add to a public List<Server Name 1> that is a view state in the server control 2.
so i make
foreach (ServerControl_1 a in ServerControl_2)
output += a;
the result is the namespace of the item not the text.
So i must have to render it first and then add to the output...
But i just dont know how...
Someone help me?
use something like
protected String displayName(Object item)
{
String name = "";
if (item != null && item.hasOwnProperty("name")) {
name = item["name"];
}
return name;
}
call this in your for loop.
output += displayName(a)
Example code:
var div = new HtmlGenericControl("div");
div.Controls.Add(new Literal() { ID = "litSomeLit" });
var lit = (Literal)div.FindControl("litSomeLit");
Assert.IsNotNull(lit);
This code fails the assert, because lit is null. Debugging shows that div.Controls definitely contains a literal with ID of "litSomeLit." My questions are "Why?" and "Is there any way to get a control of a specific ID without doing a recursive search of div.Controls[] by hand one element at a time?"
The reason I'm doing things this way is that my actual application is not so straightforward- a method I'm writing is given a complex control with several subcontrols in a number of possible configurations. I need to access a specific control several layers down (eg, the control with ID "txtSpecificControl" might be at StartingControl.Controls[0].Controls[2].Controls[1].Controls[3]). Normally I could just do FindControl("txtSpecificControl"), but that does not seem to work when the controls were just dynamically created (as in the above example code).
Near as I can tell, there is no way to do what I'm trying to accomplish without adding the control to the page. If I had to guess, I'd say that FindControl uses the UniqueID property of the control, which generally contains the IDs of all the controls above the current one (eg OuterControlID$LowerControlId$TargetControlID). That would only get generated when the control actually gets added to the page.
Anyway, here's an implementation of recursive depth-first-search FindControl that'll work when the control is not attached to the page yet:
public static Control FindControl(Control parent, string id)
{
foreach (Control control in parent.Controls)
{
if (control.ID == id)
{
return control;
}
var childResult = FindControl(control, id);
if (childResult != null)
{
return childResult;
}
}
return null;
}
Change your code to
var div = new HtmlGenericControl("div");
Page.Controls.Add(div);
div.Controls.Add(new Literal() { ID = "litSomeLit" });
var lit = (Literal)div.FindControl("litSomeLit");
As far as i know FindControl only works when the control is in the visual tree of the page.
When you confirmed that the control was in the Controls collection, did you do that by inspecting the collection directly? FindControl() may not work in this context.
When you debug the test, is the var lit null? If so, you may have to access the member by item index instead of using the FindControl() method.
I have seen many others with similar problems but I cannot find the flaw in my logic here. Any help would be greatly appreciated.
I have a Panel which I have added numerous label and textbox controls to, ie:
myPanel.Controls.Add(txtBox);
These controls are created and added in a method called previous to the iteration method.
I want to iterate through each textbox and use its Text property as a parameter in another method but I am not having any luck. Here is my attempt to iterate:
public void updateQuestions()
{
try
{
foreach (Control c in editQuestionsPanel.Controls)
{
if (c is TextBox)
{
TextBox questionTextBox = (TextBox)c;
string question = questionTextBox.Text;
writeNewQuestionToTblQuestions(question);
}
}
}
catch (Exception err)
{
Console.WriteLine(err.Message);
}
}
The problem I am having is that the controls are not in the Panel when I arrive at this updateQuestions() method. Here is the process involved:
A commandButton is clicked and the questions are read from a DB, for each question a method is called which adds 2 labels and a textbox to editQuestionsPanel.Controls. This panel is inside a PlaceHolder which is then made visible.
When a button inside the PlaceHolder is clicked, the updateQuestions() method is called and the editQuestionsPanel.Controls.Count = 1. As there are approx 12 questions in the DB it should be around 36. The one control inside the Panel is of type:
System.Web.UI.LiteralControl
It contains no controls.
I am sure that somwhere in the lifecycle the Panel's controls are being cleared but I do not know how to step thru the life cycle. I have a Page_load method which is called as soon as a button is clicked but once the button which calls updateQuestions() is clicked the editQuestionsPanel.Controls.Count is already back to 1 so it must be cleared before this but I do not know how to correct this...
Any help you can give to help me solve this would be greatly appreciated - its killing me!
This selects from collection controls only that which are of type TextBox.
(the same as control is TextBox or (control as TextBox) != null)
If controls are contained in editQuestionsPanel.Controls:
using System.Linq;
IEnumerable<TextBox> textBoxes = editQuestionsPanel.Controls.OfType<TextBox>();
foreach (TextBox textBox in textBoxes)
{
// do stuff
}
To select all child controls use next extension method:
public static IEnumerable<T> GetChildControls<T>(this Control control) where T : Control
{
var children = control.Controls.OfType<T>();
return children.SelectMany(c => GetChildControls<T>(c)).Concat(children);
}
Using:
IEnumerable<TextBox> textBoxes = editQuestionsPanel.GetChildControls<TextBox>();
When you add controls dynamically, you need to do that on every request - asp.net doesn't do that for you!
Add the controls in the Init or Load phase, then they will get populated with the postback values.
A frequently made mistake: Container.Controls only contains the first level child controls in this container. That is: TextBox1 in PanelA, PanelA in PanelB, you can't get TextBox1 in PanelB.Controls.
My solution is to write an extension method:
public static IEnumerable<Control> AllControls(this Control ctl)
{
List<Control> collection = new List<Control>();
if (ctl.HasControls())
{
foreach (Control c in ctl.Controls)
{
collection.Add(c);
collection = collection.Concat(c.AllControls()).ToList();
}
}
return collection;
}
Now TextBox1 is in PanelB.AllControls(). To filter all controls with type, using PanelB.AllControls().OfType<TextBox>()
If the other answers don't help, try doing your code but add recursivity. Your code would not work if it's editQuestionsPanel => Panel => Textbox
You can do something like this instead:
var questions = from tb in editQuestionsPanel.Controls.OfType<TextBox>()
select tb.Text;
foreach(var question in questions)
{
writeNewQuestionToTblQuestions(question);
}
Try this
int Count = 0;
foreach (Control ctr in Panel1.Controls)
{
if (ctr is TextBox)
Count++;
}