Dynamically creating and destroying containers

Ben H. asked a question within a comment to a previous blog post.

Suppose, there is a button on the main form and when the user clicks on the button, a new container gets instantiated and the new container appears on the top of the main form. However, there is also a "Close" button on that container that when the user clicks on, the container closes. And here is my question. When the user clicks on the the "Close" button, I'll set the visibility of that Container to Collapsed, however I need to raise some kind of event in that Close button that the main form (who originally created the Container), will respond to this event that the container has been closed, therefore I need to remove it from the Children.Remove(Container) and then set the container to null.

I don't know how to raise that event in the "Close" button and how to respond to it in the main form.

A sample or explanation or snippet or pointer to some info is much appreciated!

I fully admit that I may not have answered this completely, but after a few minutes of noodling with it, here is what I have… which may be a good start or may be off point but perhapsinteresting anyway.

I created a page.xaml with a grid (one row, two columns) and a button,

<UserControl x:Class="DynamicControls.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Button x:Name="Create" Content="Create" 
              Width="50" Height="30" Grid.Row="0" 
              Grid.Column="0" />
    </Grid>
</UserControl>

In the code behind I created an event handler for the button,

private int id = 0;
public Page()
{
    InitializeComponent();
    Loaded += new RoutedEventHandler(Page_Loaded);
}

void Page_Loaded(object sender, RoutedEventArgs e)
{
    Create.Click +=new RoutedEventHandler(Create_Click);
}

The job of the event handler is to dynamically create a container that holds two buttons, one of which is the close button.

void  Create_Click(object sender, RoutedEventArgs e)
{

    StackPanel sp = new StackPanel();
    sp.Orientation = Orientation.Horizontal;
    sp.VerticalAlignment = VerticalAlignment.Center;
    sp.Width = 300;
    sp.Height = 500;
    sp.SetValue(Grid.RowProperty, 0);
    sp.SetValue(Grid.ColumnProperty, 1);
    sp.Tag = (++id).ToString();
    sp.Background = new SolidColorBrush(Colors.Cyan);

    Button btn = new Button();
    btn.Width = 50;
    btn.Height = 30;
    btn.Content = "Close";
    btn.Margin = new Thickness(5.0, 0, 0, 0);
    btn.Tag = id.ToString();
    btn.Click += new RoutedEventHandler(btn_Click);
    sp.Children.Add(btn);

    btn = new Button();
    btn.Width = 50;
    btn.Height = 30;
    btn.Content = "Hello";
btn.Margin = new Thickness(5.0, 0, 0, 0); btn.Click += new RoutedEventHandler(otherBtn_click); sp.Children.Add(btn); LayoutRoot.Children.Add(sp); }

 

Note that the stack panel is assigned a unique ID in its TAG property as is the button

TagProperty

We'll come back to what this is for in just a moment.  After adding the stack panel we add two buttons. One to close the stack panel, and another just to have something else in the stack panel (in this case a button that knows how to change its background color.

CreatedStackPanel

Note that each of the buttons is added to the stack panel's children collection and the stack panel itself is added to the grid's children collection.

sp.Children.Add(btn);

LayoutRoot.Children.Add(sp);

Events

Each of the buttons has its own event, and its own event handler. The second button has an event handler cleverly named "otherBtn_click" (it was late, I was tired…)

btn.Click += new RoutedEventHandler(otherBtn_click);

That event handler picks one of six colors and sets the background for the button,

void otherBtn_click(object sender, RoutedEventArgs e)
{
    int randomNumber;
    Random r = new Random();
    randomNumber = r.Next(0,5);
    List<Color> myColors = new List<Color>();
    myColors.Add(Colors.Magenta);
    myColors.Add(Colors.Purple);
    myColors.Add(Colors.Red);
    myColors.Add(Colors.Gray);
    myColors.Add(Colors.Green);
    myColors.Add(Colors.Blue);
    Button btn = sender as Button;
    if ( btn != null )
    {
        btn.Background = new SolidColorBrush(myColors[randomNumber]);
        btn.Foreground = new SolidColorBrush(Colors.Black);
    }
}

 

Close The Door!

 

The first button is the one that answers the question. Its job is to close the form.

To be explicit, when the user clicks on the Close button, we want the page to be alerted. To accomplish this, we assign an event to the button

btn.Click += new RoutedEventHandler(btn_Click);

The event handler looks through all the elements in the children of the page to find any of type stack panel. If it finds a stack panel it looks to see if the stack panel's Tag holds an ID that matches the ID of the button that fired the event. If so, it has the right stack panel and it removes it from the page's children collection and <poof> it's gone.

 

void btn_Click(object sender, RoutedEventArgs e)
{
    foreach (UIElement uie in LayoutRoot.Children)
    {
        StackPanel sp = uie as StackPanel;
        Button btn = sender as Button;
        if (sp != null && btn != null)
        {
            if (sp.Tag.ToString().Equals(btn.Tag.ToString()))
            {
                LayoutRoot.Children.Remove(uie);
                break;  
            }
        }
    }           // end foreach
}               // end btn_Click

There may be easier ways, but this works quite cleanly. As an exercise for the reader I suggest implementing this so that each click of the Create button creates a stack panel in a different grid location, so that you can have a few open and see that each close button closes the correct one.

 

 

 

Here's the complete source code

 

 

Page.xaml

<UserControl x:Class="DynamicControls.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Button x:Name="Create" Content="Create" Width="50" Height="30" Grid.Row="0" Grid.Column="0" />
    </Grid>
</UserControl>

Page.xaml.cs

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Collections.Generic;

namespace DynamicControls
{
    public partial class Page : UserControl
    {
        private int id = 0;
        public Page()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(Page_Loaded);
        }

        void Page_Loaded(object sender, RoutedEventArgs e)
        {
            Create.Click +=new RoutedEventHandler(Create_Click);
        }

        void  Create_Click(object sender, RoutedEventArgs e)
        {

            StackPanel sp = new StackPanel();
            sp.Orientation = Orientation.Horizontal;
            sp.VerticalAlignment = VerticalAlignment.Center;
            sp.Width = 300;
            sp.Height = 500;
            sp.SetValue(Grid.RowProperty, 0);
            sp.SetValue(Grid.ColumnProperty, 1);
            sp.Tag = (++id).ToString();
            sp.Background = new SolidColorBrush(Colors.Cyan);

            Button btn = new Button();
            btn.Width = 50;
            btn.Height = 30;
            btn.Content = "Close";
            btn.Margin = new Thickness(5.0, 0, 0, 0);
            btn.Tag = id.ToString();
            btn.Click += new RoutedEventHandler(btn_Click);
            sp.Children.Add(btn);

            btn = new Button();
            btn.Width = 50;
            btn.Height = 30;
            btn.Content = "Hello";
            btn.Margin = new Thickness(5.0, 0, 0, 0);
            btn.Click += new RoutedEventHandler(otherBtn_click);
            sp.Children.Add(btn);

            LayoutRoot.Children.Add(sp);

        }

        void btn_Click(object sender, RoutedEventArgs e)
        {
            foreach (UIElement uie in LayoutRoot.Children)
            {
                StackPanel sp = uie as StackPanel;
                Button btn = sender as Button;
                if (sp != null && btn != null)
                {
                    if (sp.Tag.ToString().Equals(btn.Tag.ToString()))
                    {
                        LayoutRoot.Children.Remove(uie);
                        break;  
                    }
                }
            }           // end foreach
        }               // end btn_Click

        void otherBtn_click(object sender, RoutedEventArgs e)
        {
            int randomNumber;
            Random r = new Random();
            randomNumber = r.Next(0,5);
            List<Color> myColors = new List<Color>();
            myColors.Add(Colors.Magenta);
            myColors.Add(Colors.Purple);
            myColors.Add(Colors.Red);
            myColors.Add(Colors.Gray);
            myColors.Add(Colors.Green);
            myColors.Add(Colors.Blue);
            Button btn = sender as Button;
            if ( btn != null )
            {
                btn.Background = new SolidColorBrush(myColors[randomNumber]);
                btn.Foreground = new SolidColorBrush(Colors.Black);
            }
        }

    }                   // end class
}
 

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 online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and 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 Microsoft MVP.
This entry was posted in z Silverlight Archives. Bookmark the permalink.