During the design of the user interface, controls are declared inside their containers using XAML. However, most of the times, the quantity of controls a container will hold is known only at runtime. Moreover, controls are usually dependant on a data source, which changes dynamically during the lifecycle of the application. For most cases, data binding would be the way to go; it is the most maintainable and scalable way of adding controls in runtime; however, developers will sooner or later have the need to add controls to a container dynamically during runtime without data binding. This article shows the basics of adding controls during runtime, as well as highlighting several practices recommended when performing this kind of operations.

Note: Data-binding is the recommended best practice and this way of adding controls should be used sparingly and only when there is a real need to skip bindings.

In order to illustrate how it is possible to add controls dynamically, you will create a control that loads a list of images at runtime. For the sake of simplicity the list of images will be hard-coded; however everything is created in a way that makes it easy to add a data source later. The recipe starts by creating a default Silverlight project.

When a new Silverlight project is created, Visual Studio creates a default user control named 'Page'. The default XAML and code behind added by the Visual Studio template for the project are listed in Example 1-1 and Example 1-2.

<!--Example 1-1. Default XAML generated by Visual Studio for the 'Page' user control:-->
<UserControl x:Class="ASD.Cookbook.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">

    </Grid>
</UserControl>

 

//Example 1-2. Default C# code generated by Visual Studio for the ‘Page’ user control:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace ASD.Cookbook.Recipe0106 
{
    public partial class Page : UserControl 
    {
        public Page() 
        {
          InitializeComponent();
        }
    }
}

Following, place a StackPanel inside the Grid control generated by Visual Studio, as shown in Example 1-3. A StackPanel is used here because it handles the arrangement by itself, reducing the need for customizing the UI properties of the controls which are added dynamically.

<!--Example 1-3. A StackPanel added inside LayoutRoot:-->
<Grid x:Name="LayoutRoot" Background="White">
    <StackPanel x:Name="ImageContainer" 
                VerticalAlignment="Stretch" 
                HorizontalAlignment="Stretch" 
                Orientation="Horizontal">
    </StackPanel>
</Grid>

The recently added StackPanel is named ImageContainer. You will use this name later in code. The vertical and horizontal alignments have been set to Stretch so that the StackPanel takes all the space available. The orientation of the StackPanel has been set to horizontal so that the images stack from left to right when they are added to the panel.

Now in code create a method that returns an enumeration of images that are loaded later in the StackPanel; as shown in Example 1-4.

//Example 1-4. Method that returns the image data source:
private IEnumerable<Uri> GetImageList() 
{
    for (Int32 i = 0; i <= 10; i++) 
    {
        String uriString = 
          String.Format(
          "~/images/Image{0:00}.jpg"
          , i);
        yield return new Uri(uriString, UriKind.Relative);
    }
}

Then, use the list of URIs returned by the data source to instantiate a series of Image controls and add them to the list of children of the StackPanel, as shown in Example 1-5.

//Example 1-5. Creating instances of the Image control for each URI in the data source:
private void LoadImageList()
{
    foreach(Uri imageUri in GetImageList())
    {
        Image imageControl = new Image();
        imageControl.Stretch = Stretch.UniformToFill;
        imageControl.Width = 30;
        imageControl.Height = 32;
        imageControl.Source =
          new System.Windows.Media.Imaging.BitmapImage(imageUri);
        ImageContainer.Children.Add(imageControl);
    }
}

You need to set a fixed width and height to the image controls to make as many items visible in the space available.

Note: for the sake of simplicity the visual parameters are set in code. A better practice would be to create a style for the images in XAML and assign it when the instances are created.

Finally you need to invoke the LoadImageList method from the constructor of the Page class as shown in example Example 1-6.

//Example 1-6. Calling the LoadImageList method:
public Page() 
{
    InitializeComponent();
    LoadImageList();
}

When the application runs, the LoadImageList method is invoked at startup and the images are added to the StackPanel. Figure 1-3 shows the application while running, showing the list of images.

Figure 1-3. Images are loaded when the application runs.

Figure1-3

Although adding controls dynamically is something that you will find yourself doing at some point as a developer, there are several considerations you should have in mind when doing so:

· Each control you load dynamically uses memory and the larger your control is the more you need to be careful about the number of controls you load. Think about implementing pagination if you notice the memory usage is unusually high.

· Try to encapsulate as much as you can and keep different functionality separate from each other. Something important to note in this example is how, even when the data is generated automatically instead of loaded from a data source, the loading operation is implemented in a different method. Try to keep the code that instantiates and adds the controls separate from the rest of the application.

· Controls are initialized with their default values. If you want to set specific properties on the newly created controls, you have to do it manually on each or assign a style and/or template. This can become tedious and error prone, so if you think that you may need to set many properties from code, it is better for you to create a custom control and set the properties in XAML and instantiate the custom control dynamically.

· The separation between the UI and the logic is good, so the ability to create instances of controls and add them to the UI from code does not mean you should disregard XAML in your applications. You should try to keep this to a minimum, delegating styling, templates, positioning, sizes and arrangements to XAML whenever possible and using code only for simple instantiating and adding, if strictly necessary. Sometimes it is just not possible to use data-binding; however, think of these cases as the exceptions, not the rule.

Currently rated 3.0 by 2 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5