備註
如果您執行 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 中使用數據工具,而且不會嘗試在任何深度說明基礎技術。 它假設您已熟悉 Extensible Application Markup Language (XAML)、Entity Framework 和 SQL。 雖然本教學課程中的程式代碼不會示範 Model-View-ViewModel (MVVM) 架構,這是 WPF 應用程式的標準,但您可以使用一些修改將程式代碼複製到您自己的 MVVM 應用程式。
若要檢視本教學課程的最終程序代碼,請參閱 Visual Studio教學課程範例 - EF6。
在本教學課程中,您會:
- 安裝並連線到 Northwind 資料庫
- 設定 WPF 應用程式專案
- 建立 ADO.NET 實體數據模型
- 數據會將模型系結至 XAML 頁面
- 調整頁面設計並新增網格線
- 新增按鈕以巡覽、新增、更新和刪除
- 執行 WPF 應用程式
先決條件
已安裝 .NET 桌面開發工作負載的 Visual Studio,以及已安裝 Windows Communication Foundation 元件。 若要安裝:
- 開啟 Visual Studio 安裝程式應用程式,或從 Visual Studio 功能表中選取工具>取得工具與功能。
- 在 Visual Studio 安裝程式中,選擇您想要修改之 Visual Studio 版本旁的 [修改 ]。
- 選取 [個別元件] 索引卷標,然後選擇 [開發活動] 底下的 [Windows Communication Foundation]。
- 選取 修改。
SQL Server Express LocalDB。 如果您沒有 SQL Server Express LocalDB,您可以從 SQL Server 下載頁面加以安裝。 或者,您可以將 Visual Studio 安裝程式 應用程式安裝為個別元件。
SQL Server 物件總管。 若要安裝,請在 Visual Studio 安裝程式應用程式中安裝資料儲存和處理工作負載。
Entity Framework 6 工具。 當您安裝 .NET Desktop 開發 工作負載時,通常會安裝此程式。
安裝並連線到 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 Designer],然後選取 [ 下一步]。
在 [選擇您的數據連線] 中,選取您的 LocalDB Northwind 連線(例如 ,localdb)\MSSQLLocalDB),然後選取 [ 下一步]。
如果您沒有看到連線:
選擇 [新增連線]。 如果未選取Microsoft SQL Server 作為 [連接屬性] 對話方塊中的 [資料源],請選取 [變更]。 在 [ 選擇數據源] 對話框中,選擇 [Microsoft SQL Server],然後選取 [ 確定]。
在 [ 連線屬性] 對話框中,輸入 (localdb)\MSSQLLocalDB 作為 [伺服器名稱]。
針對 [選取或輸入資料庫名稱],選取 [Northwind],然後選取 [ 確定]。
在 [選擇您的數據連線] 中,選取您的 LocalDB Northwind 連線,然後選取 [ 下一步]。
如果出現提示,請選擇您使用的 Entity Framework 版本,然後選取 [ 下一步]。
在精靈的下一頁,選擇要包含在 Entity Framework 模型中的數據表、預存程式和其他資料庫物件。 展開樹檢視中 [dbo] 節點底下的 數據表 節點。 選取 [客戶]、 [訂單詳細數據] 和 [ 訂單]。 保留已核取的預設值,然後選取 [ 完成]。
精靈會產生代表 Entity Framework 模型的 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為您執行。 若要這樣做,請遵循下列步驟:
從主功能表中,選擇 [ 專案>新增數據源 ] 以顯示 [數據源組態精靈]。 由於您要系結至模型類別,而不是系結至資料庫,請選擇 [物件]。 選取 下一步。
展開項目的節點,選取 [客戶 ] 對象,然後選取 [ 完成]。 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>數據源 ]。
在 [數據源] 中,選取 [ 客戶],然後從下拉式清單中選取 [ 詳細數據 ]。
將節點拖曳至設計區域的中間行。 如果您無法找到它,您可以選擇
Grid.Row="1",然後在 XAML 中手動指定資料列。根據預設,控件會垂直放置在網格線元素中,但您可以依您喜歡的方式排列這些控件。 例如,您可以將 [ 名稱] 文字框放在位址上方。 本教學課程的範例應用程式會將字段重新排序,並將其重新排列成兩個數據行。
在 XAML 檢視中,您現在可以在父方格的第 1 列(中間數據列)中看到新
Grid元素。 父方格具有DataContext屬性,該屬性參考的是屬於 CollectionViewSource 元素的Windows.Resources。 鑒於該數據內容,當第一個文本框系結至 Address 時,該名稱會對應至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 Forms 應用程式中,您有一個 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事件映射到程式碼後端中的方法。 將此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 類別
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。 確認客戶和訂單數據已填入方格中,且導覽按鈕會如預期般運作。
選取 提交,以將新客戶或訂單新增至模型,前提是您已輸入數據。
選取 取消 以退出新的客戶或新訂單窗體,而不儲存資料。
若要直接對現有客戶和訂單進行編輯,請使用文本框,以自動將這些變更寫入模型。
相關內容
- 適用於 .NET 的 Visual Studio 資料工具
- Entity Framework 文件