Dynamic control state changes lost on Postback

  • Thread starter Christophe Peillet
  • Start date
C

Christophe Peillet

I have been developing a set of custom controls that include several
dynamically generated controls inside them (for example, 'SupportTextBox'
include a textbox, a label, and a validation icon used if the field is
mandatory). These controls also support AJAX callbacks. Everything works
fine when used in 'Callback' mode, but when the controls are used as normal
form controls (non-Callback, meaning they are rendered as standard TextBoxes,
CheckBoxes, etc.), the postback destroys any changes made to the dynamically
generated controls.

For example, if the 'TextBoxText' properties initial value is set to
String.Empty, and I change this value to 'Test' on the form, and raise a
postback via a button, the value is always String.Empty when the page reloads.

I know there are some specific issues with PostBack and dynamic controls
(I've spent a week trying to understand how to fix this issue, but only run
in circles at this point).

If someone can show me (clearly) how I can maintain the state of the dynamic
controls after a postback, it would be enormously appreciated. I've read
perhaps 30 articles dealing with this in some manner or another, but have
never found something clear that address my problem. (I.e., I'm not asking
because I'm lazy and haven't tried to solve this myself).

A sample class is included below to help see where the problem may be. For
reference sake, this control (SupportTextBox) inherits from several others
objects in the hierarchy, but the relevant logic should all be contained here.

-----
[SupportTextBox.cs]
-----

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using Telerik.WebControls;

namespace Epson.EEE.Web.UI.FormControls
{
/// <summary>
/// An AJAX enabled TextBox control, which automatically applies the
Epson style guide to its appearance.
/// </summary>
/// <seealso cref="T:SupportTextBoxDesigner"/>
/// <seealso cref="T:SupportTextBoxActionList"/>
[Designer(typeof(SupportTextBoxDesigner))]
[DefaultProperty("Text")]
[DefaultEvent("TextChanged")]
[ToolboxData("<{0}:SupportTextBox runat=server></{0}:SupportTextBox>")]
public class SupportTextBox : SupportFormLabelledControl
{
#region Internal Fields

// Any third-party or external controls that you wish to add to the
this
// control, such as buttons, labels, etc., should be declared here.

// Declare any required controls/objects
private CallbackTextBox m_txt;
private CallbackLabel m_lbl;
private IconPopupControl m_icn;

#endregion

#region Event Handlers

// Declare any events that will be raised by this control.

/// <summary>
/// Fires when the textbox's text content is changed, and focus is
lost from the control. (Please note that this event only fires when <see
cref="P:SupportTextBox.CallbackEnabled"/> is set to True.)
/// </summary>
[Category("Action")]
[Description("Fires when the text is changed. (Please note that
this event only fires when CallbackEnabled is set to True.)")]
public event EventHandler TextChanged;

#endregion

#region Constructor

// Default values for properties should ONLY be defined in the the
// class constructor. If you set properties elsewhere, such as in the
// OnLoad event, you will make the control insensitive to external,
// tag-level settings.

/// <summary>
/// Initializes a new instance of the <see cref="SupportTextBox"/>
class.
/// </summary>
public SupportTextBox()
{
// Set default values
this.SetDefaultValues();
}

#endregion

#region Protected Methods (Page Events)

/// <summary>
/// Raises the <see cref="E:System.Web.UI.Control.Init"></see> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"></see> object
that contains the event data.</param>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
}

/// <summary>
/// Raises the <see cref="E:System.Web.UI.Control.Load"></see> event.
/// </summary>
/// <param name="e">The <see cref="T:System.EventArgs"></see> object
that contains the event data.</param>
protected override void OnLoad(EventArgs e)
{
// This method captures the controls Load event (declared and
bound in the
// class constructor). RunTime rendering takes place in this
method (as
// opposed to DesignMode rendering, which takes place elsewhere).

// Call base Load method
base.OnLoad(e);

EnsureChildControls();

// Make sure we are not running in DesignMode
if (!(this.DesignMode))
{
this.SetRunTimeProperties();
}
}

/// <summary>
/// Called by the ASP.NET page framework to notify server controls
that use
/// composition-based implementation to create any child controls
they contain
/// in preparation for posting back or rendering.
/// </summary>
protected override void CreateChildControls()
{
// Clear the control collection
Controls.Clear();

// Any dependant controls used in this custom control must
// be added to the control 'hierarchy'. If they are not added
// to the control collection, they will not be visible to other
// controls on the page.

// Instantiate any dependant controls
m_txt = new CallbackTextBox();
m_lbl = new CallbackLabel();
m_icn = new IconPopupControl();

// Register any events associated with dependant controls
m_txt.TextChanged += new EventHandler(TextChangedEvent);

// Add them to the control collection
Controls.Add(m_txt);
Controls.Add(m_lbl);
Controls.Add(m_icn);

// Call base method
base.CreateChildControls();
}

/// <summary>
/// Renders the contents of the control to the specified writer.
This method is used primarily by control developers.
/// </summary>
/// <param name="output">A <see
cref="T:System.Web.UI.HtmlTextWriter"></see> that represents the output
stream to render HTML content on the client.</param>
protected override void RenderContents(HtmlTextWriter output)
{
EnsureChildControls();

// Render temporary values if running in DesignMode. This
allows users
// to see the control as it will appear in the RunTime
environment.
if (this.DesignMode)
{
SetDesignTimeProperties();
}
else
{
SetRunTimeProperties();
}

// Create temporary HtmlTextWriter placeholder
StringBuilder stringBuilder = new StringBuilder();
StringWriter stringWriter = new StringWriter(stringBuilder);
HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);

// Create table to hold results
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Table);
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Tr);

// Render dependent controls
switch (this.LabelPosition)
{
case Position.Left:
// Label
htmlWriter.AddAttribute(HtmlTextWriterAttribute.Style,
"width: " + this.LabelWidth.ToString() + ";");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_lbl.RenderControl(htmlWriter);
htmlWriter.RenderEndTag();
// Textbox
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_txt.RenderControl(htmlWriter);
// Render the popup icon control if required
if (this.Required)
{
m_icn.RenderControl(htmlWriter);
}
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
break;
case Position.Top:
// Label
htmlWriter.AddAttribute(HtmlTextWriterAttribute.Style,
"width: " + this.LabelWidth.ToString() + ";");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_lbl.RenderControl(htmlWriter);
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
// Textbox
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
htmlWriter.AddAttribute(HtmlTextWriterAttribute.Colspan,
"2");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_txt.RenderControl(htmlWriter);
// Render the popup icon control if required
if (this.Required)
{
m_icn.RenderControl(htmlWriter);
}
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
break;
case Position.Right:
// Textbox
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_txt.RenderControl(htmlWriter);
// Render the popup icon control if required
if (this.Required)
{
m_icn.RenderControl(htmlWriter);
}
htmlWriter.RenderEndTag();
// Label
htmlWriter.AddAttribute(HtmlTextWriterAttribute.Style,
"width: " + this.LabelWidth.ToString() + ";");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_lbl.RenderControl(htmlWriter);
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
break;
case Position.Bottom:
// Textbox
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_txt.RenderControl(htmlWriter);
// Render the popup icon control if required
if (this.Required)
{
m_icn.RenderControl(htmlWriter);
}
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
// Label
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
htmlWriter.AddAttribute(HtmlTextWriterAttribute.Colspan,
"2");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_lbl.RenderControl(htmlWriter);
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
break;
default:
Debug.Assert(false);
break;
}

// Close table tag
htmlWriter.RenderEndTag();

// If you wish to make any modifications to the raw Html code
// before it is send to the output stream (for example, ensuring
// that the code is XHtml compliant, etc.), you can make the
// modifications to the 'rawHtml' field below. This code will
// then be sent to the real Html output stream.
string rawHtml = stringBuilder.ToString();
output.Write(rawHtml);
}

/// <summary>
/// Raised when the user changes the content of the textbox. This
event only fires when <see cref="P:SupportTextBox.CallbackEnabled"/> is set
to True.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance
containing the event data.</param>
protected void TextChangedEvent(object sender, EventArgs e)
{
// ToDo: Is there a better way to do this (update properties
automatically)
this.TextboxText = ((CallbackTextBox)sender).Text;

// Make sure we are not running in DesignMode
if (!(this.DesignMode))
{
if (!(TextChanged == null))
{
this.TextChanged(sender, new EventArgs());
}
}
}

#endregion

#region Private Methods

/// <summary>
/// Sets the control's default values.
/// </summary>
private void SetDefaultValues()
{
// Set properties to default values
this.TextboxCssClass = SharedConstants.SupportForm_TextBoxStyle;
this.TextboxMaxLength = 0;
this.TextboxReadOnly = false;
this.TextboxText = string.Empty;
}

/// <summary>
/// Sets the properties of this control when executing in a design
time environment.
/// </summary>
private void SetDesignTimeProperties()
{
// Associate dependent control properties with this control's
properties
m_lbl.CssClass = this.LabelCssClass;
m_lbl.Text = this.LabelText == string.Empty ?
m_lbl.Text = "[LabelText]" :
m_lbl.Text = this.LabelText;
m_lbl.Visible = this.LabelVisible;
m_lbl.RadControlsDir = this.ScriptsPath;
m_lbl.CallbackEnabled = this.CallbackEnabled;
m_lbl.DisableAtCallback = this.DisableAtCallback;
m_lbl.Enabled = this.Enabled;
m_txt.MaxLength = this.TextboxMaxLength;
m_txt.ReadOnly = this.TextboxReadOnly;
m_txt.RadControlsDir = this.ScriptsPath;
m_txt.DisableAtCallback = this.DisableAtCallback;
m_txt.CallbackEnabled = this.CallbackEnabled;
m_txt.CssClass = this.TextboxCssClass;
m_txt.Enabled = this.Enabled;
m_icn.ImageUrl = this.WarningImageUrl;
m_icn.ImageAlign = this.ImageAlign;
m_icn.EmptyImageUrl = this.EmptyImageUrl;
m_icn.MessageStyle = this.MessageStyle;
m_icn.PopupText = this.PopupText;
m_icn.PopupTextResourceKey = this.PopupTextResourceKey;
m_icn.PopupTitle = this.PopupTitle;
m_icn.PopupTitleResourceKey = this.PopupTitleResourceKey;
m_icn.WarningIconVisible = this.WarningIconVisible;
m_icn.Enabled = this.Enabled;
m_icn.CssClass = this.WarningIconCssStyle;
}

/// <summary>
/// Sets the properties of this control when executing in a run time
environment.
/// </summary>
private void SetRunTimeProperties()
{
// Associate dependent control properties with this control's
properties
m_lbl.CssClass = this.LabelCssClass;
m_lbl.Text = this.LabelText;
m_lbl.Visible = this.LabelVisible;
m_lbl.RadControlsDir = this.ScriptsPath;
m_lbl.CallbackEnabled = this.CallbackEnabled;
m_lbl.DisableAtCallback = this.DisableAtCallback;
m_lbl.Enabled = this.Enabled;
m_txt.MaxLength = this.TextboxMaxLength;
m_txt.ReadOnly = this.TextboxReadOnly;
m_txt.RadControlsDir = this.ScriptsPath;
m_txt.DisableAtCallback = this.DisableAtCallback;
m_txt.CallbackEnabled = this.CallbackEnabled;
m_txt.CssClass = this.TextboxCssClass;
m_txt.Enabled = this.Enabled;
m_icn.ImageUrl = this.WarningImageUrl;
m_icn.ImageAlign = this.ImageAlign;
m_icn.EmptyImageUrl = this.EmptyImageUrl;
m_icn.MessageStyle = this.MessageStyle;
m_icn.PopupText = this.PopupText;
m_icn.PopupTextResourceKey = this.PopupTextResourceKey;
m_icn.PopupTitle = this.PopupTitle;
m_icn.PopupTitleResourceKey = this.PopupTitleResourceKey;
m_icn.WarningIconVisible = this.WarningIconVisible;
m_icn.Enabled = this.Enabled;
m_icn.CssClass = this.WarningIconCssStyle;
}

#endregion

#region Protected Properties

/// <summary>
/// Gets or sets the number of controls in the Control collection
/// (used to manage a counter stored in ViewState).
/// </summary>
/// <value>The number of controls in the Control collection.</value>
protected int NumberOfControls
{
get
{
int i = (int)ViewState["NumControls"];
return i;
}
set
{
ViewState["NumControls"] = value;
}
}

#endregion

#region Public Properties

/// <summary>
/// Gets or sets the textbox CSS class.
/// </summary>
/// <value>The textbox CSS class.</value>
[Bindable(true)]
[Category("Textbox")]
[Description("The Css Class associated with the textbox.")]
[Localizable(false)]
public string TextboxCssClass
{
get
{
string s = (string)ViewState["TextboxCssClass"];
return s;
}
set
{
Debug.Assert(value != null, "Warning: TextboxCssClass
property is null!");
if (value != null)
{
ViewState["TextboxCssClass"] = value;
}
else
{
throw new NullReferenceException("TextboxCssClass can
not be assigned a null value.");
}
}
}


/// <summary>
/// Gets or sets a value indicating whether the textbox is read only.
/// </summary>
/// <value><c>true</c> if read only; otherwise, <c>false</c>.</value>
[Bindable(true)]
[Category("Textbox")]
[DefaultValue(typeof(bool), "False")]
[Description("Whether the textbox contents can be modified by users
or not.")]
[Localizable(false)]
public bool TextboxReadOnly
{
get
{
bool b = (bool)ViewState["TextboxReadOnly"];
return b;
}
set
{
ViewState["TextboxReadOnly"] = value;
}
}

/// <summary>
/// Gets or sets the textbox text.
/// </summary>
/// <value>The textbox text.</value>
[Bindable(true)]
[Category("Textbox")]
[DefaultValue(typeof(string), "")]
[Description("The text that will be displayed in the textbox.")]
[Localizable(true)]
public string TextboxText
{
get
{
string s = (string)ViewState["TextboxText"];
return s;
}
set
{
Debug.Assert(value != null, "Warning: TextboxText property
is null!");
if (value != null)
{
ViewState["TextboxText"] = value;
this.EnsureChildControls();
}
else
{
throw new NullReferenceException("TextboxText can not be
assigned a null value.");
}
}
}

/// <summary>
/// Gets or sets the maximum length (in characters) of the text box
content.
/// </summary>
/// <value>The maximum length of the text box content.</value>
[Bindable(true)]
[Category("Textbox")]
[Description("The maximum number of characters that can be entered
in the textbox.")]
[Localizable(false)]
public int TextboxMaxLength
{
get
{
int i = (int)ViewState["TextboxMaxLength"];
return i;
}
set
{
ViewState["TextboxMaxLength"] = value;
}
}

#endregion

}
}
 
T

Teemu Keiski

Hi,

with quick glance, you have child controls which handle postback? In that
case, your control should also implement INamingContainer interface, so that
postback data is correctly routed.

And what comes to those properties which wrap straight to the properties of
child controls, for example TextBoxMaxLength, wouldn't it make more sense
store it just once. If you have it like this:

public int TextboxMaxLength
{
get
{
int i = (int)ViewState["TextboxMaxLength"];
return i;
}
set
{
ViewState["TextboxMaxLength"] = value;
}
}

and it's later set also for the TextBox after viewstate tracking has started
(but before saving of viewstate), it basically is saved twice, once to the
ViewState of this proprrty and second time within ViewState of your child
control (assuming it has ViewState enabled).Therefore in "wrapper" property
wouldn't this be enough?

public int TextboxMaxLength
{
get
{
EnsureChildControls();
return m_txt.MaxLength;
}
set
{
EnsureChildControls();
m_txt.MaxLength = value;
}
}


--
Teemu Keiski
ASP.NET MVP, AspInsider
Finland, EU
http://blogs.aspadvice.com/joteke

Christophe Peillet said:
I have been developing a set of custom controls that include several
dynamically generated controls inside them (for example, 'SupportTextBox'
include a textbox, a label, and a validation icon used if the field is
mandatory). These controls also support AJAX callbacks. Everything works
fine when used in 'Callback' mode, but when the controls are used as
normal
form controls (non-Callback, meaning they are rendered as standard
TextBoxes,
CheckBoxes, etc.), the postback destroys any changes made to the
dynamically
generated controls.

For example, if the 'TextBoxText' properties initial value is set to
String.Empty, and I change this value to 'Test' on the form, and raise a
postback via a button, the value is always String.Empty when the page
reloads.

I know there are some specific issues with PostBack and dynamic controls
(I've spent a week trying to understand how to fix this issue, but only
run
in circles at this point).

If someone can show me (clearly) how I can maintain the state of the
dynamic
controls after a postback, it would be enormously appreciated. I've read
perhaps 30 articles dealing with this in some manner or another, but have
never found something clear that address my problem. (I.e., I'm not
asking
because I'm lazy and haven't tried to solve this myself).

A sample class is included below to help see where the problem may be.
For
reference sake, this control (SupportTextBox) inherits from several others
objects in the hierarchy, but the relevant logic should all be contained
here.

-----
[SupportTextBox.cs]
-----

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using Telerik.WebControls;

namespace Epson.EEE.Web.UI.FormControls
{
/// <summary>
/// An AJAX enabled TextBox control, which automatically applies the
Epson style guide to its appearance.
/// </summary>
/// <seealso cref="T:SupportTextBoxDesigner"/>
/// <seealso cref="T:SupportTextBoxActionList"/>
[Designer(typeof(SupportTextBoxDesigner))]
[DefaultProperty("Text")]
[DefaultEvent("TextChanged")]
[ToolboxData("<{0}:SupportTextBox runat=server></{0}:SupportTextBox>")]
public class SupportTextBox : SupportFormLabelledControl
{
#region Internal Fields

// Any third-party or external controls that you wish to add to the
this
// control, such as buttons, labels, etc., should be declared here.

// Declare any required controls/objects
private CallbackTextBox m_txt;
private CallbackLabel m_lbl;
private IconPopupControl m_icn;

#endregion

#region Event Handlers

// Declare any events that will be raised by this control.

/// <summary>
/// Fires when the textbox's text content is changed, and focus is
lost from the control. (Please note that this event only fires when <see
cref="P:SupportTextBox.CallbackEnabled"/> is set to True.)
/// </summary>
[Category("Action")]
[Description("Fires when the text is changed. (Please note that
this event only fires when CallbackEnabled is set to True.)")]
public event EventHandler TextChanged;

#endregion

#region Constructor

// Default values for properties should ONLY be defined in the the
// class constructor. If you set properties elsewhere, such as in
the
// OnLoad event, you will make the control insensitive to external,
// tag-level settings.

/// <summary>
/// Initializes a new instance of the <see cref="SupportTextBox"/>
class.
/// </summary>
public SupportTextBox()
{
// Set default values
this.SetDefaultValues();
}

#endregion

#region Protected Methods (Page Events)

/// <summary>
/// Raises the <see cref="E:System.Web.UI.Control.Init"></see>
event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"></see> object
that contains the event data.</param>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
}

/// <summary>
/// Raises the <see cref="E:System.Web.UI.Control.Load"></see>
event.
/// </summary>
/// <param name="e">The <see cref="T:System.EventArgs"></see>
object
that contains the event data.</param>
protected override void OnLoad(EventArgs e)
{
// This method captures the controls Load event (declared and
bound in the
// class constructor). RunTime rendering takes place in this
method (as
// opposed to DesignMode rendering, which takes place
elsewhere).

// Call base Load method
base.OnLoad(e);

EnsureChildControls();

// Make sure we are not running in DesignMode
if (!(this.DesignMode))
{
this.SetRunTimeProperties();
}
}

/// <summary>
/// Called by the ASP.NET page framework to notify server controls
that use
/// composition-based implementation to create any child controls
they contain
/// in preparation for posting back or rendering.
/// </summary>
protected override void CreateChildControls()
{
// Clear the control collection
Controls.Clear();

// Any dependant controls used in this custom control must
// be added to the control 'hierarchy'. If they are not added
// to the control collection, they will not be visible to other
// controls on the page.

// Instantiate any dependant controls
m_txt = new CallbackTextBox();
m_lbl = new CallbackLabel();
m_icn = new IconPopupControl();

// Register any events associated with dependant controls
m_txt.TextChanged += new EventHandler(TextChangedEvent);

// Add them to the control collection
Controls.Add(m_txt);
Controls.Add(m_lbl);
Controls.Add(m_icn);

// Call base method
base.CreateChildControls();
}

/// <summary>
/// Renders the contents of the control to the specified writer.
This method is used primarily by control developers.
/// </summary>
/// <param name="output">A <see
cref="T:System.Web.UI.HtmlTextWriter"></see> that represents the output
stream to render HTML content on the client.</param>
protected override void RenderContents(HtmlTextWriter output)
{
EnsureChildControls();

// Render temporary values if running in DesignMode. This
allows users
// to see the control as it will appear in the RunTime
environment.
if (this.DesignMode)
{
SetDesignTimeProperties();
}
else
{
SetRunTimeProperties();
}

// Create temporary HtmlTextWriter placeholder
StringBuilder stringBuilder = new StringBuilder();
StringWriter stringWriter = new StringWriter(stringBuilder);
HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);

// Create table to hold results
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Table);
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Tr);

// Render dependent controls
switch (this.LabelPosition)
{
case Position.Left:
// Label
htmlWriter.AddAttribute(HtmlTextWriterAttribute.Style,
"width: " + this.LabelWidth.ToString() + ";");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_lbl.RenderControl(htmlWriter);
htmlWriter.RenderEndTag();
// Textbox
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_txt.RenderControl(htmlWriter);
// Render the popup icon control if required
if (this.Required)
{
m_icn.RenderControl(htmlWriter);
}
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
break;
case Position.Top:
// Label
htmlWriter.AddAttribute(HtmlTextWriterAttribute.Style,
"width: " + this.LabelWidth.ToString() + ";");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_lbl.RenderControl(htmlWriter);
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
// Textbox
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Tr);

htmlWriter.AddAttribute(HtmlTextWriterAttribute.Colspan,
"2");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_txt.RenderControl(htmlWriter);
// Render the popup icon control if required
if (this.Required)
{
m_icn.RenderControl(htmlWriter);
}
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
break;
case Position.Right:
// Textbox
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_txt.RenderControl(htmlWriter);
// Render the popup icon control if required
if (this.Required)
{
m_icn.RenderControl(htmlWriter);
}
htmlWriter.RenderEndTag();
// Label
htmlWriter.AddAttribute(HtmlTextWriterAttribute.Style,
"width: " + this.LabelWidth.ToString() + ";");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_lbl.RenderControl(htmlWriter);
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
break;
case Position.Bottom:
// Textbox
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_txt.RenderControl(htmlWriter);
// Render the popup icon control if required
if (this.Required)
{
m_icn.RenderControl(htmlWriter);
}
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
// Label
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Tr);

htmlWriter.AddAttribute(HtmlTextWriterAttribute.Colspan,
"2");
htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
m_lbl.RenderControl(htmlWriter);
htmlWriter.RenderEndTag();
htmlWriter.RenderEndTag();
break;
default:
Debug.Assert(false);
break;
}

// Close table tag
htmlWriter.RenderEndTag();

// If you wish to make any modifications to the raw Html code
// before it is send to the output stream (for example,
ensuring
// that the code is XHtml compliant, etc.), you can make the
// modifications to the 'rawHtml' field below. This code will
// then be sent to the real Html output stream.
string rawHtml = stringBuilder.ToString();
output.Write(rawHtml);
}

/// <summary>
/// Raised when the user changes the content of the textbox.
This
event only fires when <see cref="P:SupportTextBox.CallbackEnabled"/> is
set
to True.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance
containing the event data.</param>
protected void TextChangedEvent(object sender, EventArgs e)
{
// ToDo: Is there a better way to do this (update properties
automatically)
this.TextboxText = ((CallbackTextBox)sender).Text;

// Make sure we are not running in DesignMode
if (!(this.DesignMode))
{
if (!(TextChanged == null))
{
this.TextChanged(sender, new EventArgs());
}
}
}

#endregion

#region Private Methods

/// <summary>
/// Sets the control's default values.
/// </summary>
private void SetDefaultValues()
{
// Set properties to default values
this.TextboxCssClass =
SharedConstants.SupportForm_TextBoxStyle;
this.TextboxMaxLength = 0;
this.TextboxReadOnly = false;
this.TextboxText = string.Empty;
}

/// <summary>
/// Sets the properties of this control when executing in a design
time environment.
/// </summary>
private void SetDesignTimeProperties()
{
// Associate dependent control properties with this control's
properties
m_lbl.CssClass = this.LabelCssClass;
m_lbl.Text = this.LabelText == string.Empty ?
m_lbl.Text = "[LabelText]" :
m_lbl.Text = this.LabelText;
m_lbl.Visible = this.LabelVisible;
m_lbl.RadControlsDir = this.ScriptsPath;
m_lbl.CallbackEnabled = this.CallbackEnabled;
m_lbl.DisableAtCallback = this.DisableAtCallback;
m_lbl.Enabled = this.Enabled;
m_txt.MaxLength = this.TextboxMaxLength;
m_txt.ReadOnly = this.TextboxReadOnly;
m_txt.RadControlsDir = this.ScriptsPath;
m_txt.DisableAtCallback = this.DisableAtCallback;
m_txt.CallbackEnabled = this.CallbackEnabled;
m_txt.CssClass = this.TextboxCssClass;
m_txt.Enabled = this.Enabled;
m_icn.ImageUrl = this.WarningImageUrl;
m_icn.ImageAlign = this.ImageAlign;
m_icn.EmptyImageUrl = this.EmptyImageUrl;
m_icn.MessageStyle = this.MessageStyle;
m_icn.PopupText = this.PopupText;
m_icn.PopupTextResourceKey = this.PopupTextResourceKey;
m_icn.PopupTitle = this.PopupTitle;
m_icn.PopupTitleResourceKey = this.PopupTitleResourceKey;
m_icn.WarningIconVisible = this.WarningIconVisible;
m_icn.Enabled = this.Enabled;
m_icn.CssClass = this.WarningIconCssStyle;
}

/// <summary>
/// Sets the properties of this control when executing in a run
time
environment.
/// </summary>
private void SetRunTimeProperties()
{
// Associate dependent control properties with this control's
properties
m_lbl.CssClass = this.LabelCssClass;
m_lbl.Text = this.LabelText;
m_lbl.Visible = this.LabelVisible;
m_lbl.RadControlsDir = this.ScriptsPath;
m_lbl.CallbackEnabled = this.CallbackEnabled;
m_lbl.DisableAtCallback = this.DisableAtCallback;
m_lbl.Enabled = this.Enabled;
m_txt.MaxLength = this.TextboxMaxLength;
m_txt.ReadOnly = this.TextboxReadOnly;
m_txt.RadControlsDir = this.ScriptsPath;
m_txt.DisableAtCallback = this.DisableAtCallback;
m_txt.CallbackEnabled = this.CallbackEnabled;
m_txt.CssClass = this.TextboxCssClass;
m_txt.Enabled = this.Enabled;
m_icn.ImageUrl = this.WarningImageUrl;
m_icn.ImageAlign = this.ImageAlign;
m_icn.EmptyImageUrl = this.EmptyImageUrl;
m_icn.MessageStyle = this.MessageStyle;
m_icn.PopupText = this.PopupText;
m_icn.PopupTextResourceKey = this.PopupTextResourceKey;
m_icn.PopupTitle = this.PopupTitle;
m_icn.PopupTitleResourceKey = this.PopupTitleResourceKey;
m_icn.WarningIconVisible = this.WarningIconVisible;
m_icn.Enabled = this.Enabled;
m_icn.CssClass = this.WarningIconCssStyle;
}

#endregion

#region Protected Properties

/// <summary>
/// Gets or sets the number of controls in the Control collection
/// (used to manage a counter stored in ViewState).
/// </summary>
/// <value>The number of controls in the Control
collection.</value>
protected int NumberOfControls
{
get
{
int i = (int)ViewState["NumControls"];
return i;
}
set
{
ViewState["NumControls"] = value;
}
}

#endregion

#region Public Properties

/// <summary>
/// Gets or sets the textbox CSS class.
/// </summary>
/// <value>The textbox CSS class.</value>
[Bindable(true)]
[Category("Textbox")]
[Description("The Css Class associated with the textbox.")]
[Localizable(false)]
public string TextboxCssClass
{
get
{
string s = (string)ViewState["TextboxCssClass"];
return s;
}
set
{
Debug.Assert(value != null, "Warning: TextboxCssClass
property is null!");
if (value != null)
{
ViewState["TextboxCssClass"] = value;
}
else
{
throw new NullReferenceException("TextboxCssClass can
not be assigned a null value.");
}
}
}


/// <summary>
/// Gets or sets a value indicating whether the textbox is read
only.
/// </summary>
/// <value><c>true</c> if read only; otherwise,
<c>false</c>.</value>
[Bindable(true)]
[Category("Textbox")]
[DefaultValue(typeof(bool), "False")]
[Description("Whether the textbox contents can be modified by users
or not.")]
[Localizable(false)]
public bool TextboxReadOnly
{
get
{
bool b = (bool)ViewState["TextboxReadOnly"];
return b;
}
set
{
ViewState["TextboxReadOnly"] = value;
}
}

/// <summary>
/// Gets or sets the textbox text.
/// </summary>
/// <value>The textbox text.</value>
[Bindable(true)]
[Category("Textbox")]
[DefaultValue(typeof(string), "")]
[Description("The text that will be displayed in the textbox.")]
[Localizable(true)]
public string TextboxText
{
get
{
string s = (string)ViewState["TextboxText"];
return s;
}
set
{
Debug.Assert(value != null, "Warning: TextboxText property
is null!");
if (value != null)
{
ViewState["TextboxText"] = value;
this.EnsureChildControls();
}
else
{
throw new NullReferenceException("TextboxText can not
be
assigned a null value.");
}
}
}

/// <summary>
/// Gets or sets the maximum length (in characters) of the text box
content.
/// </summary>
/// <value>The maximum length of the text box content.</value>
[Bindable(true)]
[Category("Textbox")]
[Description("The maximum number of characters that can be entered
in the textbox.")]
[Localizable(false)]
public int TextboxMaxLength
{
get
{
int i = (int)ViewState["TextboxMaxLength"];
return i;
}
set
{
ViewState["TextboxMaxLength"] = value;
}
}

#endregion

}
}
 
C

Christophe Peillet

Teemu:

You are indeed right about unecessarily duplicating the ViewState. I should
have notice that myself, but I think I've been focusing on this code so much
that I don't even see things objectively anymore. I was a bit anxious that
the ViewState would be largish, but I will go back now and see where I can
minimize it. I had previously implemented INamingContainer, but, when doing
some debugging to see what the problem was, removed it. Having added it
back, normal postbacks now seem to work as well as AJAX Callback requests.

Thanks for taking the time to suggest a fix for this.

C.
 
C

Christophe Peillet

Teemu:

It seems I do still have problems with the changes you suggested.

When I change the properties to access the underlying controls directly
(i.e., assigning to and reading from m_txt, rather than going through the
ViewState) the postback and callback all work as expected, solving one major
problem. However, now I am unable to modify the values of the properties in
the VS IDE. (When I try to change TextBoxText to another value, for example,
nothing is ever changed.

The properties that I keep in ViewState work properly, but the ones I access
directly (via the code you mentionned) are no insensitive to changes in the
IDE Property Explorer.

What is quite strange, is that the properties are INDEED being change when I
examine to HTML code, and when I render the page the new values are present,
but in the Designer in the IDE (the visual display of the page), these
modified values are not reflected.

That is to say: The html source reads as follows:
<CompanyUI:SupportTextBox ID="SupportTextBox2" runat="server"
TextboxText="test" />

You can see that the TextboxText value has been modified, and this shows up
at run time. However, in the Designer IDE, the property browser and visual
depiction still show this value as the default, which is String.Empty.

Any ideas why this might be occuring?
 
T

Teemu Keiski

Hmm, Did you try having [NotifyParentProperty(true)] attribute on the
property (top-level one)? I'm thinking if the problem is in that the
TextBox's Text property (child control) does not send change notification
up, so property browser does not get updated.

If it's possible, and you derive from TextBox (it's already a custom,
inherited control whose Text property you access via top-level property),
override Text property and append NotifyParentProperty attribute to it .
 
T

Teemu Keiski

And forgot to add, add also RefreshProperties attribute

http://www.bluevisionsoftware.com/WebSite/TipsAndTricksDetails.aspx?Name=RefreshPropertiesAttribute

Check also this for further reference:
http://msdn.microsoft.com/library/d...pgenref/html/cpconpropertyusageguidelines.asp

E.g in case previous mean is not enough, you might need to have a change
event for the property.

By the way what VS version do you have? I recall 2002 having issues with
composite control subproperties.

--
Teemu Keiski
ASP.NET MVP, AspInsider
Finland, EU
http://blogs.aspadvice.com/joteke


Teemu Keiski said:
Hmm, Did you try having [NotifyParentProperty(true)] attribute on the
property (top-level one)? I'm thinking if the problem is in that the
TextBox's Text property (child control) does not send change notification
up, so property browser does not get updated.

If it's possible, and you derive from TextBox (it's already a custom,
inherited control whose Text property you access via top-level property),
override Text property and append NotifyParentProperty attribute to it .


--
Teemu Keiski
ASP.NET MVP, AspInsider
Finland, EU
http://blogs.aspadvice.com/joteke



Christophe Peillet said:
Teemu:

It seems I do still have problems with the changes you suggested.

When I change the properties to access the underlying controls directly
(i.e., assigning to and reading from m_txt, rather than going through the
ViewState) the postback and callback all work as expected, solving one
major
problem. However, now I am unable to modify the values of the properties
in
the VS IDE. (When I try to change TextBoxText to another value, for
example,
nothing is ever changed.

The properties that I keep in ViewState work properly, but the ones I
access
directly (via the code you mentionned) are no insensitive to changes in
the
IDE Property Explorer.

What is quite strange, is that the properties are INDEED being change
when I
examine to HTML code, and when I render the page the new values are
present,
but in the Designer in the IDE (the visual display of the page), these
modified values are not reflected.

That is to say: The html source reads as follows:
<CompanyUI:SupportTextBox ID="SupportTextBox2" runat="server"
TextboxText="test" />

You can see that the TextboxText value has been modified, and this shows
up
at run time. However, in the Designer IDE, the property browser and
visual
depiction still show this value as the default, which is String.Empty.

Any ideas why this might be occuring?
 
C

Christophe Peillet

Thanks again for your response. I'm using VS2005 for these controls. I had
previously tried adding the [RefreshProperties(...)] attribute to the
property, but still have the same problem. The same with
NotifyParentProperty, but I haven't tried overriding the base class. (The
problem is that I will have to override several properties for this.) I can
try this as well.

I thought my problem may simply be that I haven't properly understood the
page model, and I'm placing the DesignTime code in the wrong place. At
present, in the RenderContents event, I am running a private method named
'SetDesignTimeProperties'. Perhaps this is the wrong place to be applying to
various properties to the control for DesignTime display. (It likely occurs
too late, and is overriding the changed settings.) Why would this change for
properties set in viewstate, and properties where I access the field itself
(i.e., m_txt.Text)?

This is the first time I have tried to develop custom controls that are more
than something very basic, so it's possible I have put the activity in the
wrong place.

If I can steal a few more minutes of you time, can you tell me where in the
page even model, in your opinion, the following actions should take place:

1.) Setting a custom control's default properties
2.) Setting a custom control's properties for display in DesignMode
3.) Setting a custom control's properties for display at RunTime
4.?) Restoring properties after a postback?

Best regards, and thank you again for your assistance. I appreciate it,
since I've found very little useful assistance on developing custom controls,
beyond overly simplistic examples/tutorials.

PS: I noticed that WinForms has a method called RaisePropertyChangedEvent,
but this doesn't seem to be available in Asp. I suspect this is the same as
the RefreshProperties(...) attribute, though.
 
C

Christophe Peillet

I have tried creating a control that overrides the relevant properties,
adding the [NotifyParentProperty(true)] attribute, but it doesn't change
anything. I suspect, as I said previously, that the problem is that I am
assign the property values to the dynamic controls at the wrong place.

C.
 
T

Teemu Keiski

If you want to set something design-time specific, you certainly would do
that in your control designer.

As you are using VS2005, I try to reproduce the problem you have (with the
code you sent in original post). I happen to have Telerik's WebControls also
so I should be able to reproduce it. This is getting hard to explain, so
better try it myself.

Replies:

1.) Setting a custom control's default properties

Approach should be that proeprties themselves return a default value when
they're not set, and DefaultValuer attriobute should point this

2.) Setting a custom control's properties for display in DesignMode

Read previous one. If they differ at design-time, using designer to generate
design-time spedific HTML, is the way.

3.) Setting a custom control's properties for display at RunTime

This goes woth what the control has set as property values. :)

4.?) Restoring properties after a postback?

ViewState takes care of that as long as controls are added to controls
collection (with child & dynamic control state), and viewstate tracking has
started. You should have it pretty much ok with the setup I saw.

By the way, I recommend asking this at ASP.NET Forums also. There are lots
of control experts around.

--
Teemu Keiski
ASP.NET MVP, AspInsider
Finland, EU

http://blogs.aspadvice.com/joteke
Christophe Peillet said:
Thanks again for your response. I'm using VS2005 for these controls. I
had
previously tried adding the [RefreshProperties(...)] attribute to the
property, but still have the same problem. The same with
NotifyParentProperty, but I haven't tried overriding the base class. (The
problem is that I will have to override several properties for this.) I
can
try this as well.

I thought my problem may simply be that I haven't properly understood the
page model, and I'm placing the DesignTime code in the wrong place. At
present, in the RenderContents event, I am running a private method named
'SetDesignTimeProperties'. Perhaps this is the wrong place to be applying
to
various properties to the control for DesignTime display. (It likely
occurs
too late, and is overriding the changed settings.) Why would this change
for
properties set in viewstate, and properties where I access the field
itself
(i.e., m_txt.Text)?

This is the first time I have tried to develop custom controls that are
more
than something very basic, so it's possible I have put the activity in the
wrong place.

If I can steal a few more minutes of you time, can you tell me where in
the
page even model, in your opinion, the following actions should take
place:

1.) Setting a custom control's default properties
2.) Setting a custom control's properties for display in DesignMode
3.) Setting a custom control's properties for display at RunTime
4.?) Restoring properties after a postback?

Best regards, and thank you again for your assistance. I appreciate it,
since I've found very little useful assistance on developing custom
controls,
beyond overly simplistic examples/tutorials.

PS: I noticed that WinForms has a method called
RaisePropertyChangedEvent,
but this doesn't seem to be available in Asp. I suspect this is the same
as
the RefreshProperties(...) attribute, though.
 
C

Christophe Peillet

If you prefer, I can send you the solution in c#, since this will be easier
for you.

If you send me an email at (e-mail address removed) I will reply with the solution.

Kevin.
 
T

Teemu Keiski

I'll reply in mail also, but for the record, due to not having all the
classes available I had to uncomment IconPopupControl and a few of those
properties which weren't implemented in the code you posted.

Basically having it like this:

//[Designer(typeof(SupportTextBoxDesigner))]
[DefaultProperty("Text")]
[DefaultEvent("TextChanged")]
[ToolboxData("<{0}:SupportTextBox runat=server></{0}:SupportTextBox>")]
public class SupportTextBox : WebControl,INamingContainer //base class
changed
{
...
//Other stuff here, uncommented so that compiles (e.g uncommented
only those which do not compile)
...
[Bindable(true)]
[Category("Textbox")]
[DefaultValue(typeof(string), "")]
[Description("The text that will be displayed in the textbox.")]
[Localizable(true)]
public string TextboxText
{
get
{
EnsureChildControls();
return m_txt.Text;

}
set
{
EnsureChildControls();
m_txt.Text = value;

}
}
}

And it works fine at design-time. So questions are: What impact does the
original base class have, and same question about the designer class
(SupportTextBoxDesigner)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top