注释
如果运行的是 Visual Studio 2022,请确保在本教程中使用版本 17.3 或更高版本。
本教程介绍如何在 Visual Studio 中创建一个基础数据表单应用程序。 该应用使用 SQL Server LocalDB、Northwind 数据库、Entity Framework 6(而不是 Entity Framework Core),以及用于 .NET Framework 的 Windows Presentation Foundation(WPF)(而不是 .NET Core 或 .NET 5 及更新版本)。 它演示如何使用主从视图执行基本数据绑定,并包含具有以下功能按钮的自定义 BindingNavigator 控件:移动到第一个记录、移动到上一个、移动到下一个、移动到最后一个记录、删除、添加、设置新排序、更改和取消。
本教程重点介绍如何在 Visual Studio 中使用数据工具,并且不会尝试深入解释基础技术。 它假定你已基本熟悉可扩展应用程序标记语言(XAML)、实体框架和 SQL。 尽管本教程中的代码不演示模型View-ViewModel(MVVM)体系结构,这是 WPF 应用程序的标准,但你可以通过一些修改将代码复制到自己的 MVVM 应用程序中。
若要查看本教程的最终代码,请参阅 Visual Studio 教程示例 - EF6。
在本教程中,你将:
- 安装并连接到 Northwind
- 配置 WPF 应用项目
- 创建 ADO.NET 实体数据模型
- 数据将模型绑定到 XAML 页
- 调整页面设计并添加网格
- 添加用于导航、添加、更新和删除的按钮
- 运行 WPF 应用程序
先决条件
安装了 .NET 桌面开发工作负荷,并安装了 Windows Communication Foundation 组件的 Visual Studio。 若要进行安装:
- 打开 Visual Studio 安装程序 应用,或从 Visual Studio 菜单中选择 “工具>获取工具和功能 ”。
- 在 Visual Studio 安装程序中,选择要修改的 Visual Studio 版本旁边的 “修改 ”。
- 选择“单个组件”选项卡,然后选择“开发”活动下的 Windows Communication Foundation。
- 选择“”并修改“”。
SQL Server Express LocalDB。 如果没有 SQL Server Express LocalDB,可以从 SQL Server 下载页安装它。 或者,可以将 Visual Studio Installer 应用作为单个组件进行安装。
SQL Server 对象资源管理器。 若要安装它,请在 Visual Studio Installer 应用中安装数据存储和处理工作负载。
Entity Framework 6 工具。 安装 .NET Dektop 开发 工作负荷时,通常会安装此功能。
安装并连接到 Northwind
以下示例使用 SQL Server Express LocalDB 和 Northwind 示例数据库。 如果该产品的 ADO.NET 数据提供程序支持 Entity Framework,则它还应与其他 SQL 数据库产品一起使用。
按照以下步骤安装 Northwind 示例数据库:
在 Visual Studio 中,从“视图”菜单打开“SQL Server 对象资源管理器”窗口。 展开“SQL Server”节点。 右键单击 LocalDB 实例并选择“ 新建查询”。
此时会打开查询编辑器窗口。
将 Northwind Transact-SQL (T-SQL) 脚本 复制到剪贴板。
将 T-SQL 脚本粘贴到查询编辑器中,然后选择 “执行”。
T-SQL 脚本查询将创建 Northwind 数据库,并使用数据填充该数据库。
为 Northwind 数据库添加新连接。
配置 WPF 应用项目
若要配置 WPF 应用项目,请执行以下步骤:
在 Visual Studio 中,创建新的 C# WPF 应用(.NET Framework) 项目。
添加 Entity Framework 6 的 NuGet 包。 在 解决方案资源管理器中,选择项目节点。 在主菜单中,选择 项目>管理 NuGet 包。
在 NuGet 包管理器中,选择 “浏览” 链接。 搜索并选择 EntityFramework 包。 在右窗格中选择“ 安装 ”,然后按照提示进行作。
“ 输出 ”窗口显示进度,并在安装完成后通知你。
现在可以使用 Visual Studio 基于 Northwind 数据库创建模型。
创建 ADO.NET 实体数据模型
若要创建 ADO.NET 实体数据模型,请执行以下步骤:
右键单击 解决方案资源管理器 中的 WPF 应用项目节点,然后选择“ 添加新>项”。 在左窗格中的 C# 节点下,选择 数据,然后在中间窗格中选择 ADO.NET 实体数据模型。
输入名称Northwind_model,然后选择添加。
在 实体数据模型向导中, 从数据库选择 EF 设计器,然后选择“ 下一步”。
在 “选择数据连接”中,选择 LocalDB Northwind 连接(例如 (localdb)\MSSQLLocalDB),然后选择“ 下一步”。
如果找不到连接:
选择 “新建连接”。 如果未在“连接属性”对话框中选择Microsoft SQL Server 作为数据源,请选择“更改”。 在“ 选择数据源 ”对话框中,选择 Microsoft SQL Server,然后选择“ 确定”。
在 “连接属性 ”对话框中,输入 (localdb)\MSSQLLocalDB 作为 服务器名称。
对于 “选择”或输入数据库名称,请选择 “Northwind”,然后选择“ 确定”。
在 “选择数据连接”中,选择 LocalDB Northwind 连接,然后选择“ 下一步”。
如果出现提示,请选择正在使用的 Entity Framework 版本,然后选择“ 下一步”。
在向导的下一页中,选择要包含在 Entity Framework 模型中的表、存储过程和其他数据库对象。 展开树视图中“表”节点下的“dbo”节点。 选择 “客户”、“ 订单详细信息”和 “订单”。 选中默认值并选择“ 完成”。
该向导生成表示实体框架模型的 C# 类,并且这些类是 Visual Studio 数据绑定到 WPF 用户界面的对象。 它在项目中创建以下文件:
.edmx文件描述将类与数据库中的对象关联的关系和其他元数据。.tt文件是 T4 模板,用于生成对模型进行操作的代码,并将更改保存到数据库。
这些文件在Northwind_model节点下的解决方案资源管理器中可见:
本教程未使用该文件的
.edmx窗体设计器,但你可以使用它修改模型中的某些属性和关系。
文件 .tt 是通用的,您需要编辑其中一个以便与需要 ObservableCollection 对象的 WPF 数据绑定一起使用。 执行以下步骤:
在 解决方案资源管理器中,展开 Northwind_model 节点,直到找到 Northwind_model.tt。 双击此文件并进行以下编辑:
将 ICollection 的两个匹配项替换为 ObservableCollection<T>。
将第一个 HashSet<T> 替换为 ObservableCollection<T>,靠近第 51 行。 请勿替换 HashSet 的第二次出现。
将唯一出现的 System.Collections.Generic (近第 431 行)替换为 System.Collections.ObjectModel。
按 F5 生成并运行项目。 应用程序首次运行时,模型类在数据源向导(工具)中可见。
现在,你已准备好将此模型连接到 XAML 页面,以便可以查看、导航和修改数据。
数据将模型绑定到 XAML 页
尽管可以编写自己的数据绑定代码,但更容易让 Visual Studio 为你执行此作。 为此,请执行以下步骤:
在主菜单中,选择 “项目>添加新数据源 ”以显示 “数据源配置向导”。 由于要绑定到模型类,而不是绑定到数据库,请选择 “对象”。 选择“下一步”。
展开项目的节点,选择 Customer 对象,然后选择“ 完成”。 Order 对象的源是从 Customer中的 Orders 导航属性自动生成的。
在 解决方案资源管理器中,双击项目中的 MainWindow.xaml 以编辑 XAML。 将
Title从 MainWindow 更改为更具描述性的名称,并将Height和Width分别改为 600 和 800(如有必要,稍后可以更改这些值)。将这三个行定义添加到主网格,一行用于导航按钮,一行用于客户的详细信息,一行用于显示其订单的网格:
<Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions>
接下来,你需要在每个单独的文本框中显示类 Customers 中的每个属性。 执行以下步骤:
在 解决方案资源管理器中,双击 MainWindow.xaml 以在设计器中打开它。
“数据源”选项卡显示在工具箱附近的 Visual Studio 左窗格中。
若要打开 “数据源 ”窗口,请选择“ 数据源 ”选项卡,或从菜单中选择“ 查看>其他 Windows>数据源 ”。
在 数据源中,选择 “客户”,然后从下拉列表中选择“ 详细信息 ”。
将节点拖到设计区域的中间行。 如果错放位置,稍后可以在 XAML 中手动指定行,方法是选择
Grid.Row="1"。默认情况下,控件垂直放置在网格元素中,但你可以按你喜欢的方式排列控件。 例如,可以将 “名称” 文本框放在地址上方。 本教程的示例应用程序对字段进行重新排序,并将其重新排列为两列。
在 XAML 视图中,现在可以在父网格的第 1 行(中间行)中看到一个新
Grid元素。 父网格具有DataContext属性,该属性引用属于 CollectionViewSource 元素的Windows.Resources。 给定该数据上下文,当第一个文本框绑定到“地址”时,该名称将映射到 中当前Address对象中的Customer属性。CollectionViewSource<Grid DataContext="{StaticResource customerViewSource}">将
Order类的对象Customers属性拖到窗体的下半部分,以便设计器将其置于第 2 行中。当客户在表单的上半部分可见时,可以在下半部分查看其订单。 在单个网格视图控件中显示订单。 若要使主详细信息数据绑定按预期工作,请务必绑定到
Orders类中的Customers属性,而不是绑定到单独的Orders节点。
Visual Studio 现在生成将 UI 控件连接到模型中的事件的所有绑定代码。
若要查看某些数据,请编写代码以填充模型。 导航到
MainWindow.xaml.cs,然后将数据成员添加到数据上下文的MainWindow类。此对象是为你生成的,它就像跟踪模型中的更改和事件的控件一样。
将
CollectionViewSource客户和订单的数据成员以及关联的构造函数初始化逻辑添加到现有构造函数MainWindow()。 类的第一部分应如下所示:public partial class MainWindow : Window { NorthwindEntities context = new NorthwindEntities(); CollectionViewSource custViewSource; CollectionViewSource ordViewSource; public MainWindow() { InitializeComponent(); custViewSource = ((CollectionViewSource)(FindResource("customerViewSource"))); ordViewSource = ((CollectionViewSource)(FindResource("customerOrdersViewSource"))); DataContext = this; }如果不存在,请添加一条
using指令,让System.Data.Entity可将Load扩展方法引入范围中:using System.Data.Entity;向下滚动并查找
Window_Loaded事件处理程序。 请注意,Visual Studio 添加了一个CollectionViewSource对象。 此对象表示NorthwindEntities创建模型时选择的对象。 因为你已经添加了它,所以不需要在这里添加。 替换代码Window_Loaded,使方法如下所示:private void Window_Loaded(object sender, RoutedEventArgs e) { // Load is an extension method on IQueryable, // defined in the System.Data.Entity namespace. // This method enumerates the results of the query, // similar to ToList but without creating a list. // When used with Linq to Entities, this method // creates entity objects and adds them to the context. context.Customers.Load(); // After the data is loaded, call the DbSet<T>.Local property // to use the DbSet<T> as a binding source. custViewSource.Source = context.Customers.Local; }按 F5。
应该会在数据网格中看到第一个被检索到
CollectionViewSource中的客户的详细信息及其订单。 你将在下一部分修正格式问题。 还可以创建查看其他记录并执行基本创建、读取、更新和删除(CRUD)操作的方法。
调整页面设计和为新客户和订单添加网格
Visual Studio 生成的默认排列不适合应用程序,因此我们在此处提供最终 XAML 以复制到代码中。 还需要一些网格,使用户能够添加新客户或订单。
若要添加新的客户和订单,请创建一组未将数据绑定到 CollectionViewSource 的单独文本框。 通过在处理程序方法中设置 Visible 属性来控制用户在任何给定时间看到的网格。 最后,向“订单”网格中的每个行添加“删除”按钮,使用户能够删除单个订单。
打开
MainWindow.xaml以下样式并将其添加到Windows.Resources元素:<Style x:Key="Label" TargetType="{x:Type Label}" BasedOn="{x:Null}"> <Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="Margin" Value="3"/> <Setter Property="Height" Value="23"/> </Style> <Style x:Key="CustTextBox" TargetType="{x:Type TextBox}" BasedOn="{x:Null}"> <Setter Property="HorizontalAlignment" Value="Right"/> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="Margin" Value="3"/> <Setter Property="Height" Value="26"/> <Setter Property="Width" Value="120"/> </Style>将整个外部网格替换为以下标记:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid x:Name="existingCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" Margin="5" Visibility="Visible" VerticalAlignment="Top" Background="AntiqueWhite" DataContext="{StaticResource customerViewSource}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" MinWidth="233"/> <ColumnDefinition Width="Auto" MinWidth="397"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/> <TextBox x:Name="customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}" Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/> <TextBox x:Name="companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}" Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/> <TextBox x:Name="contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}" Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/> <TextBox x:Name="contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}" Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/> <TextBox x:Name="addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}" Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/> <TextBox x:Name="cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}" Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/> <TextBox x:Name="countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}" Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/> <TextBox x:Name="faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}" Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/> <TextBox x:Name="phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}" Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/> <TextBox x:Name="postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}" Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/> <TextBox x:Name="regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}" Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> </Grid> <Grid x:Name="newCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=newCustomer, UpdateSourceTrigger=Explicit}" Visibility="Collapsed" Background="CornflowerBlue"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" MinWidth="233"/> <ColumnDefinition Width="Auto" MinWidth="397"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/> <TextBox x:Name="add_customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}" Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/> <TextBox x:Name="add_companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}" Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true }"/> <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/> <TextBox x:Name="add_contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}" Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/> <TextBox x:Name="add_contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}" Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/> <TextBox x:Name="add_addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}" Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/> <TextBox x:Name="add_cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}" Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/> <TextBox x:Name="add_countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}" Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/> <TextBox x:Name="add_faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}" Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/> <TextBox x:Name="add_phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}" Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/> <TextBox x:Name="add_postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}" Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/> <TextBox x:Name="add_regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}" Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> </Grid> <Grid x:Name="newOrderGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding Path=newOrder, Mode=TwoWay}" Visibility="Collapsed" Background="LightGreen"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" MinWidth="233"/> <ColumnDefinition Width="Auto" MinWidth="397"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Label Content="New Order Form" FontWeight="Bold"/> <Label Content="Employee ID:" Grid.Row="1" Style="{StaticResource Label}"/> <TextBox x:Name="add_employeeIDTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}" Text="{Binding EmployeeID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Order Date:" Grid.Row="2" Style="{StaticResource Label}"/> <DatePicker x:Name="add_orderDatePicker" Grid.Row="2" HorizontalAlignment="Right" Width="120" SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/> <Label Content="Required Date:" Grid.Row="3" Style="{StaticResource Label}"/> <DatePicker x:Name="add_requiredDatePicker" Grid.Row="3" HorizontalAlignment="Right" Width="120" SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/> <Label Content="Shipped Date:" Grid.Row="4" Style="{StaticResource Label}"/> <DatePicker x:Name="add_shippedDatePicker" Grid.Row="4" HorizontalAlignment="Right" Width="120" SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/> <Label Content="Ship Via:" Grid.Row="5" Style="{StaticResource Label}"/> <TextBox x:Name="add_ShipViaTextBox" Grid.Row="5" Style="{StaticResource CustTextBox}" Text="{Binding ShipVia, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> <Label Content="Freight" Grid.Row="6" Style="{StaticResource Label}"/> <TextBox x:Name="add_freightTextBox" Grid.Row="6" Style="{StaticResource CustTextBox}" Text="{Binding Freight, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/> </Grid> <DataGrid x:Name="ordersDataGrid" SelectionUnit="Cell" SelectionMode="Single" AutoGenerateColumns="False" CanUserAddRows="false" IsEnabled="True" EnableRowVirtualization="True" Width="auto" ItemsSource="{Binding Source={StaticResource customerOrdersViewSource}}" Margin="10,10,10,10" Grid.Row="2" RowDetailsVisibilityMode="VisibleWhenSelected"> <DataGrid.Columns> <DataGridTemplateColumn> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Button Content="Delete" Command="{StaticResource DeleteOrderCommand}" CommandParameter="{Binding}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding CustomerID}" Header="Customer ID" Width="SizeToHeader"/> <DataGridTextColumn x:Name="employeeIDColumn" Binding="{Binding EmployeeID}" Header="Employee ID" Width="SizeToHeader"/> <DataGridTextColumn x:Name="freightColumn" Binding="{Binding Freight}" Header="Freight" Width="SizeToHeader"/> <DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="SizeToHeader"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn x:Name="orderIDColumn" Binding="{Binding OrderID}" Header="Order ID" Width="SizeToHeader"/> <DataGridTemplateColumn x:Name="requiredDateColumn" Header="Required Date" Width="SizeToHeader"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DatePicker SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn x:Name="shipAddressColumn" Binding="{Binding ShipAddress}" Header="Ship Address" Width="SizeToHeader"/> <DataGridTextColumn x:Name="shipCityColumn" Binding="{Binding ShipCity}" Header="Ship City" Width="SizeToHeader"/> <DataGridTextColumn x:Name="shipCountryColumn" Binding="{Binding ShipCountry}" Header="Ship Country" Width="SizeToHeader"/> <DataGridTextColumn x:Name="shipNameColumn" Binding="{Binding ShipName}" Header="Ship Name" Width="SizeToHeader"/> <DataGridTemplateColumn x:Name="shippedDateColumn" Header="Shipped Date" Width="SizeToHeader"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DatePicker SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn x:Name="shipPostalCodeColumn" Binding="{Binding ShipPostalCode}" Header="Ship Postal Code" Width="SizeToHeader"/> <DataGridTextColumn x:Name="shipRegionColumn" Binding="{Binding ShipRegion}" Header="Ship Region" Width="SizeToHeader"/> <DataGridTextColumn x:Name="shipViaColumn" Binding="{Binding ShipVia}" Header="Ship Via" Width="SizeToHeader"/> </DataGrid.Columns> </DataGrid> </Grid>
添加用于导航、添加、更新和删除的按钮
在 Windows 窗体应用程序中,系统提供了一个BindingNavigator对象,其中包含用于在数据库中导航行和执行基本 CRUD 操作的按钮。 虽然 WPF 不提供一个 BindingNavigator,但可以很容易地通过创建水平 StackPanel 中的按钮,并将按钮与绑定到后台代码文件中方法的命令关联来创建一个。
命令逻辑有四个部分:
- 指令
- 绑定
- 按钮
- 后台代码中的命令处理程序
在 XAML 中添加命令、绑定和按钮
在
MainWindow.xaml文件中,在元素中添加Windows.Resources命令,如下所示:<RoutedUICommand x:Key="FirstCommand" Text="First"/> <RoutedUICommand x:Key="LastCommand" Text="Last"/> <RoutedUICommand x:Key="NextCommand" Text="Next"/> <RoutedUICommand x:Key="PreviousCommand" Text="Previous"/> <RoutedUICommand x:Key="DeleteCustomerCommand" Text="Delete Customer"/> <RoutedUICommand x:Key="DeleteOrderCommand" Text="Delete Order"/> <RoutedUICommand x:Key="UpdateCommand" Text="Update"/> <RoutedUICommand x:Key="AddCommand" Text="Add"/> <RoutedUICommand x:Key="CancelCommand" Text="Cancel"/>CommandBinding将RoutedUICommand事件映射到代码隐藏中的方法。 将此CommandBindings元素添加在Windows.Resources关闭标签之后,如下所示:<Window.CommandBindings> <CommandBinding Command="{StaticResource FirstCommand}" Executed="FirstCommandHandler"/> <CommandBinding Command="{StaticResource LastCommand}" Executed="LastCommandHandler"/> <CommandBinding Command="{StaticResource NextCommand}" Executed="NextCommandHandler"/> <CommandBinding Command="{StaticResource PreviousCommand}" Executed="PreviousCommandHandler"/> <CommandBinding Command="{StaticResource DeleteCustomerCommand}" Executed="DeleteCustomerCommandHandler"/> <CommandBinding Command="{StaticResource DeleteOrderCommand}" Executed="DeleteOrderCommandHandler"/> <CommandBinding Command="{StaticResource UpdateCommand}" Executed="UpdateCommandHandler"/> <CommandBinding Command="{StaticResource AddCommand}" Executed="AddCommandHandler"/> <CommandBinding Command="{StaticResource CancelCommand}" Executed="CancelCommandHandler"/> </Window.CommandBindings>在导航、添加、删除和更新按钮中添加
StackPanel。 将此样式添加到Windows.Resources:<Style x:Key="NavButton" TargetType="{x:Type Button}" BasedOn="{x:Null}"> <Setter Property="FontSize" Value="24"/> <Setter Property="FontFamily" Value="Segoe UI Symbol"/> <Setter Property="Margin" Value="2,2,2,0"/> <Setter Property="Width" Value="40"/> <Setter Property="Height" Value="auto"/> </Style>请将此代码粘贴到 XAML 页面顶部的外部
RowDefinitions元素中,紧接在Grid之后。<StackPanel Orientation="Horizontal" Margin="2,2,2,0" Height="36" VerticalAlignment="Top" Background="Gainsboro" DataContext="{StaticResource customerViewSource}" d:LayoutOverrides="LeftMargin, RightMargin, TopMargin, BottomMargin"> <Button Name="btnFirst" Content="|◄" Command="{StaticResource FirstCommand}" Style="{StaticResource NavButton}"/> <Button Name="btnPrev" Content="◄" Command="{StaticResource PreviousCommand}" Style="{StaticResource NavButton}"/> <Button Name="btnNext" Content="►" Command="{StaticResource NextCommand}" Style="{StaticResource NavButton}"/> <Button Name="btnLast" Content="►|" Command="{StaticResource LastCommand}" Style="{StaticResource NavButton}"/> <Button Name="btnDelete" Content="Delete Customer" Command="{StaticResource DeleteCustomerCommand}" FontSize="11" Width="120" Style="{StaticResource NavButton}"/> <Button Name="btnAdd" Content="New Customer" Command="{StaticResource AddCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/> <Button Content="New Order" Name="btnNewOrder" FontSize="11" Width="80" Style="{StaticResource NavButton}" Click="NewOrder_click"/> <Button Name="btnUpdate" Content="Commit" Command="{StaticResource UpdateCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/> <Button Content="Cancel" Name="btnCancel" Command="{StaticResource CancelCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/> </StackPanel>
将命令处理程序添加到 MainWindow 类
除 add 和 delete 方法外,MainWindow.xaml.cs 中的代码隐藏是极少的:
若要导航,请在
View的CollectionViewSource属性上调用方法。若要对订单执行级联删除,请使用代码片段中所示的
DeleteOrderCommandHandler。 但是,必须先删除订单的关联Order_Details。使用
UpdateCommandHandler将客户或订单添加到集合,或使用用户在文本框中所做的更改更新现有客户或订单。将这些处理程序方法添加到
MainWindow类中MainWindow.xaml.cs。 如果“客户”表的CollectionViewSource采用其他名称,则必须在每个方法中调整名称:private void LastCommandHandler(object sender, ExecutedRoutedEventArgs e) { custViewSource.View.MoveCurrentToLast(); } private void PreviousCommandHandler(object sender, ExecutedRoutedEventArgs e) { custViewSource.View.MoveCurrentToPrevious(); } private void NextCommandHandler(object sender, ExecutedRoutedEventArgs e) { custViewSource.View.MoveCurrentToNext(); } private void FirstCommandHandler(object sender, ExecutedRoutedEventArgs e) { custViewSource.View.MoveCurrentToFirst(); } private void DeleteCustomerCommandHandler(object sender, ExecutedRoutedEventArgs e) { // If existing window is visible, delete the customer and all their orders. // In a real application, you should add warnings and allow the user to cancel the operation. var cur = custViewSource.View.CurrentItem as Customer; var cust = (from c in context.Customers where c.CustomerID == cur.CustomerID select c).FirstOrDefault(); if (cust != null) { foreach (var ord in cust.Orders.ToList()) { Delete_Order(ord); } context.Customers.Remove(cust); } context.SaveChanges(); custViewSource.View.Refresh(); } // Commit changes from the new customer form, the new order form, // or edits made to the existing customer form. private void UpdateCommandHandler(object sender, ExecutedRoutedEventArgs e) { if (newCustomerGrid.IsVisible) { // Create a new object because the old one // is being tracked by EF now. Customer newCustomer = new Customer { Address = add_addressTextBox.Text, City = add_cityTextBox.Text, CompanyName = add_companyNameTextBox.Text, ContactName = add_contactNameTextBox.Text, ContactTitle = add_contactTitleTextBox.Text, Country = add_countryTextBox.Text, CustomerID = add_customerIDTextBox.Text, Fax = add_faxTextBox.Text, Phone = add_phoneTextBox.Text, PostalCode = add_postalCodeTextBox.Text, Region = add_regionTextBox.Text }; // Perform very basic validation if (newCustomer.CustomerID.Length == 5) { // Insert the new customer at correct position: int len = context.Customers.Local.Count(); int pos = len; for (int i = 0; i < len; ++i) { if (String.CompareOrdinal(newCustomer.CustomerID, context.Customers.Local[i].CustomerID) < 0) { pos = i; break; } } context.Customers.Local.Insert(pos, newCustomer); custViewSource.View.Refresh(); custViewSource.View.MoveCurrentTo(newCustomer); } else { MessageBox.Show("CustomerID must have 5 characters."); } newCustomerGrid.Visibility = Visibility.Collapsed; existingCustomerGrid.Visibility = Visibility.Visible; } else if (newOrderGrid.IsVisible) { // Order ID is auto-generated so we don't set it here. // For CustomerID, address, etc we use the values from current customer. // User can modify these in the datagrid after the order is entered. Customer currentCustomer = (Customer)custViewSource.View.CurrentItem; Order newOrder = new Order() { OrderDate = add_orderDatePicker.SelectedDate, RequiredDate = add_requiredDatePicker.SelectedDate, ShippedDate = add_shippedDatePicker.SelectedDate, CustomerID = currentCustomer.CustomerID, ShipAddress = currentCustomer.Address, ShipCity = currentCustomer.City, ShipCountry = currentCustomer.Country, ShipName = currentCustomer.CompanyName, ShipPostalCode = currentCustomer.PostalCode, ShipRegion = currentCustomer.Region }; try { newOrder.EmployeeID = Int32.Parse(add_employeeIDTextBox.Text); } catch { MessageBox.Show("EmployeeID must be a valid integer value."); return; } try { // Exercise for the reader if you are using Northwind: // Add the Northwind Shippers table to the model. // Acceptable ShipperID values are 1, 2, or 3. if (add_ShipViaTextBox.Text == "1" || add_ShipViaTextBox.Text == "2" || add_ShipViaTextBox.Text == "3") { newOrder.ShipVia = Convert.ToInt32(add_ShipViaTextBox.Text); } else { MessageBox.Show("Shipper ID must be 1, 2, or 3 in Northwind."); return; } } catch { MessageBox.Show("Ship Via must be convertible to int"); return; } try { newOrder.Freight = Convert.ToDecimal(add_freightTextBox.Text); } catch { MessageBox.Show("Freight must be convertible to decimal."); return; } // Add the order into the EF model context.Orders.Add(newOrder); ordViewSource.View.Refresh(); } // Save the changes, either for a new customer, a new order // or an edit to an existing customer or order. context.SaveChanges(); } // Sets up the form so that user can enter data. Data is later // saved when user clicks Commit. private void AddCommandHandler(object sender, ExecutedRoutedEventArgs e) { existingCustomerGrid.Visibility = Visibility.Collapsed; newOrderGrid.Visibility = Visibility.Collapsed; newCustomerGrid.Visibility = Visibility.Visible; // Clear all the text boxes before adding a new customer. foreach (var child in newCustomerGrid.Children) { var tb = child as TextBox; if (tb != null) { tb.Text = ""; } } } private void NewOrder_click(object sender, RoutedEventArgs e) { var cust = custViewSource.View.CurrentItem as Customer; if (cust == null) { MessageBox.Show("No customer selected."); return; } existingCustomerGrid.Visibility = Visibility.Collapsed; newCustomerGrid.Visibility = Visibility.Collapsed; newOrderGrid.UpdateLayout(); newOrderGrid.Visibility = Visibility.Visible; } // Cancels any input into the new customer form private void CancelCommandHandler(object sender, ExecutedRoutedEventArgs e) { add_addressTextBox.Text = ""; add_cityTextBox.Text = ""; add_companyNameTextBox.Text = ""; add_contactNameTextBox.Text = ""; add_contactTitleTextBox.Text = ""; add_countryTextBox.Text = ""; add_customerIDTextBox.Text = ""; add_faxTextBox.Text = ""; add_phoneTextBox.Text = ""; add_postalCodeTextBox.Text = ""; add_regionTextBox.Text = ""; existingCustomerGrid.Visibility = Visibility.Visible; newCustomerGrid.Visibility = Visibility.Collapsed; newOrderGrid.Visibility = Visibility.Collapsed; } private void Delete_Order(Order order) { // Find the order in the EF model. var ord = (from o in context.Orders.Local where o.OrderID == order.OrderID select o).FirstOrDefault(); // Delete all the order_details that have // this Order as a foreign key foreach (var detail in ord.Order_Details.ToList()) { context.Order_Details.Remove(detail); } // Now it's safe to delete the order. context.Orders.Remove(ord); context.SaveChanges(); // Update the data grid. ordViewSource.View.Refresh(); } private void DeleteOrderCommandHandler(object sender, ExecutedRoutedEventArgs e) { // Get the Order in the row in which the Delete button was clicked. Order obj = e.Parameter as Order; Delete_Order(obj); }
运行 WPF 应用程序
若要开始调试,请按 F5。 验证客户和订单数据是否填充在网格中,导航按钮按预期工作。
在输入数据后,选择 “提交 ”以向模型添加新客户或订单。
选择“取消”以退出新客户或新订单窗体,而无需保存数据。
若要直接对现有客户和订单进行编辑,请使用文本框,这些文本框会自动将这些更改写入模型。