Data binding of ListBox and string list in C#

DataBinding and MVVM Pattern

When dealing with GUI, usually we will use the MVC pattern, which means we manage a set of data as Model, the model will be demonstrated as View and the Contoller controls the logic of showing the data:

mvc_role_diagram

The main problem of this pattern is that most of the controller’s works are simple “get” and “set” functions between the data and the gui Like:

//Copy data to gui
for(int i=0;i<dataList.Count;i++)
{
	listbox1.Items.Add(dataList[i]);
}
//Copy gui to data
for(int i=0;i<listbox1.Count;i++)
{
	dataList.Add(listbox1.Items[i].ToString());
}

This is not very elegant.

In WPF, we use MVVM (Model–view–viewmodel) pattern, it put the Model and View in MVC pattern much closer with the help of the data binding technique:

MVVMPattern

The logic is simple: the View and the ViewModel have the same structure and they are binded together, so when I changed the ViewModel, the View will automatically present the ViewModel. Then all the “set” and “get” works are no longer needed once the View and ViewModel are binded.

DataBinding Implementation Details

Defining the DataContext

Data binding works in a DataContext, before everything is started, we must define the DataContext. DataContext must be an object, this object is the “ViewModel” of our GUI and all the data bindings will be done on this object. If the data we use are not all combined into one class but exist as our MainWindow’s properties, we set the DataContext to be the MainWindow itself:

public MainWindow()
{
      InitializeComponent();
      DataContext = this;
}

Defining the Binding Path

All the data we are going to bind to the GUI must be the properties of this DataContext.

Now we are binding some data to a ListBox in MainWindow.xaml:

<ListBox x:Name="listBox" ItemsSource="{Binding Names}"/>
<ListBox x:Name="listBox" ItemsSource="{Binding Path=Names}"/>

The two lines of xaml code above are the same, “Path” means the path of the data we are going to bind relative to DateContex, so we can understand it as binding the ItemsSource to DataContex.Names. “Names” is a property of the DataContex, in our case, it is this. Names, which means there must be a property “Names” existed in our MainWindow class:

public partial class MainWindow : Window
{
    public ObservableCollection<string> Names { get; set; }
    public MainWindow()
    {
      	  Names = new ObservableCollection<string>() { "hehe", "haha" };
    	  InitializeComponent();
      	  DataContext = this;
    }
}

Because we are binding some data to a ListBox, this data(property) must be some kinds of Collection. In order to synchronize between data and ListBox, it must be ObservableCollection, what’s more, it must be a public property and has explicit “get” and “set”.

If it is not an ObservableCollection, the ListBox can not notice its changes.

If it is not public, the ListBox cannot get access to it.

Now the program should run correctly. Except defining the DataBinding in xaml, we can also achieve the same functionality dynamically in the code:

listBox1.ItemsSource = Names;

Attention, when the ItemsSource is defined, the ListBox’s traditional data model ListBox.Items can no longer be used. The Items is now null, so when you try to do Items.Add, Items.Remove etc.. you will get an exception.

The result of data binding is you can and only can work on the data, the GUI, in our case the ListBox, will synchronize by itself and we don’t need to care about it anymore.

Bind SelectedItem

If we want to bind the ListBox’s selected item to some data, it is also possible:

The xaml way:

<ListBox x:Name="listBox" ItemsSource="{Binding Names}" SelectedItem="{Binding selectedName, Mode=TwoWay}"/>

Of couse we have to add a property named “selectedName” in our DataContex(MainWindow):

public partial class MainWindow : Window
{
    public ObservableCollection<string> Names { get; set; }
    public string selectedName { get; set; }
  	...
}

The C# way:

listBox1.SetBinding(ListBox.SelectedItemProperty, new Binding("selectedName"));

More complicated case: bind object list

Now we are binding a collection of string to a ListBox, what if we want to bind a collection of objects? Some tiny things have to be changed:

Change the data to :

public class Person
{
    public Person(string n, int a)
    {
    	Name = n;
        Age = a;
    }
    public string Name {get;set;}    
    public int Age {get;set;}    
}
public partial class MainWindow : Window
{
    public ObservableCollection<Person> Persons { get; set; }
    public string selectedName { get; set; }
    public MainWindow()
    {
        Persons = new ObservableCollection<Person>();
        Persons.Add(new Person("Lena",18));
        InitializeComponent();
        DataContext = this;
    }
}

So we can see that Person as a class of course can not be directly presented by ListBox, so we should set the ListBox’s DisplayItemPath in xaml:

<ListBox x:Name="listBox" ItemsSource="{Binding Persons}" DisplayMemberPath="Name"/>

Which means we want to display DataContext.Persons[k].Name as the ListBox’s kth item. In this case, the kth item will display this.Persons[k].Name.


All code Combined

C# Part:

public partial class MainWindow : Window
{
    public ObservableCollection<string> Names { get; set; }
    public string selectedName { get; set; }
    public MainWindow()
    {
        Names = new ObservableCollection<string>() { "hehe", "haha" };
        InitializeComponent();
        DataContext = this;
    }
}

XAML Part:

<Window x:Class="DataBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataBinding"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="400.838">
    <Grid Margin="0,0,4,-0.2">
        <ListBox x:Name="listBox" ItemsSource="{Binding Names}" SelectedItem="{Binding selectedName, Mode=TwoWay}"/>
    </Grid>
</Window>

Wangxin

I am algorithm engineer focused in computer vision, I know it will be more elegant to shut up and show my code, but I simply can't stop myself learning and explaining new things ...