WPF and Silverlight 学习笔记(二十四):数据源提供器(DataProvider)
在WPF中系统提供了两个数据源提供器(DataProvider):对象数据源提供器(ObjectDataProvider)和XML数据源提供器(XmlDataProvider)。其作用类似于ASP.Net数据源(DataSource)中的对象数据源(ObjectDataSource)和Xml数据源(XmlDataSource)。其继承结构如下:
ObjectDataProvider用于处理由方法返回值所产生的数据源,其应用非常广泛,通常多层应用程序通常在界面上使用ObjectDataProvider处理由组件层所产生的数据。在本节中我们主要处理ObjectDataProvider,对于XmlDataProvider感兴趣的朋友可以参考MSDN。
一、组件端定义
例如:定义一个类库项目,在其中定义一个ProductInfo类、CategoryInfo类,用来封装Northwind数据库中的Products表及Categories表中的数据。定义NorthwindDataSet,包含Product、Category两个DataTable。定义DataControl类,处理对Northwind数据库的操作,返回相应的封装后的类型或集合作为界面显示的数据源。
1、ProductInfo类和CategoryInfo类
1: namespace WPF_24_Library
2: {
3: /// <summary>
4: /// 封装产品表的信息
5: /// </summary>
6: public class ProductInfo
7: {
8: public int ProductID
9: {
10: set; get;
11: }
12: public string ProductName
13: {
14: set; get;
15: }
16: public decimal UnitPrice
17: {
18: set; get;
19: }
20: public int CategoryID
21: {
22: set; get;
23: }
24: }
25: }
1: using System.Collections.Generic;
2:
3: namespace WPF_24_Library
4: {
5: /// <summary>
6: /// 封装类别表的信息
7: /// </summary>
8: public class CategoryInfo
9: {
10: public CategoryInfo()
11: {
12: Products = new List<ProductInfo>();
13: }
14:
15: public int CategoryID
16: {
17: set; get;
18: }
19: public string CategoryName
20: {
21: set; get;
22: }
23:
24: /// <summary>
25: /// 封装该类别的所有产品
26: /// </summary>
27: public List<ProductInfo> Products
28: {
29: private set; get;
30: }
31: }
32: }
2、类型化DataSet
此类型化DataSet由Visual Studio IDE生成:
3、DataControl类
DataControl类用来处理所有的数据库的操作,其功能分为以下几个部分:
- 基于返回集合的方法GetAllProductInfo
- 基于返回集合并带参数的方法GetProductInfoByCategoryID
- 基于返回带主从关联数据的集合的方法GetAllCategoriesWithProducts
- 基于返回带主从关联数据的类型化DataSet的方法GetNorthwindDataSet
1: using System.Collections.Generic;
2: using System.Data;
3: using System.Data.SqlClient;
4:
5: namespace WPF_24_Library
6: {
7: public static class DataControl
8: {
9: // 连接字符串
10: private const string CONNECTION_STRING =
11: @"Server=.;Integrated Security=SSPI;Database=Northwind";
12:
13: // 所使用的各存储过程的名称
14: private const string SQL_GETALLPRODUCTINFO = "sp_GetAllProductInfo";
15: private const string SQL_GETPRODUCTINFOBYCATEGORYID = "sp_GetProductInfoByCategoryID";
16: private const string SQL_GETALLCATEGORIES = "sp_GetAllCategories";
17:
18: /// <summary>
19: /// 获取所有的产品
20: /// </summary>
21: /// <returns></returns>
22: public static List<ProductInfo> GetAllProductInfo()
23: {
24: SqlCommand command = new SqlCommand();
25: command.CommandType = CommandType.StoredProcedure;
26: command.CommandText = SQL_GETALLPRODUCTINFO;
27:
28: List<ProductInfo> result = GetProducts(command);
29:
30: return result;
31: }
32:
33: /// <summary>
34: /// 根据产品类别获取此类别的产品
35: /// </summary>
36: /// <param name="categoryID"></param>
37: /// <returns></returns>
38: public static List<ProductInfo> GetProductInfoByCategoryID(int categoryID)
39: {
40: SqlCommand command = new SqlCommand();
41: command.CommandType = CommandType.StoredProcedure;
42: command.CommandText = SQL_GETPRODUCTINFOBYCATEGORYID;
43:
44: command.Parameters.Add("@categoryID", SqlDbType.Int).Value = categoryID;
45:
46: List<ProductInfo> result = GetProducts(command);
47:
48: return result;
49: }
50:
51: /// <summary>
52: /// 封装产品数据
53: /// </summary>
54: /// <param name="command"></param>
55: /// <returns></returns>
56: private static List<ProductInfo> GetProducts(SqlCommand command)
57: {
58: SqlConnection connection = new SqlConnection();
59: connection.ConnectionString = CONNECTION_STRING;
60: connection.Open();
61:
62: command.Connection = connection;
63:
64: SqlDataReader dataReader = command.ExecuteReader();
65: List<ProductInfo> result = new List<ProductInfo>();
66: while (dataReader.Read())
67: {
68: int id = dataReader.GetInt32(0);
69: string name = dataReader.GetString(1);
70: int categoryID = dataReader.GetInt32(2);
71: decimal unitprice = dataReader.GetDecimal(3);
72:
73: ProductInfo info = new ProductInfo()
74: {
75: ProductID = id,
76: ProductName = name,
77: CategoryID = categoryID,
78: UnitPrice = unitprice
79: };
80: result.Add(info);
81: }
82: dataReader.Close();
83: connection.Close();
84:
85: return result;
86: }
87:
88: /// <summary>
89: /// 获取所有的类别及该类别的产品
90: /// </summary>
91: /// <returns></returns>
92: public static List<CategoryInfo> GetAllCategoriesWithProducts()
93: {
94: SqlConnection connection = new SqlConnection();
95: connection.ConnectionString = CONNECTION_STRING;
96: connection.Open();
97:
98: SqlCommand command = new SqlCommand();
99: command.CommandType = CommandType.StoredProcedure;
100: command.CommandText = SQL_GETALLCATEGORIES;
101:
102: SqlDataReader dataReader = command.ExecuteReader();
103: List<CategoryInfo> result = new List<CategoryInfo>();
104: while (dataReader.Read())
105: {
106: int id = dataReader.GetInt32(0);
107: string name = dataReader.GetString(1);
108:
109: CategoryInfo info = new CategoryInfo()
110: {
111: CategoryID = id,
112: CategoryName = name,
113: };
114: result.Add(info);
115: }
116: dataReader.Close();
117: connection.Close();
118:
119: foreach (CategoryInfo info in result)
120: {
121: info.Products.AddRange(
122: GetProductInfoByCategoryID(info.CategoryID));
123: }
124:
125: return result;
126: }
127:
128: /// <summary>
129: /// 获取封装所有类别及产品的DataSet
130: /// </summary>
131: /// <returns></returns>
132: public static NorthwindDataSet GetNorthwindDataSet()
133: {
134: SqlDataAdapter categoryAdapter = new SqlDataAdapter(
135: SQL_GETALLCATEGORIES, CONNECTION_STRING);
136: categoryAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
137:
138: SqlDataAdapter productAdapter = new SqlDataAdapter(
139: SQL_GETALLPRODUCTINFO, CONNECTION_STRING);
140: productAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
141:
142: NorthwindDataSet result = new NorthwindDataSet();
143:
144: categoryAdapter.Fill(result.Categories);
145: productAdapter.Fill(result.Products);
146:
147: return result;
148: }
149: }
150: }
二、基本ObjectDataProvider操作
(将上部分类库项目引入WPF应用程序项目中)
使用ObjectDataProvider时需提供以下几个部分:
- x:Key:此Provider对应的资源的名称,将在界面某组件的DataContext引用,以作为数据源
- ObjectType:包含返回数据源的方法的类,其语法上是这样的:ObjectType=”{x:Type 类名}”,此外还需要引用相应的命名空间
- MethodName:返回数据源的方法的方法名
例如:将GetAllProductInfo方法返回的List<ProductInfo>泛型集合做为数据源,绑定到ListBox上:
1: <Window x:Class="WPF_24.WinObjectDataProviderDemo1"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:lib="clr-namespace:WPF_24_Library;assembly=WPF_24_Library"
5: Title="ObjectDataProvider Demo 1" Height="300" Width="300">
6: <Window.Resources>
7: <ObjectDataProvider x:Key="productInfoSource"
8: ObjectType="{x:Type lib:DataControl}"
9: MethodName="GetAllProductInfo" />
10: </Window.Resources>
11: <Grid>
12: <ListBox Margin="5"
13: DataContext="{StaticResource productInfoSource}"
14: ItemsSource="{Binding}"
15: DisplayMemberPath="ProductName"
16: SelectedValuePath="ProductID" />
17: </Grid>
18: </Window>
此处的DataContext可以写在ListBox中,好可以写在Grid或Window的DataContext中。在ListBox中ItemsSource的值为默认的Binding,未定义Path,即直接将方法返回的List集合的结果显示在ListBox中。执行的结果如下:
二、为ObjectDataProvider设置参数
在ObjectDataProvider的MethodParameters中可以添加ObjectDataProvider的参数,例如使用GetProductInfoByCategoryID,根据类别查询产品:
1: <Window x:Class="WPF_24.WinObjectDataProviderDemo2"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:lib="clr-namespace:WPF_24_Library;assembly=WPF_24_Library"
5: xmlns:System="clr-namespace:System;assembly=mscorlib"
6: Title="WinObjectDataProviderDemo2" Height="300" Width="300">
7: <Window.Resources>
8: <ObjectDataProvider x:Key="productInfoByCategoryIDSource"
9: ObjectType="{x:Type lib:DataControl}"
10: MethodName="GetProductInfoByCategoryID">
11: <ObjectDataProvider.MethodParameters>
12: <System:Int32>0</System:Int32>
13: </ObjectDataProvider.MethodParameters>
14: </ObjectDataProvider>
15: </Window.Resources>
16: <Grid>
17: <Grid.RowDefinitions>
18: <RowDefinition Height="23" />
19: <RowDefinition />
20: </Grid.RowDefinitions>
21: <StackPanel Grid.Row="0" Orientation="Horizontal"
22: HorizontalAlignment="Center" VerticalAlignment="Center" >
23: <TextBlock Margin="5,0" Text="CategoryID:" VerticalAlignment="Center" />
24: <TextBox Margin="5,0" Width="80" Text="0" x:Name="txtCategoryID" />
25: <Button Margin="5,0" Width="50" Content="OK" Click="Button_Click" />
26: </StackPanel>
27: <ListBox Grid.Row="1"
28: DataContext="{StaticResource productInfoByCategoryIDSource}"
29: ItemsSource="{Binding}"
30: DisplayMemberPath="ProductName"
31: SelectedValuePath="ProductID" />
32: </Grid>
33: </Window>
此外,可以通过代码更改参数的值:
1: private void Button_Click(object sender, RoutedEventArgs e)
2: {
3: int categoryID = int.Parse(txtCategoryID.Text);
4:
5: ObjectDataProvider provider =
6: (ObjectDataProvider)(this.FindResource("productInfoByCategoryIDSource"));
7: provider.MethodParameters[0] = categoryID;
8: }
三、带主从关联数据的数据显示
所谓的“主从数据”所指的例如上图中,左侧为所有的类别,右侧为当前选中类别的所有产品,其实现的XAML如下:
1: <Window x:Class="WPF_24.WinObjectDataProviderDemo4"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:lib="clr-namespace:WPF_24_Library;assembly=WPF_24_Library"
5: Title="WinObjectDataProviderDemo4" Height="300" Width="500">
6: <Window.Resources>
7: <ObjectDataProvider x:Key="myCategories"
8: ObjectType="{x:Type lib:DataControl}"
9: MethodName="GetAllCategoriesWithProducts" />
10: </Window.Resources>
11: <Grid DataContext="{StaticResource myCategories}">
12: <Grid.ColumnDefinitions>
13: <ColumnDefinition Width="2*" />
14: <ColumnDefinition Width="3*" />
15: </Grid.ColumnDefinitions>
16: <ListBox Grid.Column="0"
17: ItemsSource="{Binding}"
18: DisplayMemberPath="CategoryName"
19: SelectedValuePath="CategoryID"
20: SelectedIndex="0"
21: IsSynchronizedWithCurrentItem="True" />
22: <ListBox Grid.Column="1"
23: ItemsSource="{Binding Path=Products}"
24: DisplayMemberPath="ProductName"
25: SelectedValuePath="ProductID" />
26: </Grid>
27: </Window>
需要注意的是,默认情况下,ListBox不会同步数据源当前项的变更改,需要使用第21行的代码,以实现点击左侧的ListBox的某一项,同时更改右侧该类别的产品。
四、使用DataSet做为数据源
将DataSet做为数据源时大体与集合相同,但要注意,DataContext对应的数据源类型是一个DataSet时,Binding时需使用Path指定控件对应绑定到的DataTable。
1: <Window x:Class="WPF_24.WinObjectDataProviderDemo3"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:lib="clr-namespace:WPF_24_Library;assembly=WPF_24_Library"
5: Title="WinObjectDataProviderDemo3" Height="300" Width="500">
6: <Window.Resources>
7: <ObjectDataProvider x:Key="myDataSetSource"
8: ObjectType="{x:Type lib:DataControl}"
9: MethodName="GetNorthwindDataSet" />
10: </Window.Resources>
11: <Grid DataContext="{StaticResource myDataSetSource}" x:Name="grid">
12: <Grid.ColumnDefinitions>
13: <ColumnDefinition Width="2*" />
14: <ColumnDefinition Width="3*" />
15: </Grid.ColumnDefinitions>
16: <ListBox Grid.Column="0" SelectedIndex="0"
17: ItemsSource="{Binding Path=Categories}"
18: DisplayMemberPath="CategoryName"
19: SelectedValuePath="CategoryID"
20: SelectionChanged="ListBox_SelectionChanged"
21: IsSynchronizedWithCurrentItem="True"
22: x:Name="lst"/>
23: <ListBox Grid.Column="1"
24: ItemsSource="{Binding Path=Categories/FK_Products_Categories}"
25: DisplayMemberPath="ProductName"
26: SelectedValuePath="ProductID" />
27: </Grid>
28: </Window>
另外还需要注意的是第24行,绑定主从数据时,子数据绑定的Path为“主表名/关系名”。
应用程序执行的结果如下:
附:代码所使用的存储过程:
1: use Northwind
2: GO
3:
4: Create Proc sp_GetAllProductInfo
5: As
6: Select ProductID,ProductName,CategoryID,UnitPrice
7: From Products
8:
9: GO
10:
11: Create Proc sp_GetProductInfoByCategoryID
12: @categoryID int
13: As
14: if @categoryID is null or @categoryID=0
15: Select ProductID,ProductName,CategoryID,UnitPrice
16: From Products
17: else
18: Select ProductID,ProductName,CategoryID,UnitPrice
19: From Products
20: Where CategoryID=@categoryID
21:
22: GO
23:
24: Create Proc sp_GetAllCategories
25: As
26: Select CategoryID,CategoryName
27: From Categories