65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF/XBAP 与 ADO.NET Entity Framework 的双向数据绑定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (11投票s)

2009年3月14日

CPOL

4分钟阅读

viewsIcon

51715

downloadIcon

1690

一篇关于在 XBAP/WPF 环境中使用 ADO.NET EF 实现数据绑定的文章

Article_src

引言

在这篇文章中,我想展示一些在 WPF/XBAP 应用程序中实现双向数据绑定的基本技术。

我无意阐述“庞大”的架构原则,例如将应用程序分为数据逻辑业务逻辑UI 逻辑(正如您作为专业开发者应该了解的那样……),但我希望使本文中的内容尽可能简单,并专注于简单的 XBAP/WPF 应用程序可能需要的“数据绑定”基本原理。

在本文中,我将演示一个用于 NorthWind 数据库“Category”表的简单管理程序的创建。这个小型 XBAP/WPF 程序旨在可视化类别,并允许用户添加/删除或修改类别对象。最后,我添加了一些排序和过滤功能。

背景

读者应具备一些关于 WinForms 数据绑定、连接 SQL Server 数据库的背景知识,以及对 XAML 的初步了解。

Using the Code

1. 模型

Sample Image - maximum width is 600 pixels

首先,我们有“Model”类,在本例中是 ADO.NET Entity Framework 实体数据模型,仅包含示例所需的 Categories 类。

为了添加一些自定义行为,我创建了一个“Categories”部分类的重写。

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;
    }
}

除了让程序员使用标准的 new object() 方法外,预见一个自定义的对象创建方法是一个好习惯。为此,我将基类构造函数设为private ,并提供了一个 Create() 方法来添加新类别。从代码中可以看到,我添加了一些自定义行为,例如为每个新创建的类别创建一个空的 image 。我还添加了 _isNew 属性,当我们将新添加的对象持久化到数据库时,持久化代码需要此属性。

2. 用户界面存储属性

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

在我们启动页面 (Page1.xaml) 中,我们需要初始化数据访问对象,即对 NorthWindEntities 的静态引用,然后我们需要一些 Categories 类型的列表来跟踪已加载和在会话期间删除的 Categories 对象,最后我们需要一个 CollectionView ,它提供了一个已排序和/或已过滤绑定数据视图。

3. 数据加载和绑定

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();
}

首先,我们将把类别加载_categoryList 集合中。接下来,我们将页面的 DataContext 设置为我们的类别集合,并创建一个数据的视图。这个视图指向我们集合的默认视图,这样就可以导航、排序和过滤我们的集合。我们还创建了一些排序属性,可用于创建数据的排序视图。最后,如果我们的集合发生任何变化,例如用户正在导航、过滤或排序,我们希望 UI 得到更新。

4. 导航

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();
}

导航数据,即从一个类别移动到另一个类别……是我们应用程序中的常见行为。我已经为 First、Prev、Next 和 Last 按钮编写了代码,使它们指向列表中的第一个、上一个、下一个或最后一个类别。还要注意,当我们创建的视图上的 CurrentChanged 处理程序将在用户“点击”任何一个上一个按钮时触发。此通知将更新 UI 中的记录指针显示。

5. 排序和过滤

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));
    }
}

通过开始时添加的排序属性,对 CollectionView 进行排序很容易实现。我还添加了一个简单的过滤器,即 CategoryName。过滤器代码更具挑战性。在这种情况下,我创建了一个过滤器类,它根据用户提供的搜索文本来过滤对象列表,然后将记录指针设置为列表中的第一个对象并刷新 UI。

6. 添加新类别

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();

}

如你所见,创建新的 Category 并将其添加到集合列表中是一个非常简单的过程!

7. 删除类别

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);
            }
        }
    }
}

首先,我们获取视图的当前项,并将其添加到已删除项列表中(持久化需要 -> 稍后查看)。接下来,我们从当前列表中删除该对象,并在存在时定位到下一条记录。

8. 持久化我们的新、修改和删除的对象

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();
}

在 EF 中,数据持久化以保存数据……是一个非常简单的过程!所有 SQL 相关命令、适配器、连接参数等都对用户隐藏,因此可以编写非常易读的代码来持久化我们的数据。我认为上面代码中的注释是不言自明的!

9. 结语

感谢阅读!希望您喜欢。当然……再次强调,在专业环境中,尤其是在大型项目中,您应该将代码分层,将数据访问抽象到数据层,通过业务层进行 UI 绑定和验证,等等……但出于教育目的,我只是让这个应用程序尽可能简单!

历史

  • 2009年3月13日:初始版本
© . All rights reserved.