Tworzenie prostej aplikacji do obsługi danych za pomocą platform WPF i Entity Framework 6

Ostrzeżenie

Jeśli używasz programu Visual Studio 2022, na potrzeby tego samouczka użyj programu Visual Studio 2022 w wersji 17.3 (wersja zapoznawcza 3 lub nowsza).

W tym przewodniku pokazano, jak utworzyć podstawową aplikację "formularzy za pośrednictwem danych" w programie Visual Studio. Aplikacja korzysta z bazy danych SQL Server LocalDB, bazy danych Northwind, programu Entity Framework 6 (nie Entity Framework Core) i programu Windows Presentation Foundation dla platformy .NET Framework (nie .NET Core lub .NET 5 lub nowszego). Pokazuje on, jak wykonać podstawowe powiązanie danych z widokiem szczegółów wzorca, a także ma niestandardowy nawigator powiązania z przyciskami Przenieś dalej, Przenieś poprzedni, Przenieś do początku, Przenieś do końca, Aktualizuj i Usuń.

Ten artykuł koncentruje się na używaniu narzędzi do obsługi danych w programie Visual Studio i nie próbuje wyjaśnić podstawowych technologii w żaden sposób. Przyjęto założenie, że masz podstawową znajomość języków XAML, Entity Framework i SQL. W tym przykładzie nie przedstawiono również architektury Model-View-ViewModel (MVVM), która jest standardem dla aplikacji WPF. Można jednak skopiować ten kod do własnej aplikacji MVVM z kilkoma modyfikacjami.

Końcowy kod tego samouczka można znaleźć w witrynie GitHub na stronie Przykłady samouczków programu Visual Studio — EF6.

Instalowanie i nawiązywanie połączenia z usługą Northwind

W tym przykładzie użyto bazy danych SQL Server Express LocalDB i przykładowej bazy danych Northwind. Jeśli dostawca danych ADO.NET dla tego produktu obsługuje program Entity Framework, powinien również współpracować z innymi produktami bazy danych SQL.

  1. Jeśli nie masz bazy danych SQL Server Express LocalDB, zainstaluj ją za pośrednictwem Instalator programu Visual Studio. W Instalator programu Visual Studio można zainstalować bazę danych SQL Server Express LocalDB w ramach obciążenia Magazynu i przetwarzania danych lub jako pojedynczy składnik.

  2. Zainstaluj przykładową bazę danych Northwind, wykonując następujące kroki:

    1. W programie Visual Studio otwórz okno Eksplorator obiektów programu SQL Server. (program SQL Server Eksplorator obiektów jest instalowany w ramach obciążenia Magazynu danych i przetwarzania w Instalator programu Visual Studio). Rozwiń węzeł PROGRAMU SQL Server. Kliknij prawym przyciskiem myszy wystąpienie bazy danych LocalDB i wybierz pozycję Nowe zapytanie.

      Zostanie otwarte okno edytora zapytań.

    2. Skopiuj skrypt Northwind Transact-SQL do schowka. Ten skrypt języka T-SQL tworzy bazę danych Northwind od podstaw i wypełnia ją danymi.

    3. Wklej skrypt języka T-SQL do edytora zapytań, a następnie wybierz przycisk Wykonaj .

      Po krótkim czasie zapytanie zakończy działanie i zostanie utworzona baza danych Northwind.

  3. Dodaj nowe połączenia dla systemu Northwind.

Konfigurowanie projektu

  1. W programie Visual Studio utwórz nowy projekt aplikacji WPF języka C# (.NET Framework).

  2. Dodaj pakiet NuGet dla programu Entity Framework 6. W Eksplorator rozwiązań wybierz węzeł projektu. W menu głównym wybierz pozycję Project Manage NuGet Packages (Zarządzanie pakietami NuGet w projekcie>).

  3. W Menedżer pakietów NuGet kliknij link Przeglądaj. Platforma Entity Framework jest prawdopodobnie najlepszym pakietem na liście. Kliknij pozycję Zainstaluj w okienku po prawej stronie i postępuj zgodnie z monitami. Okno Dane wyjściowe informuje o zakończeniu instalacji.

    Zrzut ekranu przedstawiający pakiet NuGet pakietu NuGet programu Entity Framework.

    Zrzut ekranu przedstawiający pakiet NuGet programu Entity Framework.

  4. Teraz możesz użyć programu Visual Studio do utworzenia modelu opartego na bazie danych Northwind.

Tworzenie modelu

  1. Kliknij prawym przyciskiem myszy węzeł projektu w Eksplorator rozwiązań i wybierz polecenie Dodaj>nowy element. W okienku po lewej stronie w węźle C# wybierz pozycję Dane , a następnie w środkowym okienku wybierz pozycję ADO.NET Entity Data Model.

    Zrzut ekranu przedstawiający nowy element modelu platformy Entity Framework.

    Zrzut ekranu przedstawiający nowy element modelu platformy Entity Framework.

  2. Wywołaj model Northwind_model i wybierz pozycję Dodaj. Zostanie otwarty Kreator modelu danych jednostki. Wybierz pozycję EF Projektant z bazy danych, a następnie kliknij przycisk Dalej.

    Zrzut ekranu przedstawiający model EF z bazy danych.

  3. Na następnym ekranie wybierz połączenie LocalDB Northwind (na przykład (localdb)\MSSQLLocalDB), określ bazę danych Northwind, a następnie kliknij przycisk Dalej.

    Jeśli nie widzisz połączenia, wybierz pozycję Nowy Połączenie ion, a następnie w oknie dialogowym Wybieranie źródła danych wybierz pozycję Microsoft SQL Server, wybierz pozycję Kontynuuj, a następnie w oknie dialogowym Właściwości Połączenie ion wprowadź i (localdb)\MSSQLLocalDB w obszarze Wybierz lub wprowadź nazwę bazy danych, wybierz pozycję Northwind, a następnie naciśnij przycisk OK.

  4. Jeśli zostanie wyświetlony monit, wybierz używaną wersję programu Entity Framework.

    Zrzut ekranu przedstawiający opcje wersji.

  5. Na następnej stronie kreatora wybierz tabele, procedury składowane i inne obiekty bazy danych do uwzględnienia w modelu Entity Framework. Rozwiń węzeł dbo w widoku drzewa i wybierz pozycję Klienci, Zamówienia i Szczegóły zamówienia. Pozostaw zaznaczone wartości domyślne i kliknij przycisk Zakończ.

    Zrzut ekranu przedstawiający wybieranie obiektów bazy danych dla modelu.

  6. Kreator generuje klasy języka C#, które reprezentują model platformy Entity Framework. Klasy są zwykłymi starymi klasami języka C# i są one powiązaniami danych z interfejsem użytkownika WPF. W .edmx pliku opisano relacje i inne metadane, które kojarzą klasy z obiektami w bazie danych. Pliki .tt to szablony T4, które generują kod działający na modelu i zapisują zmiany w bazie danych. Wszystkie te pliki są widoczne w Eksplorator rozwiązań w węźle Northwind_model:

    Zrzut ekranu przedstawiający pliki modelu Eksplorator rozwiązań Entity Framework.

    Zrzut ekranu przedstawiający pliki modelu Eksplorator rozwiązań Entity Framework

    Powierzchnia projektanta .edmx pliku umożliwia modyfikowanie niektórych właściwości i relacji w modelu. W tym przewodniku nie będziemy używać projektanta.

  7. Pliki .tt są ogólnego przeznaczenia i należy dostosować jeden z nich do pracy z powiązaniem danych WPF, co wymaga ObservableCollections. W Eksplorator rozwiązań rozwiń węzeł Northwind_model do momentu znalezienia Northwind_model.tt. (Upewnij się, że nie znajdujesz się w pliku .Context.tt , który znajduje się bezpośrednio poniżej .edmx pliku).

  8. Naciśnij klawisz F5 lub Ctrl+F5, aby skompilować i uruchomić projekt. Po pierwszym uruchomieniu aplikacji klasy modelu są widoczne dla kreatora źródeł danych.

Teraz możesz podłączyć ten model do strony XAML, aby móc wyświetlać, nawigować i modyfikować dane.

Powiązanie modelu ze stroną XAML

Istnieje możliwość napisania własnego kodu powiązania danych, ale znacznie łatwiej jest umożliwić programowi Visual Studio wykonywanie go za Ciebie.

  1. W menu głównym wybierz pozycję Projekt>Dodaj nowe źródło danych, aby wyświetlić Kreatora konfiguracji źródła danych. Wybierz pozycję Obiekt , ponieważ wiążesz się z klasami modeli, a nie z bazą danych:

    Zrzut ekranu kreatora konfiguracji źródła danych ze źródłem obiektu.

  2. Rozwiń węzeł projektu i wybierz pozycję Klient. (Źródła zamówień są generowane automatycznie na podstawie właściwości nawigacji Orders w kliencie).

    Zrzut ekranu przedstawiający dodawanie klas jednostek jako źródeł danych.

    Zrzut ekranu przedstawiający dodawanie klas jednostek jako źródeł danych.

  3. Kliknij przycisk Zakończ.

  4. Przejdź do pliku MainWindow.xaml w widoku kodu. Na potrzeby tego przykładu utrzymujemy prosty kod XAML. Zmień tytuł mainWindow na bardziej opisowy i zwiększ jego wysokość i szerokość do 600 x 800 na razie. Zawsze możesz go zmienić później. Teraz dodaj te trzy definicje wierszy do siatki głównej, jeden wiersz dla przycisków nawigacji, jeden dla szczegółów klienta i jeden dla siatki, w której są wyświetlane ich zamówienia:

        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
  5. Teraz otwórz plik MainWindow.xaml , aby wyświetlić go w projektancie. Spowoduje to wyświetlenie okna Źródła danych jako opcji na marginesie okna programu Visual Studio obok przybornika. Kliknij kartę, aby otworzyć okno lub naciśnij klawisze Shift+Alt+D lub wybierz pozycję Wyświetl>inne źródła danych systemu Windows.> Każda właściwość zostanie wyświetlona w klasie Customers w osobnym polu tekstowym. Najpierw kliknij strzałkę w polu kombi Klienci i wybierz pozycję Szczegóły. Następnie przeciągnij węzeł na środkową część powierzchni projektowej, aby projektant wiedział, że ma przejść w środkowym wierszu. Jeśli go zastąp, możesz ręcznie określić wiersz w dalszej części kodu XAML (Grid.Row="1"). Domyślnie kontrolki są umieszczane w pionie w elemecie siatki, ale w tym momencie można je rozmieścić, jak chcesz w formularzu. Na przykład warto umieścić pole tekstowe Nazwa u góry, nad adresem. Przykładowa aplikacja tego artykułu zmienia kolejność pól i zmienia ich rozmieszczenie w dwie kolumny.

    Zrzut ekranu przedstawiający powiązanie źródła danych klientów z poszczególnymi kontrolkami.

    Zrzut ekranu przedstawiający powiązanie źródła danych klientów z poszczególnymi kontrolkami.

    W widoku XAML można teraz zobaczyć nowy Grid element w wierszu 1 (środkowym wierszu) nadrzędnej siatki. Nadrzędna siatka ma DataContext atrybut, który odwołuje się do CollectionViewSource elementu , który został dodany do Windows.Resources elementu. Biorąc pod uwagę, że kontekst danych, gdy pierwsze pole tekstowe wiąże się z adresem, ta nazwa jest mapowana na Address właściwość w bieżącym Customer obiekcie w CollectionViewSourceobiekcie .

    <Grid DataContext="{StaticResource customerViewSource}">
    
  6. Gdy klient jest widoczny w górnej połowie okna, chcesz zobaczyć swoje zamówienia w dolnej połowie. Zamówienia są wyświetlane w jednej kontrolce widoku siatki. Aby powiązanie danych ze szczegółami wzorca działało zgodnie z oczekiwaniami, ważne jest, aby wiązać się z właściwością Orders w klasie Customers, a nie z oddzielnym węzłem Zamówienia. Przeciągnij właściwość Orders klasy Customers do dolnej połowy formularza, aby projektant umieścił go w wierszu 2:

    Zrzut ekranu przedstawiający klasy Orders przeciągnięte i porzucone jako siatkę.

    Zrzut ekranu przedstawiający klasy Orders przeciągnięte i porzucone jako siatkę.

  7. Program Visual Studio wygenerował cały kod powiązania, który łączy kontrolki interfejsu użytkownika z zdarzeniami w modelu. Wszystko, co musisz zrobić, aby wyświetlić niektóre dane, to napisanie kodu w celu wypełnienia modelu. Najpierw przejdź do MainWindow.xaml.cs i dodaj element członkowski danych do klasy MainWindow dla kontekstu danych. Ten obiekt, który został wygenerowany dla Ciebie, działa podobnie jak kontrolka, która śledzi zmiany i zdarzenia w modelu. Dodasz również elementy członkowskie danych CollectionViewSource dla klientów i zamówień oraz skojarzoną logikę inicjowania konstruktora do istniejącego konstruktora MainWindow(). Górna część klasy powinna wyglądać następująco:

    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;
        }
    

    Jeśli jeszcze jej nie ma, dodaj dyrektywę System.Data.Entity, aby przenieść Load metodę using rozszerzenia do zakresu:

    using System.Data.Entity;
    

    Teraz przewiń w dół i znajdź procedurę Window_Loaded obsługi zdarzeń. Zwróć uwagę, że program Visual Studio dodał obiekt CollectionViewSource. Reprezentuje obiekt NorthwindEntities wybrany podczas tworzenia modelu. Dodano to już, więc nie potrzebujesz go tutaj. Zastąpmy kod w pliku Window_Loaded , aby metoda wyglądała teraz następująco:

    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. Naciśnij klawisz F5. Powinny zostać wyświetlone szczegóły pierwszego klienta, który został pobrany do kolekcji CollectionViewSource. Zamówienia powinny być również widoczne w siatce danych. Formatowanie nie jest świetne, więc poprawmy to. Możesz również utworzyć sposób wyświetlania innych rekordów i wykonywania podstawowych operacji tworzenia, odczytu, aktualizowania i usuwania (CRUD).

Dostosowywanie projektu strony i dodawanie siatek dla nowych klientów i zamówień

Domyślny układ utworzony przez program Visual Studio nie jest idealny dla Twojej aplikacji, dlatego w tym miejscu udostępnimy ostateczny kod XAML, aby skopiować go do kodu. Potrzebujesz również niektórych formularzy (które są rzeczywiście siatkami), aby umożliwić użytkownikowi dodanie nowego klienta lub zamówienia. Aby można było dodać nowego klienta i zamówienie, potrzebny jest oddzielny zestaw pól tekstowych, które nie są powiązane z danymi.CollectionViewSource W dowolnym momencie będziesz kontrolować, która siatka będzie widoczna dla użytkownika, ustawiając właściwość Visible w metodach obsługi. Na koniec do każdego wiersza w siatce Orders (Zamówienia) dodasz przycisk Usuń, aby umożliwić użytkownikowi usunięcie pojedynczego zamówienia.

Najpierw dodaj te style do Windows.Resources elementu w pliku 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>

Następnie zastąp całą zewnętrzną siatkę następującym znacznikiem:

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

Dodawanie przycisków do nawigowania, dodawania, aktualizowania i usuwania

W aplikacjach Windows Forms uzyskujesz obiekt BindingNavigator z przyciskami umożliwiającymi nawigowanie po wierszach w bazie danych i wykonywanie podstawowych operacji CRUD. WPF nie zapewnia bindingNavigator, ale jest wystarczająco łatwy, aby go utworzyć. Można to zrobić za pomocą przycisków wewnątrz poziomego StackPanel i skojarzyć przyciski z poleceniami powiązanymi z metodami w kodzie za.

Logika poleceń składa się z czterech części: (1) polecenia, (2) powiązania, (3) przyciski i (4) procedury obsługi poleceń w kodzie.

Dodawanie poleceń, powiązań i przycisków w języku XAML

  1. Najpierw dodaj polecenia w pliku MainWindow.xaml wewnątrz Windows.Resources elementu :

    <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. PolecenieBinding mapuje RoutedUICommand zdarzenie na metodę w kodzie. Dodaj ten CommandBindings element po tagu Windows.Resources zamykającym:

    <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. Teraz dodaj element StackPanel za pomocą przycisków nawigacji, dodaj, usuń i zaktualizuj. Najpierw dodaj ten styl do Windows.Resourceselementu :

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

    Następnie wklej ten kod tuż po elemecie RowDefinitions zewnętrznym Grid w górnej części strony 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>
    

Dodawanie programów obsługi poleceń do klasy MainWindow

Kod-behind jest minimalny z wyjątkiem metod dodawania i usuwania. Nawigacja jest wykonywana przez wywołanie metod we właściwości View obiektu CollectionViewSource. W tym DeleteOrderCommandHandler artykule pokazano, jak wykonać usuwanie kaskadowe w zamówieniu. Najpierw musimy usunąć Order_Details, które są z nim skojarzone. Obiekt UpdateCommandHandler dodaje nowego klienta lub zamówienie do kolekcji albo po prostu aktualizuje istniejącego klienta lub zamówienie ze zmianami wprowadzanymi przez użytkownika w polach tekstowych.

Dodaj te metody obsługi do klasy MainWindow w MainWindow.xaml.cs. Jeśli tabela CollectionViewSource dla tabeli Customers ma inną nazwę, musisz dostosować nazwę w każdej z następujących 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);
}

Uruchamianie aplikacji

Aby rozpocząć debugowanie, naciśnij klawisz F5. Powinny zostać wyświetlone dane klienta i zamówienia wypełnione w siatce, a przyciski nawigacji powinny działać zgodnie z oczekiwaniami. Kliknij pozycję Zatwierdź , aby dodać nowego klienta lub zamówienie do modelu po wprowadzeniu danych. Kliknij przycisk Anuluj , aby wycofać nowego klienta lub nowego formularza zamówienia bez zapisywania danych. Zmiany można wprowadzać w istniejących klientach i zamówieniach bezpośrednio w polach tekstowych, a te zmiany są zapisywane automatycznie w modelu.