Introduction
This article goes through the different ways to bind an element in WPF for VB.NET.
Background
Being new to WPF, I’ve googled and read a few different
books on how to bind controls to data but none of them seemed to be able to put
together the full picture for me:
- Some had too complex of an example or only snippets of code out of context
- Every article was binding a different way
- Almost everything was in C# and written several years ago
This is my attempt to cover all the different ways of data binding with simple examples and walk-through some of the differences in WPF vs. Windows Forms.
Using the code
Hard-coding the data in XAML
First let’s start off with an example of no binding:
- Create a new VB.Net WPF project and call it “bindtolist”
- This will give you the usual MainWindow.xaml
In MainWindow.xaml, we’ll drag a button and a listbox in the designer to the WPF window and given the listbox a name of listbox1. We’ll
add a few items in XAML directly or using the items Collection in the properties window.:
MainWindow.xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="314" Width="289.8">
<Grid>
<Button Content="Button" HorizontalAlignment="Center"
Width="75" Click="Button_Click" Margin="104,10,104.2,251"/>
<ListBox Margin="50" Name="listbox1">
<ListBoxItem>One</ListBoxItem>
<ListBoxItem>Two</ListBoxItem>
<ListBoxItem>Three</ListBoxItem>
</ListBox>
</Grid>
</Window>
If you double click the button, the
designer will create a click event for you. We can then reference the
listbox in the code-behind and add a new item. If you’re familiar with windows
forms, you should feel right at home. The designer
MainWindow.xaml.vb
Class MainWindow
Public Sub New()
InitializeComponent()
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
listbox1.Items.Add("New item")
End Sub
End Class
You can see the application will run as expected.
No Binding – with codebehind (Items.Add)
Since we want to tie the listbox to some data. Let’s remove the Listbox items and put it in the codebehind.
MainWindow.xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="314" Width="289.8">
<Grid>
<Button Content="Button" HorizontalAlignment="Center"
Width="75" Click="Button_Click" Margin="104,10,104.2,251"/>
<ListBox Margin="50" Name="listbox1"/>
</Grid>
</Window>
MainWindow.xaml.vb
Class MainWindow
Public Sub New()
InitializeComponent()
listbox1.Items.Add("One")
listbox1.Items.Add("Two")
listbox1.Items.Add("Three")
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
listbox1.Items.Add("New item")
End Sub
End Class
Now everything still looks the same but
we’ve moved the XAML code to the code behind. Now let’s move it to a list. This
is still pretty much like Windows Forms.
Binding – with codebehind (ItemsSource)
Here is where things
start to get a little different. Listbox now has a ItemsSource property, so we
can add a list all at once to a listbox.
Class MainWindow
Public lst1 As New ArrayList
Public Sub New()
InitializeComponent()
lst1.Add("one")
lst1.Add("two")
lst1.Add("three")
listbox1.ItemsSource = lst1
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
lst1.Add("New Item")
listbox1.ItemsSource = Nothing
listbox1.ItemsSource = lst1
End Sub
End Class
Now that we’ve changed
it to a list and set it, we can no longer add items. If we try we’ll get the “operation
is not valid while itemssource is in use” error. What we’d need to do is to
clear out the list and re-add it. Clearly this is a terrible way to update a
list, but I find it interesting how many different ways there are to update a
list.
Binding – with code-behind (ObservableCollection)
By changing the arraylist to an observableCollection
, we can
now update just the list and the listbox will follow! This is called a two-way
binding.
Class MainWindow
Public lst1 As New ObservableCollection(Of String)
Public Sub New()
InitializeComponent()
lst1.Add("one")
lst1.Add("two")
lst1.Add("three")
listbox1.ItemsSource = lst1
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
lst1.Add("New Item")
End Sub
End Class
XAML target binding with
codebehind (ObservableCollection) source
OK, now it becomes tricky. When we have the XAML reference
something in the code-behind, it needs to talk to a public property and not a
public variable. What’s annoying is that the application won’t error, your
listbox just won’t show anything. Also, your constructor (the New()
function),
needs to be parameter-less. You can’t even have Optional parameters. This will
become more of an issue later when we start binding to other classes. When you
starting adding bindings to the XAML code, remember the designer is pretty
buggy. Always build your code or restart the VS application if the errors start
to sound strange. When you have parameters, you should get a “not a valid constructor”
error.
Imports System.Collections.ObjectModel
Class MainWindow
Public Property lst1 As New ObservableCollection(Of String)
Public Sub New()
InitializeComponent()
lst1.Add("one")
lst1.Add("two")
lst1.Add("three")
listbox1.ItemsSource = lst1
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
lst1.Add("New Item")
End Sub
End Class
When binding, we need to tell XAML where to bind to. Since all our code is in the
MainWindow.vb class, we can just reference that class by
referencing the window itself if we give it a name. The path is the property we want to bind to.
<Window x:Class="MainWindow"
Name="w1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="314" Width="289.8">
<Grid>
<Button Content="Button" HorizontalAlignment="Center"
Width="75" Click="Button_Click" Margin="104,10,104.2,251"/>
<ListBox Margin="50" Name="listbox1" ItemsSource="{Binding ElementName=w1, Path=lst1}"/>
</Grid>
</Window>
XAML target binding with
code-behind (ObservableCollection) source
If we set the datacontext in the window to itself, we don’t need to keep specifying the ElementName
for each property we
want to use in the MainWindow
class.
<Window x:Class="MainWindow"
Name="w1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="314" Width="289.8">
<Grid>
<Button Content="Button" HorizontalAlignment="Center"
Width="75" Click="Button_Click" Margin="104,10,104.2,251"/>
<ListBox Margin="50" Name="listbox1" ItemsSource="{Binding Path=lst1}"/>
</Grid>
</Window>
We could also could have set the datacontext in the code-behind using Me.DataContext
instead of in XAML.
Imports System.Collections.ObjectModel
Class MainWindow
Public Property lst1 As New ObservableCollection(Of String)
Public Sub New()
InitializeComponent()
lst1.Add("one")
lst1.Add("two")
lst1.Add("three")
Me.DataContext = Me
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
lst1.Add("New Item")
End Sub
End Class
Binding with Namespaces
Now, by default, VB.NET doesn’t use namespaces. However, if you’ve seen any C# code, you’ve probably noticed a lot of local namespace references. The same is possible in VB.NET and
is a good intro into more complex setups.
Let’s wrap our class in a namespace. We already have an instance of MainWindow
in our XAML, so it wouldn’t make too much sense to try to create another window instance in XAML. Instead,
we should break out the list data into its own class. We can still keep everything in the same namespace. Here we use a couple of things we mentioned before:
- We need to bind to a property – we’ve kept the
lst1 property we had before
- Our New constructor still has no parameters
- A new rule: we can’t bind to subclasses
In order to add items to the lst1, we now need to reference the class instantiated in the XAML.
Imports System.Collections.ObjectModel
Namespace MW
Public Class MainWindow
Public Sub New()
InitializeComponent()
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim ld As list_data = Me.Resources("ld")
ld.lst1.Add("New Item")
End Sub
End Class
Public Class list_data
Public Property lst1 As New ObservableCollection(Of String)
Public Sub New()
lst1.Add("one")
lst1.Add("two")
lst1.Add("three")
End Sub
End Class
End Namespace
You’ll notice a few things here:
- Since
MainWindow
is now in a namespace, we need
to reference its class differently
- In order to instantiate a new class, we need to
reference its namespace using
xmlns
. We’ve given a nickname to the namespace
called local. Local is the usual convention when referring to your current
assembly namespace
- We’ve now added a resource. This is the
equivalent of creating a new
list_data
class
- Once we have our class, we can reference it in
our binding as a source and use its lst1 property
<Window x:Class="MW.MainWindow"
Name="w1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:bindtolist.MW"
Title="MainWindow" Height="314" Width="289.8">
<Window.Resources>
<local:list_data x:Key="ld"/>
</Window.Resources>
<Grid>
<Button Content="Button" HorizontalAlignment="Center"
Width="75" Click="Button_Click" Margin="104,10,104.2,251"/>
<ListBox Margin="50" Name="listbox1"
ItemsSource="{Binding Source={StaticResource ld}, Path=lst1}"/>
</Grid>
</Window>
Wow, that’s a lot of stuff. Clearly we must almost be done? Truly there is a dizzying mix of bindings. Wait till I get going! Now, where was I?
Now that we have a context that isn’t our MainWindow
class, we can embed context in other things like the grid.
<Window x:Class="MW.MainWindow"
Name="w1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:bindtolist.MW"
Title="MainWindow" Height="314" Width="289.8">
<Window.Resources>
<local:list_data x:Key="ld"/>
</Window.Resources>
<Grid DataContext="{StaticResource ld}">
<Button Content="Button" HorizontalAlignment="Center"
Width="75" Click="Button_Click" Margin="104,10,104.2,251"/>
<ListBox Margin="50" Name="listbox1" ItemsSource="{Binding Path=lst1}"/>
</Grid>
</Window>
But what if we want to instantiate and set the datacontext at the same time?
<Window x:Class="MW.MainWindow"
Name="w1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:bindtolist.MW"
Title="MainWindow" Height="314" Width="289.8">
<Window.DataContext>
<local:list_data />
</Window.DataContext>
<Grid>
<Button Content="Button" HorizontalAlignment="Center"
Width="75" Click="Button_Click" Margin="104,10,104.2,251"/>
<ListBox Margin="50" Name="listbox1" ItemsSource="{Binding Path=lst1}"/>
</Grid>
</Window>
We now reference the data_list
like this.
Imports System.Collections.ObjectModel
Namespace MW
Public Class MainWindow
Public Sub New()
InitializeComponent()
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim ld As list_data = Me.DataContext
ld.lst1.Add("New Item")
End Sub
End Class
Public Class list_data
Public Property lst1 As New ObservableCollection(Of String)
Public Sub New()
lst1.Add("one")
lst1.Add("two")
lst1.Add("three")
End Sub
End Class
End Namespace
This hopefully covers most of the different binding scenarios that you’ll see in WPF code mixing code-behinds and XAML instances without being too complicated.