I am creating a server control that will sit in a aspx page. When the user selects a menu option, html controls (selects, inputs, etc) will have be added dynamically. I can do it using a user control, but I'm not sure how to go about it in a server control.
Can anyone tell me how I can add dynamic html into the control after it's already sitting in a page?
Since you are planning to add several controls as the output of your server control, you should consider inheriting from CompositeControl, this control is designed to work with several constituent controls, minimizing the code needed to do common things like keeping state and handling constituent control events
You need to override the CreateChildControls to add child controls. At the end of this method you should use this.ChildControlsCreated = true; to specify if the child controls were created, this is necessary because the CreateChildControls is called several times during the ASP.Net page life-cycle
You need to apply the same rules that you would typically apply to any server control, for example implement the INamingContainer interface to ensure that child controls will have a unique client ID
This is a simple example:
[DefaultProperty("UserText")]
[ToolboxData(#"<{0}:UserPassword runat=server UserText="""" PasswordText="""" />")]
public class UserPassword : CompositeControl
{
public event EventHandler Submitted = delegate { };
[Bindable(true)]
[Category("Appearance")]
[Description("User text")]
[DefaultValue("")]
[Localizable(true)]
public string UserText
{
get
{
var t = this.FindControl("Username") as TextBox;
return t.Text;
}
set
{
var t = this.FindControl("Username") as TextBox;
t.Text = value;
}
}
[Bindable(true)]
[Category("Appearance")]
[Description("Password text")]
[DefaultValue("")]
[Localizable(true)]
public string PasswordText
{
get
{
var t = this.FindControl("Password") as TextBox;
return t.Text;
}
set
{
var t = this.FindControl("Password") as TextBox;
t.Text = value;
}
}
protected override void CreateChildControls()
{
var p = new Panel { Width= new Unit(200), BackColor = Color.Silver };
var ul = new Label { Text = "Username: " };
var u = new TextBox { ID = "Username" };
var pal = new Label { Text = "Password: " };
var pa = new TextBox { ID = "Password", TextMode = TextBoxMode.Password };
var l = new Literal { Text = "<br />" };
var b = new Button { Text = "Log in" };
b.Click += (x, y) => this.Submitted(x, y);
p.Controls.Add(ul);
p.Controls.Add(u);
p.Controls.Add(l);
p.Controls.Add(pal);
p.Controls.Add(pa);
p.Controls.Add(l);
p.Controls.Add(l);
p.Controls.Add(b);
this.Controls.Add(p);
this.ChildControlsCreated = true;
}
}
Related
Please refer the attached screenshot. I have an array of the checkbox and a button for the post back in ASP.Net page. I have written a function as follows to determine what all check boxes have been checked on the button click event: The following code is a part of the business component which is called from ASP.Net. Please let me know how can I return actionArray back to calling functon in ASP.Net page.
public void checkBoxValidation(Control parent, string strKey)
{
XmlDocument getCyleXML = new XmlDocument();
string strChkID="", strActionXPath = "",strAction="";
ArrayList actionArray = new ArrayList();
// Loop through all the controls on the page
foreach (Control c in parent.Controls)
{
// Check and see if it's a checkbox.
if ((c.GetType() == typeof(CheckBox)))
{
// Since its a checkbox, see if this is checked.
if (((CheckBox)(c)).Checked == true)
{
// Find the ID of the checkbox
strChkID = ((CheckBox)(c)).ID.ToString();
getCyleXML = CycleXML(strKey);
strActionXPath = "/Actions/Action[checkbox='" + strChkID + "']/*[self::Name]";
strAction = getCyleXML.SelectSingleNode(strActionXPath).ToString();
actionArray.Add(strAction);
}
}
// Now we need to call itself (recursion) because all items (Panel, GroupBox, etc) is a container so we need to check
// all containers for any checkboxes.
if (c.HasControls())
{
checkBoxValidation(c, strKey);
}
}
}
The code should be like this :
public ArrayList checkBoxValidation(Control parent, string strKey, ArrayList actionArray)
{
XmlDocument getCyleXML = new XmlDocument();
string strChkID="", strActionXPath = "",strAction="";
if(actionArray == null) { actionArray = new ArrayList(); }
// Loop through all the controls on the page
foreach (Control c in parent.Controls)
{
// Check and see if it's a checkbox.
if ((c.GetType() == typeof(CheckBox)))
{
// Since its a checkbox, see if this is checked.
if (((CheckBox)(c)).Checked == true)
{
// Find the ID of the checkbox
strChkID = ((CheckBox)(c)).ID.ToString();
getCyleXML = CycleXML(strKey);
strActionXPath = "/Actions/Action[checkbox='" + strChkID + "']/*self::Name]";
strAction = getCyleXML.SelectSingleNode(strActionXPath).ToString();
actionArray.Add(strAction);
}
}
// Now we need to call itself (recursion) because all items (Panel, GroupBox, etc) is a container so we need to check
// all containers for any checkboxes.
if (c.HasControls())
{
checkBoxValidation(c, strKey, actionArray);
}
}
return actionArray;
}
public partial class ChatUserControl : System.Web.UI.UserControl
{
UserChatClass ucc = new UserChatClass();
public ChatUserControl()
{
lblChatFriend = new Label();
txtChatMessage = new TextBox();
imgFriend = new Image();
rpChatMessages = new Repeater();
}
public string ChatFriend { get { return this.lblChatFriend.Text; } set { this.lblChatFriend.Text = value; } }
public string imgFriendUrl { get { return this.imgFriend.ImageUrl; } set { this.imgFriend.ImageUrl = value; } }
public object rpChatDataSource { get { return this.rpChatMessages.DataSource; } set { this.rpChatMessages.DataSource = value; } }
public Repeater rpChatMessagesToBind { get { return this.rpChatMessages; } set { this.rpChatMessages = value; } }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ChatUserControl user1 = new ChatUserControl();
divChatUserControlCover.Controls.Add(user1);
}
}
private void BindUserControls()
{ ChatUserControl user1 = divChatUserControlCover.Controls[1] as ChatUserControl;
user1.ChatFriend = row["username"].ToString();
user1.imgFriendUrl = "../../HttpImageHandler.jpg?username=" + row["username"].ToString();
DataSet dsCM = ucc.GetChatMessages(Session["username"].ToString(), row["username"].ToString());
user1.rpChatDataSource = dsCM;
user1.DataBindForRpChatMessagesToBind();
user1.Visible = true;
}
Master.aspx
<div id="divChatUserControlCover" runat="server">
</div>
Ok I have edited the code and now I have created properties. How do I call the DataBind method for rpChatMessages? I also cant see my usercontrol on page. Why
I'm not sure if your trying to reference the first label or second label. If its the second lable you can't just do chatMessage. you would have to do
((Label)rpChatMessages.FindControl("chatMessage")) due to scope of controls.
When you reference a component inside another component (ie Repeater) the child component no longer belongs to the document (implied this) but rather belongs to the control, ie
this.rpChatMessages { chatMessage }
I think you are just trying to pass a value to one control inside a UserControl if this is correct, declare a public property like this:
ASCX code behind
public string MyProperty
{
get
{
return this.lbl.Text;
}
set
{
this.lbl.Text = value;
}
}
Setting the value to the UserControl
private void BindUserControls()
{
ChatUserControl user1 = divChatUserControlCover.Controls[1] as ChatUserControl;
user1.MyProperty = row["username"].ToString();
Setting the value in the page markup
<uc1:ChatUserControl MyProperty='<%# Eval("some field") %>' ...
Edit 1
Remove that line
public object rpChatDataSource { get { return this.rpChatMessages.DataSource; } set { this.rpChatMessages.DataSource = value; }
And instead add a method
public void BindMyRepeaterOrWhatever(IEnumerable<Yourentity> data)
{
this.myDataBoundControl.DataSource = data;
this.myDataBoundControl.DataBind();
}
You can change the IEnumerable<Yourentity> data for object data but if you can pass a strongly typed enumeration would be better
To my surprise I found why my user control's child controls dont get instantiated. Its because ChatUserControl user1 = new ChatUserControl() doesnt get its child controls initialized.
The proper way to create a new intance of user control is this way....
ChatUserControl user1 = (ChatUserControl)Page.LoadControl("~/ChatUserControl.ascx");
I have a nested web user control. Main web user control I have used on a page but now I want to access the control inside the inner web user control and its events.
Can any body help me in this issue.
In the parent user control, expose a reference to your child control, or it's properties through a property. For example
public partial class ParentControl : UserControl
{
...
// Expose the whole child control
public ChildControl MyChild
{
get { return this.theIdOfTheChildControl; }
}
...
// or expose specific properties
public string MyChildText
{
get { return this.theIdOfTheChildControl.Text; }
set { this.theIdOfTheChildControl.Text = value; }
}
}
try this method
private List<Control> GetAllNestedUserControl(Control ph)
{
List<Control> Get = new List<Control>();
foreach (var control in ph.Controls)
{
if (control is UserControl)
{
UserControl uc = control as UserControl;
if (uc.HasControls())
{
Get = GetAllNestedUserControl(uc);
}
}
else
{
Control c = (Control)control;
if (!(control is LiteralControl))
{
Get.Add(c);
}
}
}
return Get;
}
this method will return the list of all controls then do the following to get the control u want
List<Control> Get = GetAllNestedUserControl(ph);
Label l = (Label)Get.Find(o => o.ID == "lblusername");
l.Text = "changed from master";
I am developing a sharepoint 2007 web part that uses custom properties. Here is one:
[Personalizable(PersonalizationScope.User), WebDisplayName("Policy Update List Name")]
[WebDescription("The name of the SharePoint List that records all the policy updates.\n Default value is Policy Updates Record.")]
public string PolicyUpdateLogName
{
get { return _PolicyUpdateLogName == null ? "Policy Updates Record" : _PolicyUpdateLogName; }
set { _PolicyUpdateLogName = value; }
}
The properties work fine except that the changes are not reflected in the web part until you leave the page and navigate back (or just click on the home page link). Simply refreshing the page doesn't work, which makes me think it has something to do with PostBacks.
My current theory is that the ViewState is not loading postback data early enough for the changes to take effect. At the very least, the ViewState is involved somehow with the issue.
Thanks,
Michael
Here is more relevant code:
protected override void CreateChildControls()
{
InitGlobalVariables();
FetchPolicyUpdateLog_SPList();
// This function returns true if the settings are formatted correctly
if (CheckWebPartSettingsIntegrity())
{
InitListBoxControls();
InitLayoutTable();
this.Controls.Add(layoutTable);
LoadPoliciesListBox();
}
base.CreateChildControls();
}
...
protected void InitGlobalVariables()
{
this.Title = "Employee Activity Tracker for " + PolicyUpdateLogName;
policyColumnHeader = new Literal();
confirmedColumnHeader = new Literal();
pendingColumnHeader = new Literal();
employeesForPolicy = new List<SPUser>();
confirmedEmployees = new List<SPUser>();
pendingEmployees = new List<SPUser>();
}
...
// uses the PolicyUpdateLogName custom property to load that List from Sharepoint
private void FetchPolicyUpdateLog_SPList()
{
site = new SPSite(siteURL);
policyUpdateLog_SPList = site.OpenWeb().GetList("/Lists/" + PolicyUpdateLogName);
}
...
protected void InitListBoxControls()
{
// Init ListBoxes
policies_ListBox = new ListBox(); // This box stores the policies from the List we loaded from SharePoint
confirmedEmployees_ListBox = new ListBox();
pendingEmployees_ListBox = new ListBox();
// Postback & ViewState
policies_ListBox.AutoPostBack = true;
policies_ListBox.SelectedIndexChanged += new EventHandler(OnSelectedPolicyChanged);
confirmedEmployees_ListBox.EnableViewState = false;
pendingEmployees_ListBox.EnableViewState = false;
}
...
private void LoadPoliciesListBox()
{
foreach (SPListItem policyUpdate in policyUpdateLog_SPList.Items)
{
// Checking for duplicates before adding.
bool itemExists = false;
foreach (ListItem item in policies_ListBox.Items)
if (item.Text.Equals(policyUpdate.Title))
{
itemExists = true;
break;
}
if (!itemExists)
policies_ListBox.Items.Add(new ListItem(policyUpdate.Title));
}
}
Do some reading up on the Sharepoint web part life cycle. Properties are not updated until the OnPreRender event.
while the method we use in Substitution control should return strings, so how is it possible to use a donut caching in web forms on a server control which should be rendered server side?
for example Loginview control?
UPDATE
This is now a fully working example. There a few things happening here:
Use the call back of a substitution control to render the output of the usercontrol you need.
Use a custom page class that overrides the VerifyRenderingInServerForm and EnableEventValidation to load the control in order to prevent errors from being thrown when the usercontrol contains server controls that require a form tag or event validation.
Here's the markup:
<asp:Substitution runat="server" methodname="GetCustomersByCountry" />
Here's the callback
public string GetCustomersByCountry(string country)
{
CustomerCollection customers = DataContext.GetCustomersByCountry(country);
if (customers.Count > 0)
//RenderView returns the rendered HTML in the context of the callback
return ViewManager.RenderView("customers.ascx", customers);
else
return ViewManager.RenderView("nocustomersfound.ascx");
}
Here's the helper class to render the user control
public class ViewManager
{
private class PageForRenderingUserControl : Page
{
public override void VerifyRenderingInServerForm(Control control)
{ /* Do nothing */ }
public override bool EnableEventValidation
{
get { return false; }
set { /* Do nothing */}
}
}
public static string RenderView(string path, object data)
{
PageForRenderingUserControl pageHolder = new PageForUserControlRendering();
UserControl viewControl = (UserControl) pageHolder.LoadControl(path);
if (data != null)
{
Type viewControlType = viewControl.GetType();
FieldInfo field = viewControlType.GetField("Data");
if (field != null)
{
field.SetValue(viewControl, data);
}
else
{
throw new Exception("ViewFile: " + path + "has no data property");
}
}
pageHolder.Controls.Add(viewControl);
StringWriter result = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, result, false);
return result.ToString();
}
}
See these related questions:
Turn off page-level caching in a
user control
UserControl’s RenderControl is
asking for a form tag in (C#
.NET)
One thing Micah's answer left out is that the substitution function must be static, accept a HttpContext parameter, and return a string. See this msdn page for more info.
I've also extended Micah's helper class to be a little more flexible.
Markup
<asp:Substitution ID="Substitution1" MethodName="myFunction" runat="server" />
Implemenation
public static string myFunction(HttpContext httpContext){
ViewManager vm = new ViewManager();
//example using a Button control
Button b = new Button();
b.Text = "click me"; //we can set properties like this
//we can also set properties with a Dictionary Collection
Dictionary<string,object> data = new Dictionary<string,object>();
data.add("Visible",true);
String s = vm.RenderView(b,data); //don't do anything (just for example)
//we can also use this class for UserControls
UserControl myControl = vm.GetUserControl("~mypath");
data.clear();
data.add("myProp","some value");
return vm.RenderView(myControl,data); //return for Substitution control
}
Class
using System.IO;
using System.ComponentModel;
public class ViewManager
{
private PageForRenderingUserControl pageHolder;
public ViewManager()
{
pageHolder = new PageForRenderingUserControl();
}
public UserControl GetUserControl(string path)
{
return (UserControl)pageHolder.LoadControl(path);
}
public string RenderView(Control viewControl, Dictionary<string, object> data)
{
pageHolder.Controls.Clear();
//Dim viewControl As UserControl = DirectCast(pageHolder.LoadControl(Path), UserControl)
if (data != null) {
Type viewControlType = viewControl.GetType();
dynamic properties = TypeDescriptor.GetProperties(viewControl);
foreach (string x in data.Keys) {
if ((properties.Item(x) != null)) {
properties.Item(x).SetValue(viewControl, data[x]);
}
}
}
pageHolder.Controls.Add(viewControl);
StringWriter result = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, result, false);
return result.ToString();
}
private class PageForRenderingUserControl : Page
{
public override void VerifyRenderingInServerForm(Control control)
{
// Do nothing
}
public override bool EnableEventValidation {
get { return false; }
// Do nothing
set { }
}
}
}
Thanks again to Micah for the code
I'm fairly certain you can't do this - the Substitution control will only allow you to insert a string into an outputcached page.
This makes sense if you think about the whole output of a server control, which could be a <table> that'll disrupt all your carefully crafted markup and/or something that requires a load of <script> injected into the page - whereas injecting a single string is something that's relatively straightforward.