Vytvoření jednoduché datové aplikace s WPF a Entity Framework 6
Varování
Pokud používáte Visual Studio 2022, měli byste pro účely tohoto kurzu použít Visual Studio 2022 verze 17.3 Preview 3 nebo novější.
Tento návod ukazuje, jak ve Visual Studiu vytvořit základní aplikaci "formuláře nad daty". Aplikace používá SQL Server LocalDB, databázi Northwind, Entity Framework 6 (nikoli Entity Framework Core) a Windows Presentation Foundation pro .NET Framework (ne .NET Core nebo .NET 5 nebo novější). Ukazuje, jak provádět základní vazby dat pomocí zobrazení podrobností předlohy a má také vlastní navigátor vazeb s tlačítky pro Přesunout další, Přesunout předchozí, Přesunout na začátek, Přesunout na konec, Aktualizovat a Odstranit.
Tento článek se zaměřuje na použití datových nástrojů v sadě Visual Studio a nepokoušá se vysvětlit základní technologie do hloubky. Předpokládá se, že máte základní znalosti XAML, Entity Frameworku a SQL. Tento příklad také neukazuje architekturu model-View-ViewModel (MVVM), což je standard pro aplikace WPF. Tento kód ale můžete zkopírovat do vlastní aplikace MVVM s několika úpravami.
Konečný kód pro tento kurz najdete na GitHubu na ukázkách kurzů sady Visual Studio – EF6.
Instalace a připojení k Northwind
Tento příklad používá SQL Server Express LocalDB a ukázkovou databázi Northwind. Pokud ADO.NET zprostředkovatele dat pro tento produkt podporuje Entity Framework, měl by fungovat i s dalšími databázovými produkty SQL.
Pokud nemáte SQL Server Express LocalDB, nainstalujte jej pomocí instalátoru Visual Studio . V instalačním programu sady Visual Studiomůžete SQL Server Express LocalDB nainstalovat jako součást ukládání a zpracování dat úlohy nebo jako jednotlivou komponentu.
Následujícím postupem nainstalujte ukázkovou databázi Northwind:
V sadě Visual Studio otevřete okno Průzkumník objektů SQL Serveru. ( Průzkumník objektů SYSTÉMU SQL Server je nainstalován jako součást úlohy Ukládání a zpracování dat v instalačním programu sady Visual Studio.) Rozbalte uzel SQL Server. Klikněte pravým tlačítkem na instanci LocalDB a vyberte Nový dotaz.
Otevře se okno editoru dotazů.
Zkopírujte skript Northwind Transact-SQL do schránky. Tento skript T-SQL vytvoří zcela novou databázi Northwind a naplní ji daty.
Vložte skript T-SQL do editoru dotazů a pak zvolte tlačítko Spustit.
Po krátké době se dotaz dokončí a vytvoří se databáze Northwind.
Přidat nová připojení pro Northwind.
Konfigurace projektu
V sadě Visual Studio vytvořte nový projekt aplikace WPF (.NET Framework) C#.
Přidejte balíček NuGet pro Entity Framework 6. V Průzkumníku řešenívyberte uzel projektu. V hlavní nabídce zvolte Project>Spravovat balíčky NuGet.
Ve správci balíčků NuGetklikněte na odkaz Procházet. Entity Framework je pravděpodobně hlavním balíčkem v seznamu. V pravém podokně klikněte na Nainstalovat a postupujte podle pokynů. Okno Výstup vám řekne, až je instalace dokončená.
Teď můžete pomocí sady Visual Studio vytvořit model založený na databázi Northwind.
Vytvoření modelu
Klikněte pravým tlačítkem myši na uzel projektu v průzkumníku řešení a zvolte Přidat>Nová položka. V levém podokně pod uzlem C# zvolte Data a v prostředním podokně zvolte ADO.NET Entity Data Model.
Zavolejte model
Northwind_model
a vyberte Přidat . Otevře se Průvodce datovým modelem entity. V databázi zvolteEF Designer a poté vyberte Další.Na další obrazovce zvolte připojení LocalDB Northwind (například (localdb)\MSSQLLocalDB), zadejte databázi Northwind a klikněte na Další.
Pokud se připojení nezobrazuje, zvolte Nové připojení, pak v dialogovém okně Zvolit zdroj dat, zvolte Microsoft SQL Server, zvolte Pokračovat a v dialogovém okně Vlastnosti připojení zadejte
(localdb)\MSSQLLocalDB
a v části Vyberte nebo zadejte název databáze, zvolte Northwind, potom stiskněte OK.Pokud se zobrazí výzva, zvolte verzi entity Framework, kterou používáte.
Na další stránce průvodce vyberte tabulky, uložené procedury a další databázové objekty, které chcete zahrnout do modelu Entity Framework. Rozbalte uzel dbo ve stromovém zobrazení a vyberte: Customers; Orders; a Order Details. Ponechte výchozí hodnoty zaškrtnuté a klikněte na Dokončit.
Průvodce vygeneruje třídy jazyka C#, které představují model Entity Framework. Třídy jsou obyčejné staré třídy jazyka C# a k nim vážeme data v uživatelském rozhraní WPF. Soubor
.edmx
popisuje relace a další metadata, která přidružují třídy k objektům v databázi. Soubory.tt
jsou šablony T4, které generují kód, který pracuje s modelem, a uloží změny do databáze. Všechny tyto soubory můžete zobrazit v Průzkumníku řešení pod uzlem Northwind_model:Plocha návrháře pro soubor
.edmx
umožňuje upravit některé vlastnosti a relace v modelu. V tomto názorném postupu nebudeme používat návrháře.Soubory
.tt
jsou pro obecné účely a potřebujete jeden z nich upravit pro práci s datovou vazbou WPF, což vyžaduje ObservableCollections. V průzkumníku řešení rozbalte uzel Northwind_model, dokud nenajdete Northwind_model.tt . (Ujistěte se, že nejste v souboru .Context.tt, který je přímo pod souborem.edmx
.)Nahraďte dva výskyty ICollectionObservableCollection<T>.
Kolem řádku 51 nahraďte první výskyt HashSet<T> s ObservableCollection<T>. Nenahrazovat druhý výskyt HashSet.
Nahraďte jediný výskyt System.Collections.Generic (kolem řádku 431) System.Collections.ObjectModel.
Stisknutím klávesy F5 nebo Ctrl+F5 sestavte a spusťte projekt. Při prvním spuštění aplikace jsou třídy modelu viditelné v průvodci zdroji dat.
Nyní jste připraveni připojit tento model na stránku XAML, abyste mohli zobrazit, procházet a upravovat data.
Datová vazba modelu na stránku XAML
Je možné napsat vlastní kód pro vytváření vazby dat, ale je mnohem jednodušší nechat Visual Studio, aby to udělal za vás.
V hlavní nabídce zvolte Project>Přidat nový zdroj dat, aby se zobrazil Průvodce konfigurací zdroje dat. Zvolte Objekt, protože vytváříte vazbu na třídy modelu, nikoli na databázi:
Rozbalte uzel projektu a vyberte Customer. (Zdroje pro objednávky se automaticky generují z navigační vlastnosti Objednávky v Zákazník.)
Klikněte na Dokončit.
V zobrazení kódu přejděte na MainWindow.xaml. Pro účely tohoto příkladu zachováváme jednoduchý kód XAML. Změňte název MainWindow na něco popisnějšího a prozatím zvyšte jeho výšku a šířku na 600 x 800. Můžete ho kdykoli později změnit. Teď přidejte tyto tři definice řádků do hlavní mřížky, jeden řádek pro navigační tlačítka, jeden pro podrobnosti zákazníka a druhý pro mřížku, která zobrazuje jejich objednávky:
<Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions>
Teď otevřete MainWindow.xaml, abyste ho zobrazili v návrháři. To způsobí, že se okno zdrojů dat zobrazí jako možnost na okraji okna ve Visual Studiu vedle sady nástrojů. Kliknutím na kartu otevřete okno, nebo stiskněte Shift+Alt+D, nebo zvolte Zobrazit>Jiná okna>Zdroje dat. Každou vlastnost v třídě Zákazníci zobrazíme ve vlastním samostatném textovém poli. Klikněte nejprve na šipku v seznamu Zákazníci a zvolte Podrobnosti. Potom přetáhněte uzel do prostřední části návrhové plochy, aby návrhář věděl, že má jít do prostředního řádku. Pokud ho chybně zadáte, můžete řádek zadat ručně později v XAML (
Grid.Row="1"
). Ve výchozím nastavení jsou ovládací prvky umístěny svisle v prvku mřížky, ale nyní je můžete uspořádat dle libosti na formuláři. Může například dávat smysl umístit textové pole Název nahoru, nad adresu. Ukázková aplikace pro tento článek změní pořadí polí a přeuspořádá je do dvou sloupců.V zobrazení XAML teď uvidíte nový prvek
Grid
v řádku 1 (prostřední řádek) nadřazené mřížky. Nadřazená mřížka má atributDataContext
, který odkazuje na CollectionViewSource, který byl přidán doWindows.Resources
elementu. Vzhledem k tomu kontextu dat, kdy se první textové pole váže na Adresa, je tento název mapován na vlastnostAddress
v aktuálním objektuCustomer
vCollectionViewSource
.<Grid DataContext="{StaticResource customerViewSource}">
Když je zákazník viditelný v horní polovině okna, chcete zobrazit objednávky v dolní polovině okna. Objednávky zobrazíte v jednom ovládacím prvku zobrazení mřížky. Aby vazby dat master-detail fungovaly podle očekávání, je důležité vytvořit vazbu na vlastnost Orders ve třídě Customers, nikoli na samostatný uzel Orders. Přetáhněte vlastnost Orders třídy Customers do dolní poloviny formuláře, aby ji návrhář umístil do řádku 2.
Visual Studio vygeneroval veškerý kód vazby, který spojuje ovládací prvky uživatelského rozhraní k událostem v modelu. Vše, co potřebujete udělat, aby se zobrazila nějaká data, je napsat kód pro naplnění modelu. Nejprve přejděte na MainWindow.xaml.cs a přidejte datový člen do Třídy MainWindow pro kontext dat. Tento objekt, který byl pro vás vygenerován, funguje jako ovládací prvek, který sleduje změny a události v modelu. Přidáte také datové členy CollectionViewSource pro zákazníky a objednávky a přidruženou logiku inicializace konstruktoru do existujícího konstruktoru
MainWindow()
. Horní část třídy by měla vypadat takto: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; }
Pokud tam ještě není, přidejte direktivu
using
pro System.Data.Entity, která přidá metodu rozšířeníLoad
do oboru:using System.Data.Entity;
Teď se posuňte dolů a vyhledejte obslužnou rutinu události
Window_Loaded
. Všimněte si, že Visual Studio přidalo objekt CollectionViewSource. Představuje NorthwindEntities objekt, který jste vybrali při vytváření modelu. Už jste to přidali, takže ho tady nepotřebujete. Pojďme nahradit kód vWindow_Loaded
tak, aby metoda teď vypadala takto: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; }
Stiskněte F5. Měli byste vidět podrobnosti o prvním zákazníkovi, který byl načten do zdroje CollectionView. Měli byste také vidět jejich objednávky v datové mřížce. Formátování není skvělé, takže to opravíme. Můžete také vytvořit způsob, jak zobrazit ostatní záznamy a provádět základní operace vytváření, čtení, aktualizace a odstraňování (CRUD).
Úprava návrhu stránky a přidání mřížek pro nové zákazníky a objednávky
Výchozí uspořádání vytvořené sadou Visual Studio není ideální pro vaši aplikaci, takže tady poskytneme konečný KÓD XAML, který se má zkopírovat do kódu. K tomu, aby uživatel mohl přidat nového zákazníka nebo objednávku, potřebujete také nějaké "formuláře" (které jsou ve skutečnosti gridy). Abyste mohli přidat nového zákazníka a objednávku, potřebujete samostatnou sadu textových polí, která nejsou svázaná s daty CollectionViewSource
. Určujete, která mřížka se uživateli v daném okamžiku zobrazí, nastavením vlastnosti Visible v metodách obslužné rutiny. Nakonec na každý řádek v mřížce Objednávky přidáte tlačítko Odstranit, které uživateli umožní odstranit jednotlivé objednávky.
Nejprve přidejte tyto styly do elementu Windows.Resources
v MainWindow.xaml:
<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>
Dále nahraďte celou vnější mřížku tímto kódem:
<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>
Přidání tlačítek pro navigaci, přidání, aktualizaci a odstranění
V aplikacích Windows Forms získáte objekt BindingNavigator s tlačítky pro procházení řádků v databázi a provádění základních operací CRUD. WPF neposkytuje BindingNavigator, ale stačí ho vytvořit. Uděláte to pomocí tlačítek uvnitř vodorovného objektu StackPanel a přidružíte tlačítka k příkazům, které jsou svázané s metodami v kódu za sebou.
Logika příkazů má čtyři části: (1) příkazy, (2) vazby, (3) tlačítka a (4) obslužné rutiny příkazů v kódu.
Přidání příkazů, vazeb a tlačítek v XAML
Nejprve přidejte příkazy do souboru MainWindow.xaml uvnitř elementu
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 mapuje tuto
RoutedUICommand
událost na metodu v zákulisním kódu. Přidejte tento prvekCommandBindings
za uzavírací značkuWindows.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>
Teď přidejte
StackPanel
pomocí navigačních tlačítek, přidejte, odstraňte a aktualizujte. Nejprve přidejte tento styl doWindows.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>
Za druhé vložte tento kód těsně za
RowDefinitions
vnějšího prvkuGrid
směrem k horní části stránky 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>
Přidání zpracovatelů příkazů do třídy MainWindow
Kód je minimální s výjimkou metod pro přidání a odstranění. Navigace se uskutečňuje voláním metod na vlastnosti View v rámci CollectionViewSource.
DeleteOrderCommandHandler
ukazuje, jak provést kaskádové odstranění v objednávce. Musíme nejprve odstranit Order_Details, které jsou k němu přidružené.
UpdateCommandHandler
přidá nového zákazníka nebo objednávku do kolekce, nebo jenom aktualizuje existujícího zákazníka nebo objednávku změnami, které uživatel provedl v textových polích.
Přidejte tyto metody obslužné rutiny do třídy MainWindow v MainWindow.xaml.cs. Pokud má vaše KolekceViewSource pro tabulku Customers jiný název, musíte upravit název v každé z těchto metod:
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);
}
Spuštění aplikace
Ladění spustíte stisknutím klávesy F5. V mřížce byste měli vidět data zákazníků a objednávek a navigační tlačítka by měla fungovat podle očekávání. Po zadání dat klikněte na Potvrdit a přidejte do modelu nový zákazník nebo objednávku. Kliknutím na Zrušit zrušíte formulář nového zákazníka nebo nové objednávky bez uložení dat. Úpravy stávajících zákazníků a objednávek můžete provádět přímo v textových polích a tyto změny se do modelu zapisují automaticky.