Vytvoření jednoduché datové aplikace pomocí WPF a Entity Framework 6

Upozorňující

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 v sadě Visual Studio vytvořit základní "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 kurzech 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.

  1. Pokud nemáte SQL Server Express LocalDB, nainstalujte ho prostřednictvím Instalační program pro Visual Studio. V Instalační program pro Visual Studio můžete sql Server Express LocalDB nainstalovat jako součást úlohy ukládání a zpracování dat nebo jako jednotlivé komponenty.

  2. Následujícím postupem nainstalujte ukázkovou databázi Northwind:

    1. V sadě Visual Studio otevřete okno Průzkumník objektů SQL Serveru. (SQL Server Průzkumník objektů se instaluje jako součást úlohy ukládání a zpracování dat v Instalační program pro Visual Studio.) Rozbalte uzel SQL Serveru. Klikněte pravým tlačítkem na instanci LocalDB a vyberte Nový dotaz.

      Otevře se okno editoru dotazů.

    2. Zkopírujte do schránky skript Northwind Transact-SQL. Tento skript T-SQL vytvoří zcela novou databázi Northwind a naplní ji daty.

    3. 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.

  3. Přidejte nová připojení pro Northwind.

Konfigurace projektu

  1. V sadě Visual Studio vytvořte nový projekt aplikace WPF jazyka C# (.NET Framework).

  2. Přidejte balíček NuGet pro Entity Framework 6. V Průzkumník řešení vyberte uzel projektu. V hlavní nabídce zvolte Spravovat balíčky NuGet projectu>.

  3. V Správce balíčků NuGet klikněte na odkaz Procházet. Entity Framework je pravděpodobně hlavním balíčkem v seznamu. Klikněte na Nainstalovat v pravém podokně a postupujte podle pokynů. Okno Výstup vám řekne, až se instalace dokončí.

    Snímek obrazovky s balíčkem NuGet entity Framework NuGet

    Snímek obrazovky znázorňující balíček NuGet Entity Framework

  4. Teď můžete pomocí sady Visual Studio vytvořit model založený na databázi Northwind.

Vytvoření modelu

  1. Klikněte pravým tlačítkem na uzel projektu v Průzkumník řešení a zvolte Přidat>novou položku. V levém podokně v uzlu C# zvolte Data a v prostředním podokně zvolte ADO.NET Entity Data Model.

    Snímek obrazovky s novou položkou modelu Entity Framework

    Snímek obrazovky s novou položkou modelu Entity Framework

  2. Zavolejte model Northwind_model a zvolte Přidat. Otevře se Průvodce datovým modelem entity. V databázi zvolte EF Designer a potom klepněte na tlačítko Další.

    Snímek obrazovky modelu EF z databáze

  3. Na další obrazovce zvolte připojení LocalDB Northwind (například (localdb)\MSSQLLocalDB), zadejte databázi Northwind a klikněte na Další.

    Pokud připojení nevidíte, zvolte Nový Připojení ion, pak v dialogovém okně Zvolit zdroj dat zvolte Microsoft SQL Server, zvolte Pokračovat a v dialogovém okně Vlastnosti Připojení ion zadejte (localdb)\MSSQLLocalDB a v části Vyberte nebo zadejte název databáze, zvolte Northwind a pak stiskněte OK.

  4. Pokud se zobrazí výzva, zvolte verzi entity Framework, kterou používáte.

    Snímek obrazovky zobrazující volby verzí

  5. 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 zvolte Zákazníci, Objednávky a Podrobnosti objednávky. Ponechte zaškrtnuté výchozí hodnoty a klikněte na Dokončit.

    Snímek obrazovky s výběrem databázových objektů pro model

  6. Průvodce vygeneruje třídy jazyka C#, které představují model Entity Framework. Třídy jsou prosté staré třídy jazyka C# a jsou to, co jsme databindovali s uživatelským rozhraním 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ík řešení pod uzlem Northwind_model:

    Snímek obrazovky znázorňující soubory modelu Entity Framework Průzkumník řešení

    Snímek obrazovky znázorňující soubory modelu Entity Framework Průzkumník řešení

    Plocha návrháře pro .edmx soubor umožňuje upravit některé vlastnosti a relace v modelu. V tomto názorném postupu nebudeme používat návrháře.

  7. 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ík řešení rozbalte uzel Northwind_model, dokud nenajdete Northwind_model.tt. (Ujistěte se, že se nenacházíte v souboru .Context.tt , který je přímo pod souborem .edmx .)

  8. 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.

  1. V hlavní nabídce zvolte Project>Add new data source to bring up the Data Source Configuration Wizard. Zvolte Objekt , protože vytváříte vazbu na třídy modelu, nikoli na databázi:

    Snímek obrazovky s Průvodce konfigurací zdroje dat se zdrojem objektů

  2. Rozbalte uzel projektu a vyberte Zákazník. (Zdroje pro objednávky se automaticky generují z navigační vlastnosti Objednávky v customer.)

    Snímek obrazovky znázorňující přidání tříd entit jako zdrojů dat

    Snímek obrazovky znázorňující přidání tříd entit jako zdrojů dat

  3. Klikněte na Finish (Dokončit).

  4. 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>
    
  5. Teď otevřete MainWindow.xaml , abyste ho zobrazili v návrháři. To způsobí, že se okno Zdroje dat zobrazí jako možnost v okraji okna sady Visual Studio vedle panelu nástrojů. Kliknutím na kartu otevřete okno, jinak stiskněte Shift+Alt+D nebo zvolte Zobrazit>další zdroje dat Windows.> Každou vlastnost v třídě Zákazníci zobrazíme ve vlastním samostatném textovém poli. Nejprve klikněte na šipku v poli Se seznamem 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 kódu XAML (Grid.Row="1"). Ve výchozím nastavení jsou ovládací prvky umístěny svisle v prvku mřížky, ale v tomto okamžiku je můžete uspořádat, ale chcete je uspořádat ve formuláři. Může například dávat smysl umístit textové pole Název nad adresu. Ukázková aplikace pro tento článek změní pořadí polí a přeuspořádá je do dvou sloupců.

    Snímek obrazovky znázorňující vazbu zdroje dat Customers s jednotlivými ovládacími prvky

    Snímek obrazovky znázorňující vazbu zdroje dat Customers s jednotlivými ovládacími prvky

    V zobrazení XAML teď uvidíte nový Grid prvek v řádku 1 (prostřední řádek) nadřazené mřížky. Nadřazená mřížka má DataContext atribut, který odkazuje na přidaný CollectionViewSource prvek Windows.Resources . Vzhledem k tomu, že kontext dat, když první textové pole vytvoří vazbu na Adresu, tento název je namapován na Address vlastnost v aktuálním Customer objektu v objektu CollectionViewSource.

    <Grid DataContext="{StaticResource customerViewSource}">
    
  6. 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 ho návrhář umístí do řádku 2:

    Snímek obrazovky zobrazující třídy Objednávek přetažené a zahozené jako mřížku

    Snímek obrazovky zobrazující třídy Objednávek přetažené a zahozené jako mřížku

  7. 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á metodu Load rozšíření přenese do oboru:

    using System.Data.Entity;
    

    Teď se posuňte dolů a vyhledejte obslužnou rutinu Window_Loaded události. Všimněte si, že Sada Visual Studio přidala 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 Window_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;
    }
    
  8. Stiskněte klávesu F5. Měli byste vidět podrobnosti pro prvního zákazníka, který byl načten do CollectionViewSource. 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 model Windows Forms aplikacích 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

  1. 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"/>
    
  2. CommandBinding mapuje RoutedUICommand událost na metodu v kódu za sebou. Přidejte tento CommandBindings prvek za Windows.Resources uzavírací značku:

    <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>
    
  3. Teď přidejte StackPanel tlačítka navigace, přidejte, odstraňte a aktualizujte. Nejprve přidejte tento styl do 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>
    

    Za druhé vložte tento kód těsně za RowDefinitions vnější Grid prvek 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í obslužných rutin příkazů do třídy MainWindow

Kód je minimální s výjimkou metod add a delete. Navigace se provádí voláním metod pro View Vlastnost CollectionViewSource. Ukazuje DeleteOrderCommandHandler , jak provést kaskádové odstranění v objednávce. Musíme nejprve odstranit Order_Details, které jsou k němu přidružené. Přidá UpdateCommandHandler nového zákazníka nebo objednávku do kolekce nebo jenom aktualizuje existujícího zákazníka nebo objednávku pomocí změn, 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 tlačítko Storno se vrátíte z nového formuláře 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.