Jesse Liberty - Silverlight Geek

By, For and About Silverlight Developers

Dynamically Creating User Controls That Fire Events Back To You

Aha! Okay, I may not fully understand all the requirements, but the following demo will show how to dynamically create a user control and then have that user control close itself, remove itself from the containing page and fire an event to the page so that the page can clean up any associated other controls that might be left laying about.

This is based on the User Control sample that goes with the video that hasn't yet been posted (you don't mind that, do you?) but will be in a couple days. I'll strip it down so as not to get hung up in the parts we don't care about.

First, let's look at the effects. When the application begins there is just a single button marked "Create".

CreateButton

Clicking on that button creates two text blocks and two user controls,

dynamicAddandRemove

In the UserControl video the User Controls are quite nicer looking but here we're interested in their ability to self-destruct; hence the close button.

When you click the close button, not only does the User Control remove itself from its parent panel's children collection, it raises an event to which the page can subscribe so that it can clean up anything else that might be lingering about; in this case the text block (which is not part of the control). Thus, if I close the upper control, I want also to remove the "Event Address" prompt.

Here's how it all works. I assume we have the custom control already and I add the button to it.  The key is to give that user control its own EventArgs type (to hold its unique ID ) and thus also give it a delegate and an event.

public partial class AddressUserControl : UserControl
{
    public class AddressEventArgs : RoutedEventArgs
    {
        public object Tag { get; private set; }
        public AddressEventArgs(object theTag)
        {
            this.Tag = theTag;
        }
    }
Notice both that AddressEventArgs is derived from RoutedEventArgs adn that it is nested within AddressUserControl (my User Control).  It has a constructor and a public property called Tag (to parallel the idea that the control itself has a Tag of type object).

We now give the control a delegate and an event

public delegate void AddressEventHandler(object o, AddressEventArgs e);
public event AddressEventHandler Closed;
The closed event is what the page will subscribe to, in order to be alerted when the control is closed. This event is fired as part of the control's handling of the button's click event,
public AddressUserControl()
{
    InitializeComponent();
    Close.Click += new RoutedEventHandler(Close_Click);
}

void Close_Click(object sender, RoutedEventArgs e)
{
    Panel parent = this.Parent as Panel;
    if (parent != null)
    {
        parent.Children.Remove(this);
        if (Closed != null && this.Tag != null)
        {
            Closed(this, new AddressEventArgs(this.Tag));
        }
    }
}

In the constructor we wire up the Close.Click; our internal handler for when the button is pressed. That handler does two things; it first makes sure we're in a panel, and if so, it removes us from the panel. It then checks to see if anyone has registered with our Closed event and that our Tag is not null; if so then it fires the Closed event to anyone who is interested.

Creating The Control Dynamically

 

All of the above falls into place when you see the control created dynamically. The XAML has nothing but the stack panel to hold the dynamically created controls,

Page.xaml

<UserControl x:Class="UserControlDemo.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:jl="clr-namespace:UserControlDemo;assembly=UserControlDemo"
    Width="600" Height="800">
    <StackPanel x:Name="MasterContainer" Background="White">
        <Button x:Name="Create" Content="Create" Width="60" Height="40" HorizontalAlignment="Left"/>
    </StackPanel>
</UserControl>
Here's how the control is created in Page.xaml.cs:
void Create_Click(object sender, RoutedEventArgs e)
{
    TextBlock tb = new TextBlock();
    tb.Text = "Event Address";
    tb.FontFamily = new FontFamily("Verdana");
    tb.FontSize = 24;
    tb.HorizontalAlignment = HorizontalAlignment.Left;
    tb.Margin = new Thickness(15, 0, 0, 0);
    tb.Tag = "1";
    MasterContainer.Children.Add(tb);

    AddressUserControl auc = new AddressUserControl();
    auc.Tag = "1";
    auc.Closed += new AddressUserControl.AddressEventHandler(auc_Closed);
    MasterContainer.Children.Add(auc);


    tb = new TextBlock();
    tb.Text = "Billing Address";
    tb.FontFamily = new FontFamily("Verdana");
    tb.FontSize = 24;
    tb.HorizontalAlignment = HorizontalAlignment.Left;
    tb.Margin = new Thickness(15, 0, 0, 0);
    tb.Tag = "2";
    MasterContainer.Children.Add(tb);

    auc = new AddressUserControl();
    auc.Tag = "2";
    auc.Closed += new AddressUserControl.AddressEventHandler(auc_Closed);
    MasterContainer.Children.Add(auc);

}
Unpacking this, we start by dynamically creating a textblock and adding it to the stack panel. We then create an AddressUserControl and assign it the same tag as the TextBlock and then we register with the user control's closed event, passing in the name of the method to be invoked when that event is raised (auc_closed). Finally, we add the user control to the stack panel.

This is repeated for the second text block and the second user control.

When the user clicks on the button, the user control takes care of removing itself from the stack panel, but it also fires the Closed event, which the page has now registered for. Per the registration, the method auc_Closed is called,

void auc_Closed(object o, AddressUserControl.AddressEventArgs e)
{
    foreach (UIElement uie in MasterContainer.Children)
    {
        TextBlock tb = uie as TextBlock;
        if (tb != null)
        {
            if (tb.Tag.ToString().Equals(e.Tag.ToString()))
            {
                MasterContainer.Children.Remove(uie);
                break;
            }
        }
    }
}

Auc_Closed iterates through the stack panel's children collection looking for textBlocks. If it finds one it checks the Tag against the tag in the AddressEventArgs (put there when the event was fired) and if they match, then it removes that text block from the stack panel as well.

Sweet.

I've put the entire source code Here

Comments

» Dynamically Creating User Controls That Fire Events Back To You Available Domains: said:

Pingback from  &raquo; Dynamically Creating User Controls That Fire Events Back To You Available Domains:

# May 7, 2008 5:50 PM

BenHayat said:

>>Sweet<<

You took the words out of my mouth...

I'm stunned! In such a short time since my response, you cooked up all these codes and explanations. My hat off to you Jesse.

Let me first thank Nick and yourself for going over board to answer my question. I'm a bit embarrassed and didn't ask for all this.

Secondly, I have a question which I also discussed it with Nick;

Here in this code which is part of the Page, you register the auc.Closed Handler:

auc.Closed += new AddressUserControl.AddressEventHandler(auc_Closed);

And here in the UserControl:

parent.Children.Remove(this);

You remove the UserControl from it's parent.children.

My question is, just by removing the child from the parent, is no indication to GC to permanently "destroy" the UserControl (auc). The only way for GC to see that, is if auc=null. However, for GC to do that it has to make sure there is no reference to that object or it's internal. So shouldn't we also do the following as part of the closing?

auc.Closed -= new AddressUserControl.AddressEventHandler(auc_Closed);

Personally, I would remove the UserControl (auc) and setting it to null, in the parent Page when responding to Closed.

# May 7, 2008 10:40 PM

jesseliberty said:

>> The only way for GC to see that, is if auc=null <<

No, there is no necessity for that. In fact, you'll notice that I reuse that reference to point to a second instance.  The reference is maintained by the colleciton and when I remove it from the collection, there is no longer a refernece to it.

I don't know if having the registration on the event handler matters; I suppose it must because the page has to be able to get to the event even though the control has been removed from its children collection. I'd have to look into the order of operations, but it would be a simple matter to deregister it while cleaning up.

In any case, no problem; it was a fun project and a good blog topic. I'll come back to it with more time and make it into a video at some point.

# May 7, 2008 11:00 PM

BenHayat said:

>>No, there is no necessity for that. In fact, you'll notice that I reuse that reference to point to a second instance.  The reference is maintained by the colleciton and when I remove it from the collection, there is no longer a refernece to it.<<

So, you're saying when we create a User Control and add the reference to Grid's collection the User Control gets created and then when we remove the reference from from the collection, the actual User Control gets destroyed.

And if I add that reference back to the collection, the user control object gets created again?

# May 7, 2008 11:22 PM

BenHayat said:

Oh wait, let me take back my previous question.

When we add a User Control to a collection (even though as part of the constructor, we pass the reference to that object to the collection (auc)), but from that point on, the collection now maintains (as you said) a new internal reference to the User Control object and then when we remove the object from collection, the internal collection reference and the User Control object are destroyed. Even though I may still have my original Reference auc hanging around, but that doesn't mean it's pointing to the old object any more. And if it is not, then GC will eventually remove the auc too.

Did I get right?

# May 7, 2008 11:33 PM

Dew Drop - May 8, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - May 8, 2008 | Alvin Ashcraft's Morning Dew

# May 8, 2008 9:19 AM

jesseliberty said:

>>Even though I may still have my original Reference auc hanging around, but that doesn't mean it's pointing to the old object any more. And if it is not, then GC will eventually remove the auc too.<<

You certainly did. Now, the question is whether I have the order of operations correct and what happens if there are no other references to that object... that is, if the GC cleans up the object is there a chance it won't fire its event, or is the fact that the page is registered with the event handler enough to keep it alive. For this, I need to make inquiries.

# May 8, 2008 11:15 AM

BenHayat said:

>>or is the fact that the page is registered with the event handler enough to keep it alive. For this, I need to make inquiries.<<

This is something that's been in my head. So I figured, just to be on the safe side, I'd do two extra step to ensure the object and it's original reference has been destroyed, one by deregetering the event and secondly setting the reference (auc) to null. Because if the user clicks on the create button again, a new object is created and is hooked up to reference and then gets added to children.

When I used to write in Delphi for Win32 (which didn't have it's own GC), we had to be very careful about leaving objects around.

Thanks to you, this blog has been very valuable [to me at least].

# May 8, 2008 5:20 PM

Mind Gravy » Blog Archive » links for 2008-05-10 said:

Pingback from  Mind Gravy  &raquo; Blog Archive   &raquo; links for 2008-05-10

# May 10, 2008 8:31 AM

Mind Gravy ?? Blog Archive ?? links for 2008-05-10 | My Geek Solutions said:

Pingback from  Mind Gravy ?? Blog Archive ?? links for 2008-05-10 | My Geek Solutions

# May 10, 2008 9:36 AM

jesseliberty said:

Ben,

I double checked, you do not have to take extra steps either to ensure that the parent page will receive the notification, nor to ensure that the control will be destroyed; simply fire the event and remove the control from the parent's collection and it will work as it should.

# May 10, 2008 4:41 PM

Mirrored Blogs said:

Post: Approved at: May-12-2008 Timeline change from Flash to Silverlight http://designwithsilverlight

# May 12, 2008 4:50 PM