9/9/11

WPF: Binding an array of objects to a ListBox

One way Windows Presentation Foundation (WPF) brings data to a user is through data binding. This post creates an object bounded example program using a WPF ListBox. The example uses a typical Model-View-Controller architecture.
Description:
Imagine receiving the task of displaying some array of objects. One approach would be to iterate over the array and perform a “toString” on each object outputting the concatenation of all strings. This is silly, let alone plain inefficient. Sure, if what needs to be displayed changes you can simply update the respective object’s toString implementation and be done. However, if the object has changed such that representing it by a simple string is not descriptive enough, then there is a big problem. A lot of pieces will have to change in order to accommodate the displaying of the array in some other way. Using the M-V-C architecture creates a helpful layering of business and visual logic specific operations giving some of the wanted flexibility.


The source code for this post is available here.
For time and simplicity, it is assumed that data has arrived at the controller from some data source. This data should be displayed to the user in a “pleasing manner.” (At least what I consider pleasing for a quick example.)
The type of data to be displayed is contained in a small object (“ClassInfo”) as defined below. For now it will only hold the name of a particular class. (We will later update it with another piece of data using a different data type.)

ClassInfo.cs:
namespace ObjectDatabindingExample
{
public class ClassInfo
{
public string Name { get; set; }
}
}

The next class definition we need to see is the controller. As mentioned earlier, it is assumed that the controller has already received the array of ClassInfo objects it needs to display. The controller class will begin small and only contain the array of ClassInfo objects. See below.

ClassController.cs:
namespace ObjectDatabindingExample
{
public class ClassController
{
#region Private Members

private readonly ClassInfo[] _data = new ClassInfo[] {
new ClassInfo() { Name = “Calculus II” },
new ClassInfo() { Name = “Physics” },
new ClassInfo() { Name = “AI Algorithms and Design Considerations” },
new ClassInfo() { Name = “Programming Patterns” },
new ClassInfo() { Name = “Computer Networks” },
new ClassInfo() { Name = “Building and Programming Robots” },
new ClassInfo() { Name = “Distributed Systems” }
};

#endregion
}
}

Thinking about this for a moment, it seems we are going to want to display this data somehow. Next up for creation is the View class. (in this case “ClassView.”) See below for the XAML, the code behind is simply default for now.

ClassView.xaml:
<Window x:Class=”ObjectDatabindingExample.ClassView”
xmlns=”http://schemas.microsoft.com/winfx/2...resentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
Title=”Object Data Binding Example” Height=”300″ Width=”300″>
<Grid>
</Grid>
</Window>

ClassView is the “View”, and ClassController is the “Controller” in the MVC architecture. The next class needed is a “Model” (in this case “ClassModel”) that will be what the viewer binds to and what the controller updates with data. See ClassModel’s initial definition below.

ClassModel.cs:
namespace ObjectDatabindingExample
{
public class ClassModel
{
}
}

Okay, this makes up the initial architecture of this small example. However, no linkage has been done between the objects/classes so nothing interesting is happening yet. We know that the controller needs to update the model with its data, and that ClassView will be bound to properties on that model. Let’s give the model a property that will store the ClassInfo objects given to it by the controller. We’ll use an ObservableCollection in the model class so that ClassView can bind directly to the property. ClassModel’s definition changes to the following.

ClassModel.cs:
using System.Collections.ObjectModel;

namespace ObjectDatabindingExample
{
public class ClassModel
{
public ObservableCollection<ClassInfo> Classes { get; set; }
}
}

Next, we know that ClassView will have to bind to and display this property. Since the title of this post says to use a ListBox, that is what we will use. ClassView’s definition will change to the following.

ClassView.xaml:
<Window x:Class=”ObjectDatabindingExample.ClassView”
xmlns=”http://schemas.microsoft.com/winfx/2...resentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
Title=”Object Data Binding Example” Height=”300″ Width=”300″>
<Grid>
<ListBox />
</Grid>
</Window>

Okay, now that we know what visible object will be bound to ClassModel, let’s move forward by having the ClassView create an instance of ClassModel to which it will bind. Currently we do not have access to our project’s code from within ClassView’s xaml. To remedy this we have to add our project’s namespace in the opening “<Window>” tag with a namespace identifier. I’m arbitrarily choosing to use “local” as the label to represent the project’s namespace. Now that we have access to the project objects, ClassView can create an instance of ClassModel in its resource dictionary. See below for changes.

ClassView.xaml:
<Window x:Class=”ObjectDatabindingExample.ClassView”
xmlns=”http://schemas.microsoft.com/winfx/2...resentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:ObjectDatabindingExample”
Title=”Object Data Binding Example” Height=”300″ Width=”300″>

<Window.Resources>
<local:ClassModel x:Key=”model” />
</Window.Resources>

<Grid>
<ListBox />
</Grid>
</Window>

Here, it is important to note a few things. When you create an object for use in a resource dictionary (as we have done with our ClassModel object) a key is required. This requirement makes sense since we will later want to retrieve the object from the dictionary.
Moving forward, in order for ClassController to modify the correct ClassModel object, ClassView will have to give its instance of ClassModel to ClassController. For our purposes, this will be done on the window loaded event. This is the first time we have to change the code behind in ClassView. We have to add an event handler in the code behind to be called after the window loads. See both definitions below.

ClassView.xaml:
<Window x:Class=”ObjectDatabindingExample.ClassView”
xmlns=”http://schemas.microsoft.com/winfx/2...resentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:ObjectDatabindingExample”
Title=”Object Data Binding Example” Height=”300″ Width=”300″ Loaded=”Window_Loaded”>

<Window.Resources>
<local:ClassModel x:Key=”model” />
</Window.Resources>

<Grid>
<ListBox />
</Grid>
</Window>


ClassView.xaml.cs:
using System.Windows;

namespace ObjectDatabindingExample
{
public partial class ClassView : Window
{
public ClassView()
{
InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
}

In ClassView.xaml.cs Window_Loaded(object, RoutedEventArgs) is called after the window has loaded, and hopefully after the ClassModel instance has been created and added to the window’s dictionary. Next we have to implement this event by retrieving the instance of ClassModel from the resources dictionary and giving it to ClassController. ClassView has to have a way to give ClassController the ClassModel object, so we will implement a Initialize(ClassModel ) method on the controller that stores a reference to the given ClassModel instance. This initialize method will be called from ClassView’s Window_Loaded event handler.

ClassView.xaml.cs:
using System.Windows;

namespace ObjectDatabindingExample
{
public partial class ClassView : Window
{
public ClassView()
{
InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
ClassModel model = this.Resources["model"] as ClassModel;
this._controller.Initialize(model);
}
catch { /* Chomp, chomp! */ }
}

#region Private Members

private ClassController _controller = new ClassController();

#endregion
}
}


ClassController.cs:
namespace ObjectDatabindingExample
{
public class ClassController
{
public void Initialize(ClassModel model)
{
this._model = model;
}

#region Private Members

private ClassModel _model;

private readonly ClassInfo[] _data = new ClassInfo[] {
new ClassInfo() { Name = “Calculus II” },
new ClassInfo() { Name = “Physics” },
new ClassInfo() { Name = “AI Algorithms and Design Considerations” },
new ClassInfo() { Name = “Programming Patterns” },
new ClassInfo() { Name = “Computer Networks” },
new ClassInfo() { Name = “Building and Programming Robots” },
new ClassInfo() { Name = “Distributed Systems” }
};

#endregion
}
}

Okay, ClassController now has a reference to the same ClassModel object to which ClassView will bind. Let us go ahead and bind the ListBox in ClassView to the ObservableCollection contained by the model instance.

ClassView.xaml:
<Window x:Class=”ObjectDatabindingExample.ClassView”
xmlns=”http://schemas.microsoft.com/winfx/2...resentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:ObjectDatabindingExample”
Title=”Object Data Binding Example” Height=”300″ Width=”300″ Loaded=”Window_Loaded”>

<Window.Resources>
<local:ClassModel x:Key=”model” />
</Window.Resources>

<Grid>
<ListBox ItemsSource=”{Binding Classes, Source={StaticResource model}}” />
</Grid>
</Window>


Since this is why we’re here, let’s dissect this statement for a moment. The statement:

<ListBox ItemsSource=”{Binding Classes, Source={StaticResource model}}” />

We have a ListBox whose source is the result of “{Binding Classes, Source={StaticResource model}}”. The first part of this (“{Binding Classes,…”) specifies that we want the ItemsSource property on the ListBox to be bound to the Classes property in the current DataContext. In order for the binding statement to refer to the Classes property on the ClassModel instance we have in ClassView’s resource dictionary, we override the current DataContext by specifying the Source attribute in the binding statement to point to the resource dictionary key of the ClassModel instance, namely “model.” “{StaticResource model}” returns an item from the static resources dictionary that is associated with the key “model.” The full statement sets up binding to the Classes property on the object in the resource dictionary whose key is “model.”
So, ClassView is now bound to the Classes property on an instance of ClassModel, and ClassController has a reference to that instance. Let’s have the controller try this whole MVC/Databinding idea out by modifying the ClassModel instance in its Initialize method. (We will simply set Classes property to a new ObservableCollection<ClassInfo> containing data from the controller.)

ClassController.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ObjectDatabindingExample
{
public class ClassController
{
public void Initialize(ClassModel model)
{
this._model = model;

// The ObservableCollection’s constructor requires a List object. Because of this
// we have to wrap the ClassInfo array in a List.
this._model.Classes = new ObservableCollection<ClassInfo>(new List<ClassInfo>(this._data));
}

#region Private Members

private ClassModel _model;

private readonly ClassInfo[] _data = new ClassInfo[] {
new ClassInfo() { Name = “Calculus II” },
new ClassInfo() { Name = “Physics” },
new ClassInfo() { Name = “AI Algorithms and Design Considerations” },
new ClassInfo() { Name = “Programming Patterns” },
new ClassInfo() { Name = “Computer Networks” },
new ClassInfo() { Name = “Building and Programming Robots” },
new ClassInfo() { Name = “Distributed Systems” }
};

#endregion
}
}


So we have the data binding done, and the controller is updating the correct model instance, however, running the application shows that something is still wrong. We don’t see any items in the ListBox. What happened is that the ClassModel object didn’t tell ClassView about the controller changing its Classes property during the call to Initialize. To remedy this, ClassModel must implement the INotifyPropertyChanged interface which allows for ClassView to register for updates to the ClassModel instance. When you setup data binding it appears that if the object being bound to implements this interface it is automatically used

ClassModel.cs:
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace ObjectDatabindingExample
{
public class ClassModel : INotifyPropertyChanged
{
public ObservableCollection<ClassInfo> Classes
{
get { return this._classes; }
set
{
this._classes = value;
this.sendPropertyChanged(“Classes”);
}
}

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

#endregion

#region Private Members

private void sendPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}

private ObservableCollection<ClassInfo> _classes;

#endregion
}
}



Alright! Our objects are being shown in the ListBox! But there is obviously a problem. The type of the object is being shown, not the data within. This is okay except that I think we would rather see the data which distinguishes the objects from each other.
So we have a problem with the way these objects are being displayed. Let’s change the way ClassView displays these ClassInfo objects by creating a DataTemplate for the ListBox to apply to its items. For simplicity we will display each item by a Label whose content is bound to a Name property. Changes to ClassView follows.

ClassView.xaml:
<Window x:Class=”ObjectDatabindingExample.ClassView”
xmlns=”http://schemas.microsoft.com/winfx/2...resentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:ObjectDatabindingExample”
Title=”Object Data Binding Example” Height=”300″ Width=”300″ Loaded=”Window_Loaded”>

<Window.Resources>
<local:ClassModel x:Key=”model” />
</Window.Resources>

<Grid>

<ListBox ItemsSource=”{Binding Classes, Source={StaticResource model}}”>

<ListBox.ItemTemplate>

<DataTemplate>

<Label Content=”{Binding Name}” />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>

This XAML is saying that we want to set the ListBox’s ItemTemplate to a DataTemplate which contains a Label whose Content is bound to the Name property of the item to which the template is being applied. In our case this data template is being applied to each of the ClassInfo objects contained by the ObservableCollection<ClassInfo> Classes property of the ClassModel object.

Bookmark and Share

0 comments:

Post a Comment

Next previous home

Cộng đồng yêu thiết kế Việt Nam Thiet ke website, danang