Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Two Way DataBinding with WPF/XBAP and the ADO.NET Entity Framework

0.00/5 (No votes)
14 Mar 2009 1  
An article on DataBinding in an XBAP/WPF environment with ADO.NET EF
Article_src

Introduction

With this article, I want to show some basic techniques to achieve 2-way databinding in a WPF/XBAP application.

I do not intend to clarify "huge" architect principles like NTier separation into DataLogic, BusinessLogic and UILogic (as you should be aware of as a professional developer ...), but I wanted to make things in this article as simple as possible and focus on the basic "DataBinding" principles in what may be needed by a simple XBAP/WPF application.

In this article, I will demonstrate the creation of a simple administration program for the "Category" table of the NorthWind database. The purpose of this small XBAP/WPF program is to visualize the categories, and let the user add/delete or modify category objects. Finally, I've added some sorting and filtering possibilities.

Background

The reader is supposed to have some background knowledge about databinding in WinForms, connecting to a SQL-Server dataBase and a brief knowledge of XAML.

Using the Code

1. The Model

Sample Image - maximum width is 600 pixels

First of all, we have our "Model" class, which in this case is an ADO.NET EntityFramework Entity Data Model, just containing our Categories class, as needed by the example.

To add some custom behaviour, I've created an override of the "Categories" partial class:

public partial class Categories
{
    private bool _isNew;
    public bool IsNew
    {
        get { return _isNew; }
        set { _isNew = value; }
    }

    protected override void OnPropertyChanged(string property)
    {
        base.OnPropertyChanged(property);
    }

    private Categories()
    {
    }

    public static Categories Create()
    {
        Categories c = new Categories();

        Bitmap emptyBitMap = WpfBrowserApplication1.Properties.Resources.Empty;

        MemoryStream ms = new MemoryStream();
        emptyBitMap.Save(ms, ImageFormat.Bmp);
        byte[] buffer = ms.ToArray();

        c.CategoryName = "[Enter Categoryname]";
        c.IsNew = true;
        c.Picture = buffer;

        return c;
    }
}

It's a good habit to foresee a custom method for object creating, other than letting the programmer use the standard new object() method. For this reason, I've put the base class constructor as private and provided a Create() method to add a new category. As you can see from the code, I've added some custom behaviour, like creating an empty image for each newly created category. I've also added the _isNew property, this is needed by the persistence code, when we're going to persist the newly added object to the database.

2. Userinterface Storage Properties

private static NorthwindEntities nwEntities = new NorthwindEntities();
private List<categories> _categoryList;
private List<categories> _deletedCategoryList;
private CollectionView _view;
private SortDescription _sortByCategoryName;
private SortDescription _sortByDescription;

Within our startup page (Page1.xaml), we have to initialize our data access object, namely a static reference to our NorthWindEntities, next we need some lists of type Categories to hold track of our loaded and during the session deleted Categories objects, and finally we need a CollectionView which makes a sorted and/or filtered view of our binded data.

3. Data Loading and Binding

private void Page_Loaded(object sender, RoutedEventArgs e)
{
    // Load our Category entities and put them in a locale EntityList
    this._categoryList = nwEntities.Categories.ToList();

    // Set the DataContext of the Page to our Items List
    this.DataContext = _categoryList;

    // Create a View to Navigate
    this._view = (CollectionView)CollectionViewSource.GetDefaultView(_categoryList);

    // Create some sortdescriptions for our view
    this._sortByCategoryName = new SortDescription
				("CategoryName", ListSortDirection.Ascending);
    this._sortByDescription = new SortDescription
				("Description", ListSortDirection.Ascending);


    // Used to Refresh the current record position display
    this._view.CurrentChanged += new EventHandler(_view_CurrentChanged);

    this.SetCurrentItemPointer();
}

First, we're going to load our categories into our _categoryList collection. Next we set the DataContext of our Page to our categories collection and create a view to our data. This view points to the default view of our collection, and in this way, navigation, sorting and filtering our collection is made possible. We also create some sorting properties that can be used to create a sorted view of our data. Finally, if something changes in our collection, like a user is navigation or filtering, or sorting, we want the UI to be updated.

4. Navigating

private void btnFirst_Click(object sender, RoutedEventArgs e)
{
    this._view.MoveCurrentToFirst();
}

private void btnPrev_Click(object sender, RoutedEventArgs e)
{
    if (this._view.CurrentPosition > 0)
        this._view.MoveCurrentToPrevious();

}

private void btnNext_Click(object sender, RoutedEventArgs e)
{
    if (this._view.CurrentPosition < this._view.Count - 1)
        this._view.MoveCurrentToNext();
}

private void btnLast_Click(object sender, RoutedEventArgs e)
{
    this._view.MoveCurrentToLast();
}

void _view_CurrentChanged(object sender, EventArgs e)
{
    this.SetCurrentItemPointer();
}

private void SetCurrentItemPointer()
{
    string currentPosition = Convert.ToString(_view.CurrentPosition + 1);
    this.textBlockCurrentItem.Text = currentPosition;
    this.textBlockTotalItems.Text = _view.Count.ToString();
}

Navigating our data, thus moving from one category to another ... , is a common behaviour of our application. I've coded the First, Prev, Next and Last buttons to point our data to the first, previous, next or last category of our list. Also notice that the CurrentChanged handler we created on our view will be triggered each time the user "hits" one of the previous buttons. This notification will update our recordpointer display in the UI.

5. Sorting and Filtering

private void btnSortByCategoryName_Click(object sender, RoutedEventArgs e)
{
    this._view.SortDescriptions.Clear();
    this._view.SortDescriptions.Add(this._sortByCategoryName);
    this._view.MoveCurrentToFirst();
}

private void btnSortByDescription_Click(object sender, RoutedEventArgs e)
{
    this._view.SortDescriptions.Clear();
    this._view.SortDescriptions.Add(this._sortByDescription);
    this._view.MoveCurrentToFirst();
}

private void btnResetSort_Click(object sender, RoutedEventArgs e)
{
    this._view.SortDescriptions.Clear();
    this._view.MoveCurrentToFirst();
}

private void _searchBar_TextChanged(object sender, TextChangedEventArgs e)
{
    TextBox source = e.OriginalSource as TextBox;

    if (source == null)
        return;

    switch (source.Name)
    {
        case "_searchText":

            // Filter CategoryName
            //this._view.Filter = new Predicate[object](Contains);
            FilterCategory predicate = new FilterCategory(source.Text);
            this._view.Filter = predicate.Match;

            if (this._view.Count > 0)
                this._view.MoveCurrentToFirst();

            break;
    }
}

public class FilterCategory
{
    private string _filter;

    public FilterCategory(string p_filter)
    {
        _filter = p_filter;
    }

    public Predicate[object] Match
    {
        get { return Contains; }
    }

    public bool Contains(object p_category)
    {
        Categories category = p_category as Categories;
        if (_filter == string.Empty)
        {
            return (category.CategoryName != string.Empty);
        }

        return (category.CategoryName.Contains(_filter));
    }
}

By means of the sort properties added in the beginning, sorting a CollectionView is quite easy to achieve. I've also added a simple filter, namely CategoryName. The filter code is a bit more of a challenge. In this case, I've created a filter class filtering the objectlist on the searchtext as supplied by the user and next sets the record pointer to the first object in the list and refreshes the UI.

6. Adding a New Category

private void btnAdd_Click(object sender, RoutedEventArgs e)
{
    // Create new Category and add to the Current List
    this._categoryList.Add(Categories.Create());
    this._view.MoveCurrentToLast();

}

Creating a new Category and adding it to the collectionlist is, as you can see, quite a simple process!

7. Deleting a Category

private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    // Remove the Current Item from the Current List
    if (_view.CurrentPosition > -1)
    {
        Categories c = (Categories)_view.CurrentItem;

        if (c != null)
        {
            // Add Deleted Item to the Deleted List
            if (this._deletedCategoryList == null)
            {
                this._deletedCategoryList = new List[Categories]();
            }
            this._deletedCategoryList.Add(c);
            int currentPosition = (this._view.CurrentPosition - 1);
            this._categoryList.Remove(c);
            this._view.Refresh();
            if (currentPosition > -1)
            {
                this._view.MoveCurrentToPosition(currentPosition);
            }
        }
    }
}

First we get the current item for our view and add it to the deleted items list (needed for persistence -> see later). Next we remove the object from our current list and position to the next record if any.

8. Persisting our New, Modified and Deleted Objects

private void btnSave_Click(object sender, RoutedEventArgs e)
{
    // Add Categories First
    IEnumerable[Categories] newCategories =
        this._categoryList.Where(cat => cat.IsNew == true);

    foreach (Categories newCategory in newCategories)
    {
        nwEntities.AddToCategories(newCategory);
        newCategory.IsNew = false;
    }

    // Delete Categories
    if (this._deletedCategoryList != null)
    {
        foreach (Categories deleteCategory in this._deletedCategoryList)
        {
            nwEntities.DeleteObject(deleteCategory);
        }
    }

    // Save the Changes
    nwEntities.SaveChanges();

    // Clear the Deleted Category List
    if(this._deletedCategoryList != null)
        this._deletedCategoryList.Clear();
}

Data persistency for saving the data ... is quite an easy process in EF! All SQL-Related Commands, Adapters, Connections parameters and so on, are hidden for the user, so quite readable code can be written to persist our data. The comments in the code above are self-explanatory, I think!

9. Final Words

So, thanks for reading! Hope you enjoyed it. And of course ... once more, in a professional environment, certainly in case of large projects, you should separate your code in layers, abstracting data-access in a DataLayer, UI binding and validation though a BusinessLayer, and so on ... but for educational reasons, I just kept this application as simple as possible!

History

  • 13th March, 2009: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here