Composite control with dynamic controls depending on a property value

M

Matt Sollars

Hi, all. I am really stuck here. I've written a few composite controls
before and fully understand the "typical" scenarios. However, this one is
far different. I understand that CreateChildControls must create the
controls in exactly the same way as it did before a post-back in order for
everything to "link" back up properly.

However, I have a composite control that creates a single label and a single
custom control. The catch is, the single custom control is determined by a
property value. This makes things very complicated, it seems. I'd like to
create the children when the property is set. I'd also like to get the
property's value BEFORE the children are re-created upon post-back. That
way, I know which custom control to create when re-creating.

Any ideas? I'm afraid I've put myself in a bit of a bind by thinking I could
pull this off.

Thank you in advance,

Matt
 
S

Steven Cheng[MSFT]

Hi Matt,

As for the composite control with dynamic created control(depending on a
certain property value) issue you mentioned, here are some of my opinions:

1. Since the dynamic's creation depend on a property of the composition
control, then where to maintain this property should be our first problem.
The creating of sub control hierarchy (when postback) is before loading
viewstate , so using ViewState to store the property may cause abit problem
in the page's sequential post back.
Alternatively, we can use a custom <intput type="hidden" > element or even
the SessionState to maintain this.But still make the problem too complex.

2. Then, if we still want to use ViewState to maintian the property, we
should do some particular changes in our CreateChildControl function.
There, we create the whole control hierarchy of the composite control, and
I think we need to make the "Dynamic control" be created everytime in
CreateChildControl, then, when we set the certain property of the server
controls, we change the "Dynamic Control"'s visible feature according to
the certain property. Thus, we can avoid struggling with "where to create
the control?" , " where to store the proerty value so that it can be always
accessable when control is being recreated"
How do you think so?
If have any other ideas , please feel free to post here. Thanks.

Regards,

Steven Cheng
Microsoft Online Support

Get Secure! www.microsoft.com/security
(This posting is provided "AS IS", with no warranties, and confers no
rights.)

Get Preview at ASP.NET whidbey
http://msdn.microsoft.com/asp.net/whidbey/default.aspx
 
M

Matt Sollars

Steven,

You make some very good points. I appreciate your input. The fact is, I
considered your second option and decided against it only to briefly
ponder your first option (a hidden input). The second option seems the
best to get this to work, but I was concerned with memory on the server,
at first. Maybe it's a moot point considering.

This is a survey tool for the government. I reduced a 50 table database
to 7 tables, by making things dynamic. The property I mentioned on this
composite control is an enumeration representing an answer type:
SingleLine, MultiLine, DropDownList, RadioButtonList, etc. The added
catch is, there are other properties that need to reference this
dynamically created control. Such as, the answer already given, choices
for multiple choice questions, and a few other layout properties.

Normally, this would really complicate things, but each of these dynamic
controls descends from a base. So, it's conceivable that I could follow
your suggested #2 option. However, I think I would need to override the
LoadViewState method in order to ensure that the AnswerType property is
read first. That way, the proper control is the only one visible and set
to an internal variable of the base type. Then, after each of the other
properties are loaded from view state, the needed properties of the
visible control can be set.

I was wondering though; since this control is in a repeater, would
several instances of it (with an instance of each child control;
although invisible) take up too much memory on the server? I guess I'll
have to find out.

Thanks again for the help, Steven.

Regards,
Matt
 
M

Matt Sollars

Steven:

I have a possible solution but I am missing something. I've been messing
with this for a couple of hours today after I replied to you this
morning.

I have changed all the composite's properties to store their values in
private fields. The AnswerType property defaults to a value of NotSet.
My CreateChildControls() method only creates the children if AnswerType
is NOT NotSet (so, something has to be set before the children are
created). Once the child control is created, an event handler is added
for the appropriate Changed event.

I have overridden SaveViewState and LoadViewState. SaveViewState stores
AnswerType first, then appends base.SaveViewState() before returning.
LoadViewState reads AnswerType first, then it sets ChildControlsCreated
to false and calls EnsureChildControls() to create the children now that
there is a valid value for AnswerType. Then, it calls base.LoadViewState
to finish up.

Finally, I have overridden the OnPreRender() method. Here, I am setting
all of the child control's properties from the private variables.

This seems to be working fantastically. Only the needed control is
created on post-backs. However, I still can't get events to fire for the
child control. I'm creating the exact same child control in the exact
same order after post-back.

Am I missing something in the timing?

Thanks a bunch,
Matt
 
S

Steven Cheng[MSFT]

Hi Matt,

Thanks for your response. Since you mentioned that you set the private
field default to NotSEt, when do you set the intial value or if you want to
do change , when will you set new value to it? In page's Init Or post back
event and does any change to the AnswerType filed will make the
createChildControl be recalled( recreate the control)?
In addition, I think it'll be more helpful if you can provide a simplified
demo which can demostrate your new solution so that I can do some tests on
my side.

Thanks.

Regards,

Steven Cheng
Microsoft Online Support

Get Secure! www.microsoft.com/security
(This posting is provided "AS IS", with no warranties, and confers no
rights.)
 
M

Matt Sollars

Hello again, Steven.

After more testing yesterday, I finally got things working. I'll admit,
it took quite a bit of studying the Page's request processing.

The solution, ultimately, was to store all property values in private
fields. The set of each property only sets the fields; it does not call
EnsureChildControls, since the AnswerType property absolutely must be
set before others can "carry through" to the dynamic child control. In
case, it is called, CreateChildControls checks that AnswerType != NotSet
before creating the child.

SaveViewState stores AnswerType and the AnswerValue, then calls the base
implementation. LoadViewState loads AnswerType and the original
AnswerValue, then calls EnsureChildControls which creates the
appropriate child control. Then, it calls the base implementation.
OnPreRender sets the child control's properties from the private fields
since everything should be created and set at this point. Finally,
OnLoad reverses this if IsPostBack is true, by setting the private
fields to the values of the child control's properties to restore the
state of the composite control and indicate any changes by the user.

The original design was relying on the appropriate Change event of the
child to indicate that a change was made. Since I couldn't get the
events to fire, I'm checking the original AnswerValue against the child
control's value after post-back for any change.

I would really like to create a scaled down demo for testing and to see
if I've created a "hack" instead of a proper solution. However, I'm
already a bit behind on 2 projects, so it may be a week or so.

Thanks for all your help and I'll get back to you soon.

Regards,
Matt
 
S

Steven Cheng[MSFT]

Hi Matt,

Thanks for your followup. Well, I'm also doing some test on my side. And
I've made a simple demo and which use public property to wrapper a private
field and when the property is reset, if the value is changed, I'll recall
the CreateChildControl to reconstruct the composite control. And I also
override the Load/SaveViewState method to handle the maintainance of the
controltype field. I've attached my demo class and test page in this
message, you can use OE client to view this messsage and get the
attachment. (I'll also paste the control's code in the bottom of this
message)Also, I find that you haven't register a email address for your
managed account, I recommend that you register one so that we can nofify
you when there is any update in your issues.

Anyway, if you have any updates , please feel free to let me know.

Thanks.

Regards,

Steven Cheng
Microsoft Online Support

Get Secure! www.microsoft.com/security
(This posting is provided "AS IS", with no warranties, and confers no
rights.)

=====================================================
[DefaultProperty("Text"),
ToolboxData("<{0}:SimpleCC runat=server></{0}:SimpleCC>")]
public class SimpleCC : System.Web.UI.WebControls.WebControl
{
private string text;
private int _type = 0;

public int Type
{
get
{
return _type;
}
set
{
if(value != _type)
{
_type = value;
this.ChildControlsCreated = false;
this.EnsureChildControls();
}
}
}

[Bindable(true),
Category("Appearance"),
DefaultValue("")]
public string Text
{
get
{
return text;
}

set
{
text = value;
}
}

/// <summary>
/// Render this control to the output parameter specified.
/// </summary>
/// <param name="output"> The HTML writer to write out to </param>
// protected override void Render(HtmlTextWriter output)
// {
// output.Write(Text);
// }
public SimpleCC()
{

}

public SimpleCC(int type)
{
if(type>= 0 && type<3)
{
this._type = type;
}
}

protected override void LoadViewState(object savedState)
{
Triplet triplet = savedState as Triplet;
_type = (int)triplet.First;

this.ChildControlsCreated = false;
this.EnsureChildControls();

base.LoadViewState (triplet.Second);
}

protected override object SaveViewState()
{
Triplet triplet = new Triplet(_type,base.SaveViewState());

return triplet;
}

protected override void CreateChildControls()
{
//base.CreateChildControls ();
Controls.Clear();
switch(_type)
{
case 0:
Label lbl = new Label();
lbl.ID = "lblDynamic";
lbl.Text = "Dynamic Label";
Controls.Add(lbl);

break;
case 1:
TextBox txt = new TextBox();
txt.ID = "txtDynamic";
txt.AutoPostBack = true;
txt.TextChanged +=new EventHandler(txt_TextChanged);
Controls.Add(txt);
break;
case 2:

Button btn = new Button();
btn.ID = "btnDynamic";
btn.Text = "Dynamic Button";
btn.Click +=new EventHandler(btn_Click);
Controls.Add(btn);
break;


}
}


protected void txt_TextChanged(object source, EventArgs argument)
{
TextBox txt = (TextBox)source;
Page.Response.Write("<br>" + txt.ID + " posted back");
}

protected void btn_Click(object source, EventArgs argument)
{
Button btn = (Button)source;
Page.Response.Write("<br>" + btn.ID + " posted back");
}


}
==================================================
 
M

Matt Sollars

Thanks for the code, Steven.

There must be something in all of my comvoluted code that was preventing
the child event from firing. I'll certainly revisit the issue in short
time. Again, thanks for the test files. They work great and we've found
a way to get this thing to work without creating all the possible
controls or using a hidden. It's a good thing.

Regards,

Matt
 

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

Forum statistics

Threads
473,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top