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

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen Pluralsight & LinkedIn Learning courses. He was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Xamarin Certified Mobile Developer and a Xamarin MVP and a Microsoft MVP.
This entry was posted in z Silverlight Archives. Bookmark the permalink.

3 Responses to Dynamically Creating User Controls That Fire Events Back To You

  1. Current research have identified a form of organic element that devices itself around the nutritional supplements
    inside the product, building them more readily absorbable.

  2. Marc Schluper says:

    @Marc Schluper
    Actually, your example does not show a memory leak. But my app, using the DataGrid DataTemplate in a DataGridTemplateColumn, did. My excuses.

  3. Marc Schluper says:

    That works. But if I click the Create button a hundred times and then click the many close buttons I end up with just the Create button and … quite a lot of memory still claimed. A System.GC.Collect() does not help …

    So for larger applications creating user control dynamically we have a problem.

    See also http://www.devtoolshed.com/silverlight-memory-leak-datagrid-dataform-datatemplate-etc

Comments are closed.