Some of the Windows Forms developers I've spoken to have said
that one thing they want to learn is how to dynamically create
controls in WPF and Silverlight. In this post I'll show you several
different ways to create controls at runtime using Silverlight 4
and WPF 4.
First, we'll start with how to create controls in XAML. From
there, we'll move to dynamically-loaded XAML before we take a look
at using the CLR object equivalents.
Creating Controls at Design Time in XAML
Creating controls using the design surface and/or XAML editor is
definitely the easiest way to create your UI. You can use
Expression Blend or Visual Studio, depending upon how creative you
want to be. If you want a more dynamic layout, you can hide and
show panels at runtime.
Here's an example layout:
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="First Name"
Height="19"
Margin="0,7,31,4" />
<TextBox x:Name="FirstName"
Margin="3"
Grid.Row="0"
Grid.Column="1" />
<TextBlock Text="Last Name"
Margin="0,7,6,3"
Grid.Row="1"
Height="20" />
<TextBox x:Name="LastName"
Margin="3"
Grid.Row="1"
Grid.Column="1" />
<TextBlock Text="Date of Birth"
Grid.Row="2"
Margin="0,9,0,0"
Height="21" />
<sdk:DatePicker x:Name="DateOfBirth"
Margin="3"
Grid.Row="2"
Grid.Column="1" />
<Button x:Name="SubmitChanges"
Grid.Row="3"
Grid.Column="3"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="3"
Width="80"
Height="25"
Content="Save" />
</Grid>
That markup creates a layout that looks like this in
Silverlight:
Or, if you're using WPF, like this:
(note that you'll need to remove or remap the "sdk" prefix when
using this XAML in WPF, as the date control is built-in)
Once you're familiar with working in XAML, you can easily modify
the process to load the XAML at runtime to dynamically create
controls.
Creating Controls at runtime using XAML strings
In Silverlight, this block of code in the code-behind creates
the same controls at runtime by loading the XAML from a string
using the System.Windows.Markup.XamlReader class. This class
exposes a Load method which (in Silverlight) takes a well-formed
and valid XAML string and returns back a visual tree
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
CreateControls();
}
private void CreateControls()
{
string xaml =
"<Grid Margin='10' " +
"xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " +
"xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' " +
"xmlns:sdk='http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk'>" +
"<Grid.ColumnDefinitions>" +
"<ColumnDefinition Width='100' />" +
"<ColumnDefinition Width='*' />" +
"</Grid.ColumnDefinitions>" +
"<Grid.RowDefinitions>" +
"<RowDefinition Height='Auto' />" +
"<RowDefinition Height='Auto' />" +
"<RowDefinition Height='Auto' />" +
"<RowDefinition Height='*' />" +
"</Grid.RowDefinitions>" +
"<TextBlock Text='First Name' Height='19' Margin='0,7,31,4' />" +
"<TextBox x:Name='FirstName' Margin='3' Grid.Row='0' Grid.Column='1' />" +
"<TextBlock Text='Last Name' Margin='0,7,6,3' Grid.Row='1' Height='20' />" +
"<TextBox x:Name='LastName' Margin='3' Grid.Row='1' Grid.Column='1' />" +
"<TextBlock Text='Date of Birth' Grid.Row='2' Margin='0,9,0,0' Height='21' />" +
"<sdk:DatePicker x:Name='DateOfBirth' Margin='3' Grid.Row='2' Grid.Column='1' />" +
"<Button x:Name='SubmitChanges' Grid.Row='3' Grid.Column='3' " +
"HorizontalAlignment='Right' VerticalAlignment='Top' " +
"Margin='3' Width='80' Height='25' Content='Save' />" +
"</Grid>";
UIElement tree = (UIElement)XamlReader.Load(xaml);
LayoutRoot.Children.Add(tree);
}
Note that I needed to add the namespace definitions directly in
this XAML. A chunk of XAML loaded via XamlReader.Load must be
completely self-contained and syntactically correct.
The WPF XamlReader.Load call is slightly different as it has no
overload which would take a string. Instead, it takes an XmlReader
as one form of parameter:
StringReader stringReader = new StringReader(xaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
UIElement tree = (UIElement)XamlReader.Load(xmlReader);
LayoutRoot.Children.Add(tree);
This technique also works for loading chunks of XAML from a file
on the local machine, or as the result of a database query. It's
also helpful for enabling the use of constants (like the extended
color set) that are recognized by XAML parser in Silverlight, but
not from code.
The more typical approach to dynamically creating controls,
however, is to simply use the CLR objects.
Creating Controls at runtime using Code and CLR Objects
Everything you do in XAML can also be done from code. XAML is a
representation of CLR objects, rather than a markup language that
abstracts the underlying objects. Creating controls from code tends
to be more verbose than doing the same from XAML. However, it is a
familiar approach for Windows Forms developers, and a great way to
handle dynamic UI.
private void CreateControlsUsingObjects()
{
// <Grid Margin="10">
Grid rootGrid = new Grid();
rootGrid.Margin = new Thickness(10.0);
// <Grid.ColumnDefinitions>
// <ColumnDefinition Width="100" />
// <ColumnDefinition Width="*" />
//</Grid.ColumnDefinitions>
rootGrid.ColumnDefinitions.Add(
new ColumnDefinition() { Width = new GridLength(100.0) });
rootGrid.ColumnDefinitions.Add(
new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
//<Grid.RowDefinitions>
// <RowDefinition Height="Auto" />
// <RowDefinition Height="Auto" />
// <RowDefinition Height="Auto" />
// <RowDefinition Height="*" />
//</Grid.RowDefinitions>
rootGrid.RowDefinitions.Add(
new RowDefinition() { Height = GridLength.Auto });
rootGrid.RowDefinitions.Add(
new RowDefinition() { Height = GridLength.Auto });
rootGrid.RowDefinitions.Add(
new RowDefinition() { Height = GridLength.Auto });
rootGrid.RowDefinitions.Add(
new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
//<TextBlock Text="First Name"
// Height="19"
// Margin="0,7,31,4" />
var firstNameLabel = CreateTextBlock("First Name", 19, new Thickness(0, 7, 31, 4), 0, 0);
rootGrid.Children.Add(firstNameLabel);
//<TextBox x:Name="FirstName"
// Margin="3"
// Grid.Row="0"
// Grid.Column="1" />
var firstNameField = CreateTextBox(new Thickness(3), 0, 1);
rootGrid.Children.Add(firstNameField);
//<TextBlock Text="Last Name"
// Margin="0,7,6,3"
// Grid.Row="1"
// Height="20" />
var lastNameLabel = CreateTextBlock("Last Name", 20, new Thickness(0, 7, 6, 3), 1, 0);
rootGrid.Children.Add(lastNameLabel);
//<TextBox x:Name="LastName"
// Margin="3"
// Grid.Row="1"
// Grid.Column="1" />
var lastNameField = CreateTextBox(new Thickness(3), 1, 1);
rootGrid.Children.Add(lastNameField);
//<TextBlock Text="Date of Birth"
// Grid.Row="2"
// Margin="0,9,0,0"
// Height="21" />
var dobLabel = CreateTextBlock("Date of Birth", 21, new Thickness(0, 9, 0, 0), 2, 0);
rootGrid.Children.Add(dobLabel);
//<DatePicker x:Name="DateOfBirth"
// Margin="3"
// Grid.Row="2"
// Grid.Column="1" />
DatePicker picker = new DatePicker();
picker.Margin = new Thickness(3);
Grid.SetRow(picker, 2);
Grid.SetColumn(picker, 1);
rootGrid.Children.Add(picker);
//<Button x:Name="SubmitChanges"
// Grid.Row="3"
// Grid.Column="3"
// HorizontalAlignment="Right"
// VerticalAlignment="Top"
// Margin="3"
// Width="80"
// Height="25"
// Content="Save" />
Button button = new Button();
button.HorizontalAlignment = HorizontalAlignment.Right;
button.VerticalAlignment = VerticalAlignment.Top;
button.Margin = new Thickness(3);
button.Width = 80;
button.Height = 25;
button.Content = "Save";
Grid.SetRow(button, 3);
Grid.SetColumn(button, 1);
rootGrid.Children.Add(button);
LayoutRoot.Children.Add(rootGrid);
}
private TextBlock CreateTextBlock(string text, double height, Thickness margin, int row, int column)
{
TextBlock tb = new TextBlock()
{ Text = text, Height = height, Margin = margin };
Grid.SetColumn(tb, column);
Grid.SetRow(tb, row);
return tb;
}
private TextBox CreateTextBox(Thickness margin, int row, int column)
{
TextBox tb = new TextBox() { Margin = margin };
Grid.SetColumn(tb, column);
Grid.SetRow(tb, row);
return tb;
}
You'll see the code is only slightly more verbose when expanded
out. The two helper functions help minimize that. In the code, I
create the entire branch of the visual tree before I add it to the
root. Doing this helps minimize layout cycles you'd otherwise have
if you added each item individually to the root.
I tend to put any UI interaction inside the Loaded event.
However, you could place this same code inside the constructor,
after the InitializeComponent call. As your code gets more complex,
and relies on other UI elements to be initialized and loaded,
you'll want to be smart about which function you use.
Handling Events
If you want to handle events, like button clicks, you'd do that
like any other .NET event handler:
{
Button button = new Button();
...
button.Click += new RoutedEventHandler(button_Click);
LayoutRoot.Children.Add(rootGrid);
}
void button_Click(object sender, RoutedEventArgs e)
{
...
}
Creating controls from code doesn't mean you lose the valuable
ability to data bind. In some cases, especially where the binding
source is hard to reference from XAML, binding is easier in
code.
Binding Dynamically Created Controls
We haven't used any binding yet, so we'll need to create a
binding source. For that, I created a simple shared project that
targets Silverlight 4. It's a Silverlight class library project and
is used by both the WPF and Silverlight examples. Remember,
to use it from WPF 4 (without any additions), you'll need to use a
file reference to the compiled DLL, not a project
reference.
Inside that project, I created a single ViewModel class named
ExampleViewModel.
public class ExampleViewModel : INotifyPropertyChanged
{
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; NotifyPropertyChanged("LastName"); }
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; NotifyPropertyChanged("FirstName"); }
}
private DateTime _dateOfBirth;
public DateTime DateOfBirth
{
get { return _dateOfBirth; }
set { _dateOfBirth = value; NotifyPropertyChanged("DateOfBirth"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Inside the code-behind (this is a demo, after all) of the Window
(or Page), initialize the viewmodel class:
private ExampleViewModel _vm = new ExampleViewModel();
public MainWindow()
{
_vm.LastName = "Brown";
_vm.FirstName = "Pete";
_vm.DateOfBirth = DateTime.Parse("Jan 1, 1910");
InitializeComponent();
...
}
Once that is done, we can create an example binding. I'm going
to use the First Name TextBox and set up two-way binding with the
FirstName property of the ExampleViewModel instance.
var firstNameField = CreateTextBox(new Thickness(3), 0, 1);
Binding firstNameBinding = new Binding();
firstNameBinding.Source = _vm;
firstNameBinding.Path = new PropertyPath("FirstName");
firstNameBinding.Mode = BindingMode.TwoWay;
firstNameField.SetBinding(TextBox.TextProperty, firstNameBinding);
rootGrid.Children.Add(firstNameField);
The same approach to expressing binding also works in XAML. It's
just that we have a binding markup extension that makes the process
easier.
One thing that tripped me up in this example was I passed in
TextBlock.TextProperty to the SetBinding call. That's a
valid dependency property, so it compiles just fine. In
WPF, that fails silently, even when you have verbose binding
debugging turned on. In Silverlight, it throws a catastrophic error
(without any additional information). That catastrophic error made
me look more closely at the call, ultimately leading to the
fix.
To bind controls added using dynamically-loaded XAML, you'll
need to provide a valid Name to each control you want to reference,
then use FindName after loading to get a reference to the control.
From there, you can using the Binding object and SetBinding method.
Of course, you can also embed the binding statements directly in
the XAML if you wish to do a little string manipulation.
Summary
So, we've seen that there are three different ways you can
display controls in Silverlight and WPF.
- Use the design surface / XAML Editor / Blend and create them
prior to compiling
- Load XAML at runtime
- Use CLR Objects at runtime
Each way is useful in different scenarios, and also has
different performance characteristics. XAML parsing is surprisingly
efficient and the XAML can be stored in a large text field in a
single row in a database or as a loose file on the file system, for
example.
For additional information on the layout system and the visual
tree, please see the following
The attached zip file contains source for both Silverlight 4 and
WPF 4.