使用 WPF 和 Entity Framework 6 建立簡單的資料應用程式
警告
如果您使用 Visual Studio 2022,請在本教學課程中使用 Visual Studio 2022 17.3 版 Preview 3 或更新版本。
本逐步解說示範如何在 Visual Studio 中建立基本的「資料表單」(Forms Over Data) 應用程式。 此應用程式會使用 SQL Server LocalDB、Northwind 資料庫、Entity Framework 6 (而非 Entity Framework Core),以及適用於 .NET Framework (而非 .NET Core 或 .NET 5 或更新版本) 的 Windows Presentation Foundation。 其會示範如何使用主從式檢視執行基本的資料繫結,其也有具有 [移到下一個]、[移到上一個]、[移到開頭]、[移到結尾]、[更新] 和 [刪除] 的自訂繫結導覽器。
本文聚焦於如何在 Visual Studio 中使用資料工具,而不會嘗試說明任何程度的基礎技術。 文中假設您基本上已熟悉 XAML、Entity Framework 和 SQL。 此範例也不會示範屬於 WPF 應用程式標準的 Model-View-ViewModel (MVVM) 架構。 不過,您只要稍做修改,便可將此程式碼複製到自己的 MVVM 應用程式。
您可以在 GitHub 的 Visual Studio 教學課程範例 – EF6 中找到本教學課程的最終程式碼。
安裝並連線到 Northwind
本範例會使用 SQL Server Express LocalDB 和 Northwind 範例資料庫。 如果該產品的 ADO.NET 資料提供者支援 Entity Framework,其就也應該可以與其他 SQL 資料庫產品搭配運作。
如果您沒有 SQL Server Express LocalDB,請透過 Visual Studio 安裝程式加以安裝。 在 Visual Studio 安裝程式中,您可以將 SQL Server Express LocalDB 安裝為資料儲存和處理工作負載的一部分,或安裝為個別元件。
請遵循下列步驟安裝 Northwind 範例資料庫:
在 Visual Studio 中,開啟 [SQL Server 物件總管]視窗。 (在 Visual Studio 安裝程式中,SQL Server 物件總管會安裝為資料儲存和處理工作負載的一部分。)展開 [SQL Server]節點。 以滑鼠右鍵按一下您的 LocalDB 執行個體,然後選取 [新增查詢]。
查詢編輯器視窗會隨即開啟。
將 Northwind Transact-SQL 指令碼複製到剪貼簿。 此 T-SQL 指令碼會從頭開始建立 Northwind 資料庫,並將資料填入其中。
將 T-SQL 指令碼貼入查詢編輯器中,然後選擇 [執行] 按鈕。
查詢很快就會完成執行,並建立 Northwind 資料庫。
為 Northwind 新增連線。
設定專案
在 Visual Studio 中,建立新的 C# WPF 應用程式 (.NET Framework) 專案。
新增適用於 Entity Framework 6 的 NuGet 套件。 在 [方案總管] 中選取專案節點。 在主功能表中,選擇 [專案]>[管理 NuGet 套件]。
在 [NuGet 套件管理員] 中,按一下 [瀏覽] 連結。 Entity Framework 大概會是清單頂端的套件。 在右窗格中按一下 [安裝],然後遵循提示進行操作。 [輸出] 視窗會在安裝完成時讓您知道。
現在您可以使用 Visual Studio,根據 Northwind 資料庫建立模型。
建立模型
在 [方案總管] 中,以滑鼠右鍵按一下專案節點,然後選擇 [新增]>[新增項目]。 在左窗格中的 [C#] 節點下,選擇 [資料],然後在中間窗格中,選擇 [ADO.NET 實體資料模型]。
呼叫模型
Northwind_model
,然後選擇 [新增]。 [實體資料模型精靈] 隨即開啟。 選擇 [來自資料庫的 EF 設計工具],然後選取 [下一步]。在下一個畫面中,選擇您的 LocalDB Northwind 連線 (例如 (localdb)\MSSQLLocalDB)、指定 Northwind 資料庫,然後按 [下一步]。
如果沒有看到任何連線,請選擇 [新增連線],然後在 [選擇資料來源] 對話方塊中,選擇 [Microsoft SQL Server],選擇 [繼續],然後在 [連線屬性] 對話方塊中輸入
(localdb)\MSSQLLocalDB
,並在 [選取或輸入資料庫名稱] 下,選擇 [Northwind],然後按 [確定]。如果出現提示,請選擇您使用的 Entity Framework 版本。
在精靈的下一頁,選擇要包含在 Entity Framework 模型中的資料表、預存程序和其他資料庫物件。 展開樹狀檢視中的 [dbo] 節點,然後選擇 [客戶]、[訂單] 和 [訂單詳細資料]。 讓預設值保持核取狀態,然後按一下 [完成]。
精靈會產生代表 Entity Framework 模型的 C# 類別。 這些類別是簡單的 C# 類別,取自以資料繫結至 WPF 使用者介面的內容。
.edmx
檔案會描述關聯性以及其他會將類別與資料庫物件產生關聯的中繼資料。.tt
檔案是 T4 範本,會產生在模型上運作並將變更儲存至資料庫的程式碼。 您可以在 [方案總管] 的 [Northwind_model] 節點底下看到所有這些檔案:.edmx
檔案的設計工具介面可讓您修改模型中的某些屬性和關聯性。 我們不會在本逐步解說中使用此設計工具。.tt
檔案屬於一般用途,您需要調整其中一個檔案以處理需要 ObservableCollections 的 WPF 資料繫結。 在 [方案總管] 中,不斷展開 [Northwind_model] 節點,直到您找到「Northwind_model.tt」為止。 (請確定您不在 .Context.tt 檔案中,該檔案位於.edmx
檔案正下方。)將出現的兩個 ICollection 取代為 ObservableCollection<T>。
將出現的第一個 HashSet<T> 取代為 51 行附近的 ObservableCollection<T>。 請勿取代所出現的第二個 HashSet。
將出現的唯一一個 System.Collections.Generic (431 行附近) 取代為 System.Collections.ObjectModel。
按 F5 或 Ctrl+F5 以建置和執行專案。 應用程式第一次執行時,資料來源精靈可以看到模型類別。
現在您已準備好將此模型連結至 XAML 頁面,以便檢視、瀏覽及修改資料。
將模型以資料繫結至 XAML 頁面
您可以撰寫自己的資料繫結程式碼,但讓 Visual Studio 幫您撰寫會簡單得多。
從主功能表中,選擇 [專案]>[新增資料來源] 以顯示 [資料來源組態精靈]。 選擇 [物件],因為您要繫結至模型類別,而不是繫結至資料庫:
展開專案的節點,然後選取 [客戶]。 (系統會自動從 [客戶] 中的 [訂單] 導覽屬性產生訂單的來源。)
按一下完成。
在程式碼檢視中瀏覽至「MainWindow.xaml」。 為了進行此範例,我們會讓 XAML 保持簡單。 將 MainWindow 的標題變更為更能描述清楚的文字,且目前將其 Height 和 Width 增加到 600 x 800。 您稍後可以隨時加以變更。 現在,將這三列定義新增至主要格線,一列用於導覽按鈕、一列用於客戶詳細資料,一列用於顯示其訂單的格線:
<Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions>
現在開啟「MainWindow.xaml」,以便在設計工具中檢視該檔案。 這會導致 [資料來源] 視窗在 [工具箱] 旁的 Visual Studio 視窗邊界中顯示為選項。 按一下索引標籤以開啟該視窗,或按 Shift+Alt+D,或選擇 [檢視]>[其他視窗]>[資料來源]。 我們將在其各自的文字方塊中顯示 [客戶] 類別中的每個屬性。 首先,按一下 [客戶] 下拉式方塊中的箭號,然後選擇 [詳細資料]。 然後,將節點拖曳到設計介面的中間部分,讓設計工具知道您要讓該節點進入中間列。 如果位置不對,您稍後可以在 XAML (
Grid.Row="1"
) 中手動指定該列。 根據預設,控制項會垂直放在格線元素中,但此時,您可以依照喜好在表單上排列這些控制項。 例如,將 [名稱] 文字方塊放在地址之上可能很合理。 本文的範例應用程式會將欄位重新排序,並將其重新排列為兩行。在 XAML 檢視中,您現在可以在父格線的第 1 列 (中間列) 中看到新的
Grid
元素。 父格線具有DataContext
屬性,此屬性會參考已新增至 CollectionViewSource 元素的Windows.Resources
。 鑒於該資料內容,當第一個文字方塊繫結至 [地址] 時,該名稱會對應至Address
中目前Customer
物件中的CollectionViewSource
屬性。<Grid DataContext="{StaticResource customerViewSource}">
當客戶顯示在視窗上半部時,您想要在下半部看到其訂單。 您可以在單一格線檢視控制項中顯示訂單。 若要讓主從式繫結如預期般運作,請務必繫結至 [客戶] 類別中的 [訂單] 屬性,而不是繫結至個別的 [訂單] 節點。 將 [客戶] 類別的 [訂單] 屬性拖曳到表單下半部,讓設計工具將其放在第 2 列:
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; }
請按 F5。 您應該會看到第一個擷取到 CollectionViewSource 之客戶的詳細資料。 您也應該會在資料格線中看到其訂單。 格式不夠完美,讓我們修正一下。 您也可以建立方法來檢視其他記錄和執行基本的建立、讀取、更新和刪除 (CRUD) 作業。
調整頁面設計並為新的客戶和訂單新增格線
Visual Studio 所產生的預設排列不適合您的應用程式,因此我們將在這裡提供要複製到您程式碼的最終 XAML。 您也需要一些「表單」(實際上是格線),讓使用者能夠新增客戶或訂單。 若要能夠新增客戶和訂單,您需要另外一組未以資料繫結至 CollectionViewSource
的文字方塊。 您可以藉由在處理常式方法中設定 [可見] 屬性,以控制使用者在任何指定時間會看到的格線。 最後,您可以將 [刪除] 按鈕新增至 [訂單] 格線中的每一列,讓使用者能夠刪除個別訂單。
首先,將這些樣式新增至「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 內,並將按鈕關聯到繫結至程式碼後置中方法的命令。
命令邏輯有四個部分:(1) 命令、(2) 繫結、(3) 按鈕,以及 (4) 程式碼後置中的命令處理常式。
在 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>
接著,將這個程式碼貼到外部
RowDefinitions
元素的Grid
正後方,朝 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 的 [檢視] 屬性上呼叫方法,即可執行導覽。
DeleteOrderCommandHandler
會示範如何在訂單上執行串聯刪除。 我們必須先刪除與其相關聯的 Order_Details。
UpdateCommandHandler
會將新的客戶或訂單新增至集合,或是只使用使用者在文字方塊中所做的變更來更新現有的客戶或訂單。
將這些處理常式方法新增至「MainWindow.xaml.cs」中的 MainWindow 類別。 如果您的「CollectionViewSource for the Customers」資料表有不同名稱,則需要在每個方法中調整該名稱:
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。 您應該會看到格線中已填入客戶和訂單資料,而且導覽按鈕應該會如預期般運作。 在輸入資料後,按一下 [認可] 以將新的客戶或訂單新增至模型。 按一下 [取消] 可退出新客戶或新訂單的表單,而不儲存資料。 您可以直接在文字方塊中對現有的客戶和訂單進行編輯,而且這些變更會自動寫入模型中。