ID of Nested custom web control - asp.net

I have googled a lot today and have not found any helpful answers to my problem.
I have a web custom control (CustomControl1) that calls another web custom control (CustomControl2) as follows:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
for (int i= 0; i < 2; i++)
{
Panel pn = new Panel();
Controls.Add(pn);
CustomControl2 ct2= new CustomControl2();
ct2.ID = "CustomControl2";
pn.Controls.Add(rowControl);
}
}
I thought each time CustomControl2 created, it will be given the ID as {parentId} + "CustomControl2" but it's not and because of that, I get "multiple controls with ID CustomControl2 is found." I know I can make CustomControl2 Id unique but what I don't understand is why the Id has not been embedded with the parent Id.
Thanks for any feedback!

Just had to do this myself, turns out all you have to do is implement the INamingContainer "marker" interface on your web control. This will cause .net to generate an ID namespace for each instance of your control (which means all child controls will be prefixed with the parent control's ID as required).

Related

ASP dynamic usercontrols in a gridview do not update

I have a got Gridview, that gets its data by:
internal void updateGrid()
{
List<String> data = dt.getAlldata(UserID(), ListID());
gridViewer.DataSource = data;
gridViewer.DataBind();
}
After a button click the database is updated and the new data is shown. Everything works fine.
Now I have to replace the string with an Usercontrol:
internal void updateGrid()
{
List<String> data = dt.getAlldata(UserID(), ListID());
gridViewer.DataSource = data;
gridViewer.DataBind();
for (int i = 0; i < gridViewer..Rows.Count; i++)
{
UCControl dum = (UCControl)LoadControl("~/UCControl.ascx");
dum.SetData(gridViewer.Rows[i].Cells[0].Text, false);
gridViewer.Rows[i].Cells[0].Controls.Clear();
gridViewer.Rows[i].Cells[0].Controls.Add(dum);
}
}
After first Page_Load(), everything is shown correctly. But when I click my buttons, the Usercontrols do not repaint. The data is set correctly inside, but it does not update.
Because this Control reacts by javascript on UserInputs on the client side, every Usercontrol has got his own Id that is set by
this.ID = "UCControl" + data[0];
data[0] is unique, but known during the whole process.
Can somebody tell me why the UserControl does not repaint? Or better: How do I tell the Usercontrols to update?
From your code I would say that once the data is bound then you can't go in immediately after and start to change it. You would need to hook into one of the databinding events.
If you were using a list viem ItemDataBound would be a good candidate to hook into. But the GridView has a more limited set of events and doesn't offer that level of control - so you are a bit stuck on that score.
In my experience using dynamic controls in asp.net (LoadControl) is just a bit fraught. I think a better option would be to publicly expose a property in your user control and bind to that.
This question gives a good description of how to achieve this
asp.net user controls binds inside gridview
Hope that helps

Click event not detected in UserControl

I've some UserControls which are created dynamically, with parameters :
foreach (DataRow dr in drc)
{
question = (from ques in bdd.QUESTION where ques.ID_QUESTION == idQuestion select ques).FirstOrDefault();
List<Object> listParams = new List<Object>();
listParams.Add(question);
AdminQuestion qa = (AdminQuestion)(Controller.LoadControl(Page, "~/UserControls/AdminQuestion.ascx", listParams.ToArray()));
pnl_question_list.Controls.Add(qa);
}
This is a method I found on SoF and it works great.
This method is called when I click on a button, and all my usercontrols are correctly created.
In this usercontrol, I have a button :
<asp:Button ID="btn_validation_modify_question" runat="server" Text="Modifier"
CssClass="clear_left_float_left myBouton myBoutonEnvoyer"
OnClick="btn_validation_modify_question_Click"/>
And my method :
protected void btn_validation_modify_question_Click(object sender, EventArgs e)
{
QUESTION q = (from m in bdd.QUESTION where m.ID_QUESTION == question.ID_QUESTION select m).FirstOrDefault();
q.MESSAGE = txt_modify_question_message.InnerText;
q.ID_THEME = new Guid(ddl_modify_question_theme.SelectedValue);
bdd.ApplyCurrentValues<QUESTION>("QUESTION", q);
bdd.SaveChanges();
}
But when I click on the button, it don't apply the method, and reload the page, like if I didn't have any declared method.
Is there something I don't know? I already used this technique and everything worked...
If you add controls dynamically, you'll normally need to do it on Init or PreInit page events. Otherwise event handlers will never work.
So, dynamically added controls must be programmatically added to the Web page on each and every page visit. The best time to add these controls is during the initialization stage of the page life cycle, which occurs before the load view state stage. That is, we want to have the control hierarchy complete before the load view state stage arrives. For this reason, it is best to create an event handler for the Page class's Init event in your code-behind class, and add your dynamic controls there.
Note You may be able to get away with loading your controls in the
Page_Load
event handler and maintaining the view state properly. It all depends on whether or not you are setting any properties of the dynamically loaded controls programmatically and, if so, when you're doing it relative to the
Controls.Add(dynamicControl)
line. A thorough discussion of this is a bit beyond the scope of this article, but the reason it may work is because the
Controls
property's
Add()
method recursively loads the parent's view state into its children, even though the load view state stage has passed.
Here you have more information.
EDIT
Just to be sure we're talking about the same, here you have how your code should look like
protected void Page_Init(object sender, EventArgs e)
{
...
foreach (DataRow dr in drc)
{
question = (from ques in bdd.QUESTION where ques.ID_QUESTION == idQuestion select ques).FirstOrDefault();
List<Object> listParams = new List<Object>();
listParams.Add(question);
AdminQuestion qa = (AdminQuestion)(Controller.LoadControl(Page, "~/UserControls/AdminQuestion.ascx", listParams.ToArray()));
pnl_question_list.Controls.Add(qa);
}
...
}
PageLoad(){
If(!Page.IsPostBack())
foreach (DataRow dr in drc)
{
question = (from ques in bdd.QUESTION where ques.ID_QUESTION == idQuestion select ques).FirstOrDefault();
List<Object> listParams = new List<Object>();
listParams.Add(question);
AdminQuestion qa = (AdminQuestion)(Controller.LoadControl(Page, "~/UserControls/AdminQuestion.ascx", listParams.ToArray()));
pnl_question_list.Controls.Add(qa);
}
}
Did you try with this solution? And not use Page_Init()?

Adding TextBocex, CheckBoxes on Request C#

I will be starting a project soon at my company and the client would like to have the option in the portal to add textboxes or checkboxes as an administrator,,, so for instance initially I may have something like
Name [textBox]
Phone [textBox]
So the client would like to log in as an admin and be able to add
Name [textBox]
Phone [textBox]
Receive Brochure [checkBox] //added by client.
Forget about the portal and the admin part.. what I would like to know is what would be the best way to design this (the user to be able to add elements)
Any ideas would be much appreciated
Regards
You could create an additional aspx-form in which the User (or the Admin) is able to define and create his/her own forms, you supply the Variable names and they choose to add the controls, save it in a specific scheme in the Database, e.g.
UserForm:
UserID FormID
Form:
FormID FormName
FormElement:
FormID VariableName ControlType Index
Of course this could also be done by an administrator and be visible by everyone.
To view the specific forms you could add yet another aspx-page containing the following code:
protected void Page_Load(object sender, EventArgs e)
{
//you saved the FormName or ID to Session when you accessed it
string formName = Session["FormName"].ToString();
//this handles getting the elements for this Form from DB
List<FormElement> elementList = FormElement.GetForForm(formName);
this.renderForm(elementList);
}
private void renderForm(List<FormElement> eList)
{
foreach(FormElement fe in eList)
{
//Labels left, Controls right, of course this is just a design decision
if(fe.Index%2==1)
{
Label lbl = new Label();
lbl.Text = fe.Variable;
lbl.ID = fe.ControlType + fe.Variable;
divLeft.Controls.Add(lbl);
}
else
{
dynamic ctrl = null;
switch (fe.ControlType)
{
case "TextBox":
ctrl = new TextBox();
break;
case "CheckBox":
ctrl = new CheckBox();
break;
default:
break;
}
ctrl.ID = fe.ControlType + fe.Variable;
divRight.Controls.Add(ctrl);
}
}
}
Later on after the User hitting submit you'd be able to receive the values entered into those Controls by accessing divRight.FindControl(fe.ControlType + fe.Variable) since that should be unique per Form.
This approach assumes you're using .NET 4.0 (because of dynamic), but of course you can do this just fine without it, it'll just be more code.
Please let me know if this is what you searched for or if it was helpful.
Thanks,
Dennis
From what I have done in the past where I had to create a dynamic survey based on a client needs. I had a database that contained the following tables:
Survey - stores list of client surveys.
Controls - listed the type of controls that would be needed. For example, textbox, checkbox etc.
Survey Controls - links all the controls required for a survey.
User values - stores the values entered in a controll based on the survey used.
I then added some logic to dynamically create the controls based on a survey selected by reading the values from my database.

Programmatically add UserControl with events

I need to add multiple user controls to a panel for further editing of the contained data. My user control contains some panels, dropdown lists and input elements, which are populated in the user control's Page_Load event.
protected void Page_Load(object sender, EventArgs e)
{
// populate comparer ddl from enum
string[] enumNames = Enum.GetNames(typeof (SearchComparision));
var al = new ArrayList();
for (int i = 0; i < enumNames.Length; i++)
al.Add(new {Value = i, Name = enumNames[i]});
scOperatorSelection.DataValueField = "Value";
scOperatorSelection.DataTextField = "Name";
...
The data to be displayed is added to the user control as a Field, defined above Page_Load. The signature of the events is the following:
public delegate void ControlStateChanged(object sender, SearchCriteriaEventArgs eventArgs);
public event ControlStateChanged ItemUpdated;
public event ControlStateChanged ItemRemoved;
public event ControlStateChanged ItemAdded;
The update button on the user control triggers the following method:
protected void UpdateCriteria(object sender, EventArgs e)
{
var searchCritCtl = (SearchCriteria) sender;
var scEArgs = new SearchCriteriaEventArgs
{
TargetCriteria = searchCritCtl.CurrentCriteria.CriteriaId,
SearchComparision = ParseCurrentComparer(searchCritCtl.scOperatorSelection.SelectedValue),
SearchField = searchCritCtl.scFieldSelection.SelectedValue,
SearchValue = searchCritCtl.scFilterValue.Text,
ClickTarget = SearchCriteriaClickTarget.Update
};
if (ItemUpdated != null)
ItemUpdated(this, scEArgs);
}
The rendering page fetches the data objects from a storage backend and displays it in it's Page_Load event. This is the point where it starts getting tricky: i connect to the custom events!
int idIt = 0;
foreach (var item in _currentSearch.Items)
{
SearchCriteria sc = (SearchCriteria)LoadControl("~/content/controls/SearchCriteria.ascx");
sc.ID = "scDispCtl_" + idIt;
sc.ControlMode = SearchCriteriaMode.Display;
sc.CurrentCriteria = item;
sc.ItemUpdated += CriteriaUpdated;
sc.ItemRemoved += CriteriaRemoved;
pnlDisplayCrit.Controls.Add(sc);
idIt++;
}
When first rendering the page, everything is displayed fine, i get all my data. When i trigger an update event, the user control event is fired correctly, but all fields and controls of the user control are NULL. After a bit of research, i had to come to the conclusion that the event is fired before the controls are initialized...
Is there any way to prevent such behavior / to override the page lifecycle somehow? I cannot initialize the user controls in the page's Init-event, because i have to access the Session-Store (not initialized in Page_Init).
Any advice is welcome...
EDIT:
Since we hold all criteria informations in the storage backend (including the count of criteria) and that store uses the userid from the session, we cannot use Page_Init... just for clarification
EDIT #2:
I managed to get past some of the problems. Since i'm now using simple types, im able to bind all the data declaratively (using a repeater with a simple ItemTemplate). It is bound to the control, they are rendered in correct fashion. On Postback, all the data is rebound to the user control, data is available in the OnDataBinding and OnLoad events, everything looks fine.
But as soon it enters the real event (bound to the button control of the user control), all field values are lost somehow...
Does anybody know, how the page lifecycle continues to process the request after Databinding/Loading ? I'm going crazy about this issue...
Dynamic controls are can be a nightmare. The trick is to make sure you rebind everything on the postback.
I figured out a solution ;)
If i work with the OnCommand event on the buttons inside the usercontrol, i can pass a CommandArgument. Now im binding the collection identifier to the CommandArgument parameter of the button which enables me to handle all postbacks inside the usercontrol.
<asp:Button ID="scUpdate"
runat="server"
Text="Update"
OnCommand="HandleCommand"
CommandName="update"
CommandArgument='<%# CriteriaId %>' />
This declaration preserves the CriteriaId (a Guid) throughout postbacks and enables me to identify the modified entry on the underlying collection (managed on the page). The following code snippet shows how the event to the subscribing page is triggered.
scEArgs = new SearchCriteriaEventArgs
{
TargetCriteria = new Guid(e.CommandArgument.ToString()),
SearchComparision = ParseCurrentComparer(),
SearchField = scFieldSelection.SelectedValue,
SearchValue = scFilterValue.Text,
ClickTarget = SearchCriteriaClickTarget.Update
};
if (ItemUpdated != null)
ItemUpdated(this, scEArgs);
Maybe this answer helps somebody so i'll just post it ;)

Use DisplayNameAttribute in ASP.NET

I want to bind a List to a GridView on a web page, but override the way the property names display via annotation. I thought System.ComponentModel would work, but this doesn't seem to work. Is this only meant for Windows Forms?:
using System.ComponentModel;
namespace MyWebApp
{
public class MyCustomClass
{
[DisplayName("My Column")]
public string MyFirstProperty
{
get { return "value"; }
}
public MyCustomClass() {}
}
Then on the page:
protected void Page_Load(object sender, EventArgs e)
{
IList<MyCustomClass> myCustomClasses = new List<MyCustomClass>
{
new MyCustomClass(),
new MyCustomClass()
};
TestGrid.DataSource = myCustomClasses;
TestGrid.DataBind();
}
This renders with "MyFirstProperty" as the column header rather than "My Column." Isn't this supposed to work?
When using .net 4 or later you can use gridview1.enabledynamicdata(typeof(mytype)). I haven't looked at all the types you can use there but I know the [displayname("somename")] works well but the [browsable(false)] doesn't omit the column from the grid. It looks like a knit one slip one from MS. at least you can easily rename column names and to omit a column I just declare a variable instead of using a property. It has the same effect...
Just by the way, using the designer to create columns is the easy way out but to just show a different column name takes way to much time especially with classes with many fields.
What SirDemon said...
The answer appears to be no, you can't. At least not out of the box.
The System.Web.UI.WebControls.GridView uses reflected property's name:
protected virtual AutoGeneratedField CreateAutoGeneratedColumn(AutoGeneratedFieldProperties fieldProperties)
{
AutoGeneratedField field = new AutoGeneratedField(fieldProperties.DataField);
string name = fieldProperties.Name; //the name comes from a PropertyDescriptor
((IStateManager) field).TrackViewState();
field.HeaderText = name; //<- here's reflected property name
field.SortExpression = name;
field.ReadOnly = fieldProperties.IsReadOnly;
field.DataType = fieldProperties.Type;
return field;
}
While System.Windows.Forms.DataGridView uses DisplayName if available:
public DataGridViewColumn[] GetCollectionOfBoundDataGridViewColumns()
{
...
ArrayList list = new ArrayList();
//props is a collection of PropertyDescriptors
for (int i = 0; i < this.props.Count; i++)
{
if (...)
{
DataGridViewColumn dataGridViewColumnFromType = GetDataGridViewColumnFromType(this.props[i].PropertyType);
...
dataGridViewColumnFromType.Name = this.props[i].Name;
dataGridViewColumnFromType.HeaderText = !string.IsNullOrEmpty(this.props[i].DisplayName) ? this.props[i].DisplayName : this.props[i].Name;
}
}
DataGridViewColumn[] array = new DataGridViewColumn[list.Count];
list.CopyTo(array);
return array;
}
Unfortunately, while you can override the CreateAutoGeneratedColumn, neither the missing DisplayName nor underlying property descriptor gets passed, and you can't override CreateAutoGeneratedColumns (although you could CreateColumns).
This means you'd have to iterate over reflected properties yourself and in some other place.
If all you care about is the header text in GridView, just use the HeaderText property of each field you bind. If you're autogenerating the columns, you just set the HeaderText after you've bound the GridView.
If you want a GridView that takes into account some attribute you placed on the properties of your bound class, I believe you'll need to create your own GridView.
I may be wrong, but I've not seen any ASP.NET Grid from control vendors (at least Telerik , Janus Systems and Infragistics) do that. If you do it, maybe sell the idea to them.
Are you using .net4, what you need to do is to set enabledynamicdata on the grid view to true.
You can do it now on asp.net mvc2. It works just like that

Resources