WPF 使用者入門

這個逐步解說會示範如何在「主要詳細資料」表單中將 POCO 類型系結至 WPF 控制項。 應用程式會使用 Entity Framework API,將來自資料庫的資料填入物件、追蹤變更,並將資料保存到資料庫。

此模型會定義兩種參與一對多關聯性的類型: Category (principal\main) 和 Product (dependent\detail)。 WPF 資料系結架構可讓您在相關物件之間巡覽:選取主要檢視中的資料列會導致詳細資料檢視以對應的子資料更新。

本逐步解說中的螢幕擷取畫面和程式代碼清單取自 Visual Studio 2019 16.6.5。

提示

您可以檢視本文中的 GitHut 範例

必要條件

您必須安裝 Visual Studio 2019 16.3 或更新版本,並 選取 .NET 桌面工作負載 來完成本逐步解說。 如需安裝最新版 Visual Studio 的詳細資訊,請參閱 安裝 Visual Studio

建立應用程式

  1. 開啟 Visual Studio
  2. 在開始視窗中,選擇 [建立新專案]
  3. 搜尋 「WPF」,選擇 [WPF 應用程式] (.NET Core), 然後選擇 [ 下一步 ]。
  4. 在下一個畫面中,為專案命名,例如 GetStartedWPF ,然後選擇 [ 建立]。

安裝 Entity Framework NuGet 套件

  1. 以滑鼠右鍵按一下解決方案,然後選擇 [ 管理方案的 NuGet 套件...]。

    Manage NuGet Packages

  2. 在搜尋方塊中輸入 entityframeworkcore.sqlite

  3. 選取 Microsoft.EntityFrameworkCore.Sqlite 套件。

  4. 檢查右窗格中的專案,然後按一下 [ 安裝]

    Sqlite Package

  5. 重複步驟以搜尋 entityframeworkcore.proxies 並安裝 Microsoft.EntityFrameworkCore.Proxies

注意

當您安裝 Sqlite 套件時,它會自動提取相關的 Microsoft.EntityFrameworkCore 基底套件。 Microsoft.EntityFrameworkCore.Proxies 套件支援「延遲載入」資料。 這表示當您有具有子實體的實體時,只會在初始載入時擷取父系。 Proxy 會偵測何時嘗試存取子實體,並視需要自動載入它們。

定義模型

在本逐步解說中,您將使用「程式碼優先」來實作模型。這表示 EF Core 會根據您定義的 C# 類別來建立資料庫資料表和架構。

新增類別。 為它命名: Product.cs ,並填入如下:

Product.cs

namespace GetStartedWPF
{
    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }

        public int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
}

接下來,新增名為 Category.cs 的類別,並填入下列程式碼:

Category.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace GetStartedWPF
{
    public class Category
    {
        public int CategoryId { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Product>
            Products
        { get; private set; } =
            new ObservableCollection<Product>();
    }
}

Product 類別上的 Products 屬性和 Product 類別上的 Category 屬性是導覽屬性。 在 Entity Framework 中,導覽屬性提供一種方式來巡覽兩個實體類型之間的關聯性。

除了定義實體之外,您還需要定義衍生自 DbCoNtext 的類別,並公開 DbSet < TEntity > 屬性。 DbSet < TEntity > 屬性可讓內容知道您要包含在模型中的類型。

DbCoNtext 衍生型別的實例會在運行時間管理實體物件,其中包括將來自資料庫的資料填入物件、變更追蹤,以及將資料保存至資料庫。

使用下列定義將新 ProductContext.cs 類別新增至專案:

ProductContext.cs

using Microsoft.EntityFrameworkCore;

namespace GetStartedWPF
{
    public class ProductContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }

        protected override void OnConfiguring(
            DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite(
                "Data Source=products.db");
            optionsBuilder.UseLazyLoadingProxies();
        }
    }
}
  • DbSet 通知 EF Core 應該將哪些 C# 實體對應至資料庫。
  • 有各種不同的方式可以設定 EF Core DbContext 。 您可以在: 設定 DbCoNtext 中閱讀它們。
  • 此範例會 OnConfiguring 使用 覆寫來指定 Sqlite 資料檔案。
  • 呼叫 UseLazyLoadingProxies 會告知 EF Core 實作延遲載入,因此子實體會在從父代存取時自動載入。

CTRL+SHIFT+B ,或流覽至 [建 > 置建置方案 ] 來編譯專案。

提示

瞭解不同的是讓資料庫和 EF Core 模型保持同步: 管理資料庫架構

消極式載入

Product 類別上的 Products 屬性和 Product 類別上的 Category 屬性是導覽屬性。 在 Entity Framework Core 中,導覽屬性提供一種方式來巡覽兩個實體類型之間的關聯性。

EF Core 可讓您在第一次存取導覽屬性時,自動從資料庫載入相關實體。 使用這種類型的載入(稱為延遲載入),請注意,當您第一次存取每個導覽屬性時,如果內容不在內容中,就會對資料庫執行個別查詢。

使用「Plain Old C# Object」 (POCO) 實體類型時,EF Core 會在執行時間期間建立衍生 Proxy 類型的實例,然後覆寫類別中的虛擬屬性以新增載入攔截,藉以達成延遲載入。 若要取得相關物件的延遲載入,您必須將導覽屬性 getter 宣告為 公用 虛擬 在 Visual Basic 中為可 覆寫),而且您的類別不得 密封 Visual Basic 中的 NotOverridable )。 使用 Database First 時,導覽屬性會自動設為虛擬,以啟用延遲載入。

將物件系結至控制項

新增模型中定義為這個 WPF 應用程式的資料來源的類別。

  1. 按兩下 方案總管 中的 MainWindow.xaml 以開啟主表單

  2. 選擇 [XAML] 索引 標籤以編輯 XAML。

  3. 緊接在開頭 Window 標記之後,新增下列來源以連線到 EF Core 實體。

    <Window x:Class="GetStartedWPF.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:GetStartedWPF"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
        <Window.Resources>
            <CollectionViewSource x:Key="categoryViewSource"/>
            <CollectionViewSource x:Key="categoryProductsViewSource" 
                                  Source="{Binding Products, Source={StaticResource categoryViewSource}}"/>
        </Window.Resources>
    
  4. 這會設定「父代」類別的來源,以及「詳細資料」產品的第二個來源。

  5. 接下來,在開頭 Grid 標記之後,將下列標記新增至您的 XAML。

    <DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" 
              EnableRowVirtualization="True" 
              ItemsSource="{Binding Source={StaticResource categoryViewSource}}" 
              Margin="13,13,43,229" RowDetailsVisibilityMode="VisibleWhenSelected">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding CategoryId}"
                                Header="Category Id" Width="SizeToHeader"
                                IsReadOnly="True"/>
            <DataGridTextColumn Binding="{Binding Name}" Header="Name" 
                                Width="*"/>
        </DataGrid.Columns>
    </DataGrid>
    
  6. 請注意, CategoryId 是設定為 ReadOnly ,因為它是由資料庫指派,而且無法變更。

新增詳細資料方格

現在格線已存在以顯示類別,因此可以新增詳細資料方格來顯示產品。 在 專案內 Grid ,在 categories DataGrid 元素後面加入這個 。

MainWindow.xaml

<DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False" 
          EnableRowVirtualization="True" 
          ItemsSource="{Binding Source={StaticResource categoryProductsViewSource}}" 
          Margin="13,205,43,108" RowDetailsVisibilityMode="VisibleWhenSelected" 
          RenderTransformOrigin="0.488,0.251">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding CategoryId}" 
                            Header="Category Id" Width="SizeToHeader"
                            IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding ProductId}" Header="Product Id" 
                            Width="SizeToHeader" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="*"/>
    </DataGrid.Columns>
</DataGrid>

最後,將 Click 事件中的按鈕和電線新增 SaveButton_Click

<Button Content="Save" HorizontalAlignment="Center" Margin="0,240,0,0" 
        Click="Button_Click" Height="20" Width="123"/>

您的設計檢視看起來應該像這樣:

Screenshot of WPF Designer

新增處理資料互動的程式碼

是時候將一些事件處理常式新增至主視窗了。

  1. 在 XAML 視窗中,按一下 < Window > 元素,以選取主視窗。

  2. 在 [ 屬性] 視窗中,選擇 右上方的事件,然後按兩下 [載入] 標籤右邊的 文字方塊。

    Main Window Properties

這會帶您前往表單的程式碼後置,我們現在將編輯程式碼以使用 ProductContext 來執行資料存取。 更新程式碼,如下所示。

程式碼會宣告 長時間執行的 實例 ProductContext 。 物件 ProductContext 可用來查詢資料,並將資料儲存至資料庫。 Dispose()接著會 ProductContext 從覆寫 OnClosing 的方法呼叫 實例上的 方法。 程式碼註解會說明每個步驟的作用。

MainWindow.xaml.cs

using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace GetStartedWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ProductContext _context =
            new ProductContext();

        private CollectionViewSource categoryViewSource;

        public MainWindow()
        {
            InitializeComponent();
            categoryViewSource =
                (CollectionViewSource)FindResource(nameof(categoryViewSource));
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // this is for demo purposes only, to make it easier
            // to get up and running
            _context.Database.EnsureCreated();

            // load the entities into EF Core
            _context.Categories.Load();

            // bind to the source
            categoryViewSource.Source =
                _context.Categories.Local.ToObservableCollection();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // all changes are automatically tracked, including
            // deletes!
            _context.SaveChanges();

            // this forces the grid to refresh to latest values
            categoryDataGrid.Items.Refresh();
            productsDataGrid.Items.Refresh();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            // clean up database connections
            _context.Dispose();
            base.OnClosing(e);
        }
    }
}

注意

程式碼會使用 呼叫, EnsureCreated() 在第一次執行時建置資料庫。 這適用于示範,但在實際執行應用程式中,您應該查看 轉來管理架構。 程式碼也會同步執行,因為它使用本機 SQLite 資料庫。 對於通常牽涉到遠端伺服器的生產案例,請考慮使用 和 SaveChanges 方法的 Load 非同步版本。

測試 WPF 應用程式

F5 或選擇 [ > 偵錯開始 偵錯] 來編譯並執行應用程式。 資料庫應該使用名為 products.db 的檔案自動建立。 輸入類別名稱,然後按 enter 鍵,然後將產品新增至下方方格。 按一下 [儲存],並使用資料庫提供的識別碼監看方格重新整理。 反白顯示資料列,然後按 [刪除] 移除資料列。 當您按一下 [儲存 ] 時,將會刪除實體。

Running application

屬性變更通知

此範例依賴四個步驟來同步處理實體與 UI。

  1. 初始呼叫 _context.Categories.Load() 會載入類別資料。
  2. 延遲載入 Proxy 會載入相依產品資料。
  3. 呼叫 時 _context.SaveChanges() ,EF Core 的內建變更追蹤會對實體進行必要的修改,包括插入和刪除。
  4. 使用新產生的識別碼強制重載的呼叫 DataGridView.Items.Refresh()

這適用于我們入門範例,但您可能需要其他案例的額外程式碼。 WPF 控制項會藉由讀取實體上的欄位和屬性來呈現 UI。 當您在使用者介面 (UI) 中編輯值時,該值會傳遞至您的實體。 當您直接在實體上變更屬性的值時,例如從資料庫載入它時,WPF 將不會立即反映 UI 中的變更。 轉譯引擎必須收到變更的通知。 專案會藉由手動呼叫 Refresh() 來執行此動作。 自動化此通知的簡單方式是實作 INotifyPropertyChanged 介面。 WPF 元件會自動偵測介面並註冊變更事件。 實體負責引發這些事件。

提示

若要深入瞭解如何處理變更,請參閱: 如何實作屬性變更通知

後續步驟

深入瞭解如何 設定 DbCoNtext