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 。
建立應用程式
- 開啟 Visual Studio
- 在開始視窗中,選擇 [建立新專案]。
- 搜尋 「WPF」,選擇 [WPF 應用程式] (.NET Core), 然後選擇 [ 下一步 ]。
- 在下一個畫面中,為專案命名,例如 GetStartedWPF ,然後選擇 [ 建立]。
安裝 Entity Framework NuGet 套件
以滑鼠右鍵按一下解決方案,然後選擇 [ 管理方案的 NuGet 套件...]。
在搜尋方塊中輸入
entityframeworkcore.sqlite
。選取 Microsoft.EntityFrameworkCore.Sqlite 套件。
檢查右窗格中的專案,然後按一下 [ 安裝]
重複步驟以搜尋
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 應用程式的資料來源的類別。
按兩下 方案總管 中的 MainWindow.xaml 以開啟主表單
選擇 [XAML] 索引 標籤以編輯 XAML。
緊接在開頭
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>
這會設定「父代」類別的來源,以及「詳細資料」產品的第二個來源。
接下來,在開頭
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>
請注意,
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 事件中的按鈕和電線新增 Save
至 Button_Click
。
<Button Content="Save" HorizontalAlignment="Center" Margin="0,240,0,0"
Click="Button_Click" Height="20" Width="123"/>
您的設計檢視看起來應該像這樣:
新增處理資料互動的程式碼
是時候將一些事件處理常式新增至主視窗了。
在 XAML 視窗中,按一下 < Window > 元素,以選取主視窗。
在 [ 屬性] 視窗中,選擇 右上方的事件,然後按兩下 [載入] 標籤右邊的 文字方塊。
這會帶您前往表單的程式碼後置,我們現在將編輯程式碼以使用 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 鍵,然後將產品新增至下方方格。 按一下 [儲存],並使用資料庫提供的識別碼監看方格重新整理。 反白顯示資料列,然後按 [刪除] 移除資料列。 當您按一下 [儲存 ] 時,將會刪除實體。
屬性變更通知
此範例依賴四個步驟來同步處理實體與 UI。
- 初始呼叫
_context.Categories.Load()
會載入類別資料。 - 延遲載入 Proxy 會載入相依產品資料。
- 呼叫 時
_context.SaveChanges()
,EF Core 的內建變更追蹤會對實體進行必要的修改,包括插入和刪除。 - 使用新產生的識別碼強制重載的呼叫
DataGridView.Items.Refresh()
。
這適用于我們入門範例,但您可能需要其他案例的額外程式碼。 WPF 控制項會藉由讀取實體上的欄位和屬性來呈現 UI。 當您在使用者介面 (UI) 中編輯值時,該值會傳遞至您的實體。 當您直接在實體上變更屬性的值時,例如從資料庫載入它時,WPF 將不會立即反映 UI 中的變更。 轉譯引擎必須收到變更的通知。 專案會藉由手動呼叫 Refresh()
來執行此動作。 自動化此通知的簡單方式是實作 INotifyPropertyChanged 介面。 WPF 元件會自動偵測介面並註冊變更事件。 實體負責引發這些事件。
提示
若要深入瞭解如何處理變更,請參閱: 如何實作屬性變更通知 。
後續步驟
深入瞭解如何 設定 DbCoNtext 。