ASP.NET/C#: Figure THIS one out (Scoping? Doing things after rendering?)



  • Now I have seen some bizarre crap while coding, but this is definitely in my top 10.

    Basically, I can have a simple .aspx page generate some Controls (let's say, Buttons), then add them to a tag on the page, and they display just fine. I can also have it generate some Controls, put them in a ControlCollection, and iterate through the collection with foreach and add them to the tag's Controls collection later. That works fine too. I can also have a function in the page that generates controls, puts them in a ControlCollection, and then returns the ControlCollection back to an object in Page_Load, which then in turn iterates through them and puts them into a tag's controls. Again, works fine.

    If I create a user control (.ascx) which creates some controls and puts them into itself with this.Controls.Add(), they show up fine. If I have the user control add some controls to a Control Collection, then iterate through them and put them into itself, no problems. BUT, if I have the user control call another function that returns a Control Collection containing a few buttons, then iterate through that collection and put the controls into itself, the controls don't render. The Control objects themselves are intact, because I can count the loop iterations, check the control properties, and even see their own Controls collections just fine. But they never show up on the page. What in the world is going on here? It's all being done during Page_Load, so I can't imagine it's an issue with some objects being late for the render.

    Here's a live demo if you'd like to see it:
    http://brittens.org/aspnettest/UserControlMadness.aspx

    Source code from the two relevant .aspx.cs files immediately follows the test output.





  • It's very odd what you are describing. What I would do is check the value of this.Controls.Count after you are done adding those two buttons. Also, try adding them to a panel. I really don't do any webforms programming, so it's hard to know off the bat WHY. Also, you instanciate a new instance of a control collection, but then assign the return value of a function to that variable. Just leave it as ControlCollection controlbuttons; before the int count = 0; Have you tried using the standard method of calling a function named InitializeComponent from the usercontrol's constructor, in which you would do all of the logic you have in your page_load event? I think that might be the problem, try it out.



  • Well, I tried your suggestions, but the results are about the same. What's interesting is the value of .Controls.Count after it's done looping through the ControlCollection. The final value is 3, which would account only for the button created without the function call (labeled "User control button"), and the two info label controls beneath it. So the question then becomes, what on earth would cause the .Controls.Add() method to fail completely silently? (And I also tried checking this.Controls.Count from within the user control's constructor to make sure the controls weren't somehow falling out of scope or getting garbage collected or something insane like that. The results seemed consistent with checking it from the host page's Page_Load.)



  • I haven't tried this myself, but I'd suggest that you don't use a ControlCollection for this. Adding a Control to a ControlCollection appears to change the parent of the Control to the owner of the ControlCollection, and from what Reflector seems to indicate, the Control is removed from the previous parent's Controls collection in the process. It doesn't look like it's checking to see if the old parent and the new parent are the same, though, so I think it's removing the buttons immediately after adding them.

    Try using an array, ArrayList, or (if you're using .NET 2.0) a List<Control> instead of a ControlCollection and see if it does any better.



  • Hmm, a fair guess, but I'm not so sure. The createbuttons() function is EXACTLY the same in the user control as it is in the host page. The only differences are the buttons' label text, and that the returned collection is processed correctly in the host page, but not in the user control.

    Those buttons that say "Function-generated Button" and "Function-generated Button 2" are produced by the exact same means used to produce them inside the user control. For some reason, that approach works fine inside the host page, thus why I'm almost pulling my hair out. :)



  • I just gave it a shot myself, and it works when I change the ControlCollection to a List<Control>.

    I think the reason it's working in the page is because the ControlCollection you create is owned by the page (since you passed this into the constructor), but you're adding the controls to a Panel on the page. The problem only occurs when the control is added to the Controls collection of the same control that owns the ControlsCollection that already contains it. If you change your page to add the buttons to this.Controls instead of controlarea.Controls, you should see the same problem there, as well.



  • @mdavis said:

    I just gave it a shot myself, and it works when I change the ControlCollection to a List<Control>.

    I think the reason it's working in the page is because the ControlCollection you create is owned by the page (since you passed this into the constructor), but you're adding the controls to a Panel on the page. The problem only occurs when the control is added to the Controls collection of the same control that owns the ControlsCollection that already contains it. If you change your page to add the buttons to this.Controls instead of controlarea.Controls, you should see the same problem there, as well.



    I had a feeling you were onto something with that... I wish the MSDN docuementation was a little more thorough for the ControlCollection constructor... It makes sense though.



  • @mdavis said:

    I haven't tried this myself, but I'd suggest that you don't use a ControlCollection for this. Adding a Control to a ControlCollection appears to change the parent of the Control to the owner of the ControlCollection...



    This is 100% correct, at least in 1.1. We did a fair amount of hand-rolled Master Pages before there was such a thing, and I can attest to this.



  • Hmm, very interesting. It seems strange that the ControlCollection object would do something like removing the control from the original parent that's referenced in the constructor, rather than from the ControlCollection instance it was really in but I suppose that's really the only way for it to figure out where the control currently resides (I think). I still think it should behave a little more gracefully, though, maybe by throwing a "HeyDummyThisControlIsAlreadyInAControlCollectionRegisteredToThisObject" exception rather than doing something completely unexpected.

    Anyway, thanks for the tips guys.



  • @mdavis said:

    I just gave it a shot myself, and it works when I change the ControlCollection to a List<Control>.

    I think the reason it's working in the page is because the ControlCollection you create is owned by the page (since you passed this into the constructor), but you're adding the controls to a Panel on the page. The problem only occurs when the control is added to the Controls collection of the same control that owns the ControlsCollection that already contains it. If you change your page to add the buttons to this.Controls instead of controlarea.Controls, you should see the same problem there, as well.



    Okay, I just tried it myself for kicks, by adding the controls from the ControlCollection to a Panel inside the user control, and sure enough, they showed right up.

    But care to elaborate on this List<Control>? I'm a bit new to .NET stuff, and Google wasn't helping much here. Rotten fuzzy searches that ignore punctuation... ;) A link will suffice if you've got one.



  • @db2 said:


    But care to elaborate on this
    List<Control>? I'm a bit new to .NET stuff, and Google wasn't
    helping much here. Rotten fuzzy searches that ignore punctuation... ;)
    A link will suffice if you've got one.




    It's an example of a splendid addition to .NET 2.0, called generics.



    http://msdn2.microsoft.com/en-us/library/0x6a29h6.aspx



    In this case, it allows you to create a strongly-typed instance of a base collection class, like List, Hashtable, etc.



    In previous versions of .NET, these collections were not typesafe,
    which meant that you could cram any old type of object in there, and
    when it came time to get them out, you had to cast each object to the
    appropriate type, and if an object of the wrong type made its way in,
    you would throw an InvalidCastException.



    Long story short, List<Control> creates an instance of the List
    class that can only hold objects that are descended from the Control
    class. You could use List<String>, List<Boolean>,
    List<MyCustomObjectType>...the possibilities are, well, awesome.



  • <FONT face=Tahoma>I did encounter a similar scenario, but in my case, the controls are generated and added dynamically within the usercontrol itself and the usercontrol is generated and added dynamically to a page.

    The problem is that the usercontrol didn't render those controls at runtime (probably because of the way this logic was implemented, I don't know, but I'll check) but adding controls to user controls and user controls to page during design time was rendered correctly.

    The solution I found was to override the Render method of the usercontrol and call the RenderControl method (or something like that) for each of the controls on that Render method.

    This way, I can modify how the user control renders itself based on a specific property. And the controls are rendered everytime. :)

    Not sure if this will really help though... :/



    </FONT>



  • You might wanna try it this way ... no need for generics just yet.

    <FONT size=2><FONT color=#0000ff size=2>public</FONT><FONT size=2> UserControlButtons() {}
    </FONT></FONT><FONT color=#0000ff size=2></FONT>
    <FONT color=#0000ff size=2>protected</FONT><FONT size=2> </FONT><FONT color=#0000ff size=2>override</FONT><FONT size=2> </FONT><FONT color=#0000ff size=2>void</FONT><FONT size=2> CreateChildControls() </FONT><FONT size=2>
    {
    <FONT size=2>InitializeComponent</FONT>();
    }
    </FONT>


Log in to reply