使用 WPF 和 Entity Framework 6 创建简单的数据应用程序

警告

如果使用的是 Visual Studio 2022,则应使用 Visual Studio 2022 版本 17.3 预览版 3 或更高版本进行本教程。

本演练演示如何在 Visual Studio 中创建基本的“基于数据表单”应用程序。 该应用使用 SQL Server LocalDB、Northwind 数据库、Entity Framework 6(而不是 Entity Framework Core)和适用于 .NET Framework 的 Windows Presentation Foundation(而不是 .NET Core 或 .NET 5 或更高版本)。 它演示如何使用大纲-细节视图执行基本数据绑定,它还具有一个自定义绑定导航器,其中包括“移动到下一个”、“移动到上一个”、“移动到开头”、“移动到末尾”、“更新”和“删除”按钮。

本文重点介绍如何在 Visual Studio 中使用数据工具,并且不会尝试在任何深度中解释基础技术。 它假定你已基本熟悉 XAML、实体框架和 SQL。 此示例也不演示 WPF 应用程序的标准模型View-ViewModel(MVVM)体系结构。 但是,可以通过少量修改将此代码复制到自己的 MVVM 应用程序中。

本教程的最终代码可以在 GitHub Visual Studio 教程示例 - EF6中找到。

安装并连接到 Northwind

此示例使用 SQL Server Express LocalDB 和 Northwind 示例数据库。 如果该产品的 ADO.NET 数据提供程序支持 Entity Framework,则它应该同样适用于其他 SQL 数据库产品。

  1. 如果没有 SQL Server Express LocalDB,请通过 Visual Studio 安装程序安装它。 在 Visual Studio 安装程序中,可以将 SQL Server Express LocalDB 作为 数据存储和处理 工作负荷的一部分或作为单个组件安装。

  2. 按照以下步骤安装 Northwind 示例数据库:

    1. 在 Visual Studio 中,打开 SQL Server 对象资源管理器 窗口。 (SQL Server 对象资源管理器作为数据存储和处理工作负载的一部分安装在 Visual Studio 安装程序中。)展开“SQL Server”节点。 右键单击 LocalDB 实例并选择“新建查询”

      此时会打开查询编辑器窗口。

    2. Northwind Transact-SQL 脚本 复制到剪贴板。 此 T-SQL 脚本从头开始创建 Northwind 数据库,并使用数据填充该数据库。

    3. 将 T-SQL 脚本粘贴到查询编辑器中,然后选择“执行”按钮。

      短时间后,查询将完成运行并创建 Northwind 数据库。

  3. 为 Northwind 添加新连接

配置项目

  1. 在 Visual Studio 中,创建新的 C# WPF 应用(.NET Framework) 项目。

  2. 添加 Entity Framework 6 的 NuGet 包。 在 解决方案资源管理器中,选择项目节点。 在主菜单中,选择 项目>管理 NuGet 包

  3. 在 NuGet 包管理器中,单击“浏览”链接。 实体框架可能是列表中的顶部包。 在右窗格中单击 安装,并按照提示操作。 “输出”窗口会告诉你安装完成的时间。

    实体框架 NuGet 包 NuGet 包的屏幕截图。

    显示 Entity Framework NuGet 包的 屏幕截图。

  4. 现在,可以使用 Visual Studio 基于 Northwind 数据库创建模型。

创建模型

  1. 右键单击 解决方案资源管理器 中的项目节点,然后选择 “添加新项>”。 在左窗格中的 C# 节点下,选择 数据,然后在中间窗格中选择 ADO.NET 实体数据模型

    实体框架模型新项的 屏幕截图。

    实体框架模型新项的 屏幕截图。

  2. 调用模型 Northwind_model 并选择 添加。 “实体数据模型”向导随即打开。 选择“从数据库打开 EF Designer”,然后单击“下一步”。

    数据库中 EF 模型的屏幕截图。

  3. 在下一个屏幕中,选择 LocalDB Northwind 连接(例如(localdb)\MSSQLLocalDB),指定 Northwind 数据库,然后单击“下一步”

    如果未看到连接, 选择 “新建连接”,然后在“选择数据源 对话框中,选择 MICROSOFT SQL Server,选择 继续,然后在”连接属性“对话框中输入 (localdb)\MSSQLLocalDB,然后在 选择或输入数据库名称,选择”Northwind” 然后按“确定”

  4. 如果出现提示,请选择正在使用的 Entity Framework 版本。

    显示版本选项的屏幕截图。

  5. 在向导的下一页中,选择要包含在 Entity Framework 模型中的表、存储过程和其他数据库对象。 展开树视图中的 dbo 节点,然后选择 客户订单,以及 订单详细信息。 保留选中默认值,并单击“完成”。

    为模型选择数据库对象的屏幕截图。

  6. 该向导生成表示 Entity Framework 模型的 C# 类。 这些类是简单的 C# 类,用于数据绑定到 WPF 用户界面。 .edmx 文件描述将类与数据库中的对象关联的关系和其他元数据。 .tt 文件是 T4 模板,用于生成对模型进行操作的代码,并将更改保存到数据库。 可以在Northwind_model节点下 解决方案资源管理器 中看到所有这些文件:

    显示解决方案资源管理器中的实体框架模型文件的屏幕截图。

    显示解决方案资源管理器实体框架模型文件的屏幕截图

    使用 .edmx 文件的设计器图面可以修改模型中的某些属性和关系。 本演练中不会使用设计器。

  7. .tt 文件是通用文件,你需要对其中一个进行调整以适应 WPF 数据绑定,这需要使用 ObservableCollection。 在 解决方案资源管理器中,展开Northwind_model节点,直到找到 Northwind_model.tt。 (请确保不在 .Context.tt 文件中,该文件位于 .edmx 文件正下方。

  8. F5Ctrl+F5 生成并运行项目。 应用程序首次运行时,模型类在数据源向导(工具)中可见。

现在,你已准备好将此模型连接到 XAML 页面,以便可以查看、导航和修改数据。

将模型数据绑定到 XAML 页面

可以编写自己的数据绑定代码,但让 Visual Studio 为你执行此操作要容易得多。

  1. 在主菜单中选择“项目”>添加新数据源 以显示 数据源配置向导。 选择 对象,因为要绑定到模型类,而不是数据库:

    使用对象源 数据源配置向导的屏幕截图。

  2. 展开项目的节点,然后选择“客户”。 (订单的来源是从 Customer 中的“订单”导航属性自动生成的。

    显示将实体类添加为数据源的屏幕截图。

    显示将实体类添加为数据源的屏幕截图。

  3. 单击 完成

  4. 在代码视图中导航到 MainWindow.xaml。 为了本示例的目的,我们将 XAML 保持简单。 将 MainWindow 的标题更改为更具描述性的内容,并暂时将其高度和宽度增加到 600 x 800。 以后始终可以对其进行更改。 现在,将这三个行定义添加到主网格,一行用于导航按钮,一行用于客户的详细信息,一行用于显示其订单的网格:

        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
  5. 现在打开 MainWindow.xaml,以便在设计器中查看它。 这会导致 数据源 窗口在 工具箱旁边的 Visual Studio 窗口边距中显示为选项。 单击选项卡以打开该窗口,或按 Shift+Alt+D 或选择“查看”>“其他窗口”>“数据源”。 我们将在单独的文本框中显示 Customers 类的每个属性。 首先,单击 客户 组合框中的箭头,然后选择 详细信息。 然后,将节点拖到设计图面的中间部分,以便设计器知道你想要它进入中间行。 如果错放位置,稍后可以在 XAML (Grid.Row="1") 中手动指定行。 默认情况下,控件垂直放置在网格元素中,但此时,你可以按照你喜欢的形式排列控件。 例如,将 名称 文本框放在地址上方可能有意义。 本文的示例应用程序对字段进行重新排序,并将其重新排列为两列。

    屏幕截图,显示客户数据源绑定到单个控件。

    屏幕截图,显示客户数据源绑定到单个控件。

    在 XAML 视图中,现在可以在父网格的第 1 行(中间行)中看到新的 Grid 元素。 父网格具有 DataContext 属性,该属性引用已添加到 Windows.Resources 元素的 CollectionViewSource。 鉴于该数据上下文,当第一个文本框绑定到 地址时,该名称将映射到 CollectionViewSource中当前 Customer 对象中的 Address 属性。

    <Grid DataContext="{StaticResource customerViewSource}">
    
  6. 当客户显示在窗口的上半部分时,你希望在下半部分看到他们的订单。 在单个网格视图控件中显示订单。 要使大纲-细节数据绑定按预期工作,一定要绑定到 Customers 类中的 Orders 属性,而不是绑定到单独的 Orders 节点。 将 Customers 类的 Orders 属性拖到窗体的下半部分,以便设计器将其置于第 2 行:

    显示作为网格拖放的 Orders 类的屏幕截图。

    显示作为网格拖放的 Orders 类的屏幕截图。

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

    如果尚不存在,请为 System.Data.Entity 添加 using 指令,将 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;
    }
    
  8. 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 中,并将这些按钮与绑定到代代码隐藏中的方法的命令相关联。

命令逻辑有四个部分:(1)命令、(2)绑定、(3)按钮和代码隐藏中的命令处理程序(4)。

在 XAML 中添加命令、绑定和按钮

  1. 首先,在 Windows.Resources 元素内的 MainWindow.xaml 文件中添加命令:

    <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"/>
    
  2. 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>
    
  3. 现在,添加 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>
    

    然后,将此代码粘贴到外部 Grid 元素的 RowDefinitions 之后,它靠近 XAML 页面顶部:

    <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 类

除了添加和删除方法之外,后台代码是最少的。 导航是通过对 CollectionViewSource 的 View 属性调用方法执行的。 DeleteOrderCommandHandler 演示如何对订单执行级联删除。 我们必须首先删除与其关联的Order_Details。 UpdateCommandHandler 向集合中添加新客户或订单,或更新现有客户或订单的信息,具体取决于用户在文本框中所做的更改。

将这些处理程序方法添加到 MainWindow.xaml.cs中的 MainWindow 类。 如果 Customers 表的 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);
}

运行应用程序

若要开始调试,请按 F5。 应会看到网格中填充的客户和订单数据,导航按钮应按预期工作。 在输入数据后,单击 提交,以向模型添加新客户或订单。 单击“取消”,以从新客户或新订单窗体中返回而不保存数据。 可以直接在文本框中对现有客户和订单进行编辑,这些更改会自动写入模型。