Membuat aplikasi data sederhana dengan WPF dan Entity Framework 6

Peringatan

Jika Anda menggunakan Visual Studio 2022, Anda harus menggunakan Visual Studio 2022 versi 17.3 Pratinjau 3 atau yang lebih baru untuk tutorial ini.

Panduan ini menunjukkan cara membuat aplikasi "formulir atas data" dasar di Visual Studio. Aplikasi ini menggunakan SQL Server LocalDB, database Northwind, Entity Framework 6 (bukan Entity Framework Core), dan Windows Presentation Foundation untuk .NET Framework (bukan .NET Core atau .NET 5 atau yang lebih baru). Ini menunjukkan cara melakukan pengikatan data dasar dengan tampilan detail master, dan juga memiliki Navigator Pengikatan kustom dengan tombol untuk Pindah Berikutnya, PindahKan Sebelumnya, Pindah ke awal, Pindah ke akhir, Perbarui dan Hapus.

Artikel ini berfokus pada penggunaan alat data dalam Visual Studio, dan tidak mencoba menjelaskan teknologi yang mendasar secara mendalam. Ini mengasumsikan bahwa Anda memiliki keakraban dasar dengan XAML, Entity Framework, dan SQL. Contoh ini juga tidak menunjukkan arsitektur Model-View-ViewModel (MVVM), yang merupakan standar untuk aplikasi WPF. Namun, Anda dapat menyalin kode ini ke dalam aplikasi MVVM Anda sendiri dengan beberapa modifikasi.

Kode akhir untuk tutorial ini dapat ditemukan di GitHub di Sampel Tutorial Visual Studio - EF6.

Menginstal dan menyambungkan ke Northwind

Panduan ini menggunakan SQL Server Express LocalDB dan database sampel Northwind. Jika penyedia data ADO.NET untuk produk tersebut mendukung Kerangka Kerja Entitas, itu harus bekerja dengan produk database SQL lainnya juga.

  1. Jika Anda tidak memiliki SQL Server Express LocalDB, instal melalui Alat Penginstal Visual Studio. Di Alat Penginstal Visual Studio, Anda dapat memasang SQL Server Express LocalDB sebagai bagian dari beban kerja Penyimpanan dan pemrosesan data, atau sebagai komponen individual.

  2. Instal database sampel Northwind dengan mengikuti langkah-langkah berikut:

    1. Di Visual Studio, buka jendela SQL Server Object Explorer. (SQL Server Object Explorer diinstal sebagai bagian dari beban kerja Penyimpanan dan pemrosesan data di Alat Penginstal Visual Studio.) Perluas simpul SQL Server. Klik kanan pada instans LocalDB Anda dan pilih Kueri Baru.

      Jendela editor kueri terbuka.

    2. Salin skrip Northwind Transact-SQL ke clipboard Anda. Skrip T-SQL ini membuat database Northwind dari awal dan mengisinya dengan data.

    3. Tempelkan skrip T-SQL ke editor kueri, lalu pilih tombol Jalankan.

      Setelah beberapa saat, kueri selesai berjalan dan database Northwind dibuat.

  3. Tambahkan koneksi baru untuk Northwind.

Mengonfigurasi proyek

  1. Di Visual Studio, buat proyek Aplikasi C# WPF (.NET Framework) baru.

  2. Tambahkan paket NuGet untuk Entity Framework 6. Di Penjelajah Solusi, pilihlah simpul proyek. Di menu utama, pilih Proyek>Kelola Paket NuGet.

  3. Di Manajer Paket NuGet, klik tautan Telusuri. Entity Framework mungkin merupakan paket teratas dalam daftar. Klik Instal di panel kanan dan ikuti perintah. Jendela Output memberi tahu Anda ketika penginstalan selesai.

    Cuplikan layar paket NuGet Paket NuGet Entity Framework.

    Cuplikan layar memperlihatkan Paket NuGet Kerangka Kerja Entitas.

  4. Sekarang Anda dapat menggunakan Visual Studio untuk membuat model berdasarkan database Northwind.

Membuat model

  1. Klik kanan pada node proyek di Penjelajah Solusi dan pilih Tambahkan>Item Baru. Di panel kiri, di bawah simpul C#, pilih Data dan di panel tengah, pilih ADO.NET Model Data Entitas.

    Cuplikan layar Item Baru Model Kerangka Kerja Entitas.

    Cuplikan layar Item Baru Model Kerangka Kerja Entitas.

  2. Panggil model Northwind_model dan pilih Tambahkan. Wizard Model Data Entitas terbuka. Pilih Desainer EF dari database lalu klik Berikutnya.

    Cuplikan layar Model EF dari Database.

  3. Di layar berikutnya, pilih koneksi LocalDB Northwind Anda (misalnya, (localdb)\MSSQLLocalDB), tentukan database Northwind, dan klik Berikutnya.

    Jika Anda tidak melihat koneksi, pilih Koneksi ion Baru, lalu dalam kotak dialog Pilih Sumber Data, pilih Microsoft SQL Server, pilih Lanjutkan dan dalam kotak dialog Properti Koneksi ion, masukkan (localdb)\MSSQLLocalDB dan di bawah Pilih atau masukkan nama database, pilih Northwind, lalu tekan OK.

  4. Jika diminta, pilih versi Kerangka Kerja Entitas yang Anda gunakan.

    Cuplikan layar memperlihatkan pilihan versi.

  5. Di halaman panduan berikutnya, pilih tabel, prosedur tersimpan, dan objek database lainnya yang akan disertakan dalam model Kerangka Kerja Entitas. Perluas simpul dbo dalam tampilan pohon dan pilih Pelanggan, Pesanan, dan Detail Pesanan. Pastikan default telah dicentang dan klik Finish.

    Cuplikan layar memilih Objek database untuk model.

  6. Wizard menghasilkan kelas C# yang mewakili model Entity Framework. Kelas-kelasnya adalah kelas C# lama biasa dan kelas tersebut adalah apa yang kami ikatkan data ke antarmuka pengguna WPF. File menjelaskan .edmx hubungan dan metadata lain yang mengaitkan kelas dengan objek dalam database. File .tt adalah templat T4 yang menghasilkan kode yang beroperasi pada model dan menyimpan perubahan ke database. Anda dapat melihat semua file ini di Penjelajah Solusi di bawah simpul Northwind_model:

    Cuplikan layar memperlihatkan Penjelajah Solusi file model Entity Framework.

    Cuplikan layar memperlihatkan file model Kerangka Kerja Entitas Penjelajah Solusi

    Permukaan perancang .edmx untuk file memungkinkan Anda memodifikasi beberapa properti dan hubungan dalam model. Kami tidak akan menggunakan perancang dalam panduan ini.

  7. File adalah .tt tujuan umum dan Anda perlu mengubah salah satunya untuk bekerja dengan pengikatan data WPF, yang memerlukan ObservableCollections. Di Penjelajah Solusi, perluas simpul Northwind_model hingga Anda menemukan Northwind_model.tt. (Pastikan Anda tidak berada dalam file .Context.tt , yang berada tepat di bawah .edmx file.)

  8. Tekan F5 atau Ctrl+F5 untuk membangun dan menjalankan proyek. Saat aplikasi pertama kali berjalan, kelas model terlihat oleh wizard sumber data.

Sekarang Anda siap untuk menghubungkan model ini ke halaman XAML sehingga Anda dapat melihat, menavigasi, dan memodifikasi data.

Databind model ke halaman XAML

Dimungkinkan untuk menulis kode databinding Anda sendiri, tetapi jauh lebih mudah untuk membiarkan Visual Studio melakukannya untuk Anda.

  1. Dari menu utama, pilih Proyek>Tambahkan sumber data baru untuk memunculkan Panduan Konfigurasi Sumber Data. Pilih Objek karena Anda mengikat kelas model, bukan ke database:

    Cuplikan layar Wizard Konfigurasi Sumber Data dengan Sumber Objek.

  2. Perluas simpul untuk proyek Anda, dan pilih Pelanggan. (Sumber untuk Pesanan secara otomatis dihasilkan dari properti navigasi Pesanan di Pelanggan.)

    Cuplikan layar memperlihatkan penambahan kelas entitas sebagai sumber data.

    Cuplikan layar memperlihatkan penambahan kelas entitas sebagai sumber data.

  3. Klik Selesai.

  4. Navigasi ke MainWindow.xaml dalam Tampilan Kode. Kami menjaga XAML tetap sederhana untuk tujuan contoh ini. Ubah judul MainWindow menjadi sesuatu yang lebih deskriptif, dan tingkatkan Tinggi dan Lebarnya menjadi 600 x 800 untuk saat ini. Anda selalu bisa mengubah induknya nanti. Sekarang tambahkan ketiga definisi baris ini ke kisi utama, satu baris untuk tombol navigasi, satu untuk detail pelanggan, dan satu untuk kisi yang menunjukkan pesanan mereka:

        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
  5. Sekarang buka MainWindow.xaml sehingga Anda melihatnya di perancang. Hal ini menyebabkan jendela Sumber Data muncul sebagai opsi di margin jendela Visual Studio di samping Kotak Alat. Klik tab untuk membuka jendela, atau tekan Shift+Alt+D atau pilih Tampilkan>Sumber Data>Windows Lainnya. Kami akan menampilkan setiap properti di kelas Pelanggan di kotak teks individualnya sendiri. Pertama, klik panah dalam kotak kombo Pelanggan dan pilih Detail. Kemudian, seret simpul ke bagian tengah permukaan desain sehingga perancang tahu Anda ingin simpul berada di baris tengah. Jika Anda salah menempatkannya, Anda dapat menentukan baris secara manual nanti di XAML (Grid.Row="1"). Secara default, kontrol ditempatkan secara vertikal dalam elemen kisi, tetapi pada titik ini, Anda dapat mengaturnya sesuai keinginan Anda pada formulir. Misalnya, mungkin masuk akal untuk meletakkan kotak teks Nama di atas, di atas alamat. Aplikasi contoh untuk artikel ini menyusun ulang bidang dan menyusun ulang menjadi dua kolom.

    Cuplikan layar memperlihatkan Pengikatan sumber data Pelanggan ke kontrol individual.

    Cuplikan layar memperlihatkan Pengikatan sumber data Pelanggan ke kontrol individual.

    Dalam tampilan XAML, Anda sekarang dapat melihat elemen baru Grid di baris 1 (baris tengah) dari Grid induk. Grid induk memiliki DataContext atribut yang mengacu pada CollectionViewSource yang telah ditambahkan ke Windows.Resources elemen . Mengingat konteks data tersebut, ketika kotak teks pertama mengikat ke Alamat, nama tersebut Address dipetakan ke properti dalam objek saat ini Customer di CollectionViewSource.

    <Grid DataContext="{StaticResource customerViewSource}">
    
  6. Ketika pelanggan terlihat di bagian atas jendela, Anda ingin melihat pesanan mereka di bagian bawah. Anda memperlihatkan pesanan dalam kontrol tampilan kisi tunggal. Agar pengikatan data detail master berfungsi seperti yang diharapkan, penting bagi Anda untuk mengikat properti Pesanan di kelas Pelanggan, bukan ke simpul Pesanan terpisah. Seret properti Pesanan kelas Pelanggan ke bagian bawah formulir, sehingga perancang meletakkannya di baris 2:

    Cuplikan layar memperlihatkan kelas Pesanan diseret dan dihilangkan sebagai kisi.

    Cuplikan layar memperlihatkan kelas Pesanan diseret dan dihilangkan sebagai kisi.

  7. Visual Studio telah menghasilkan semua kode pengikatan yang menghubungkan kontrol UI ke peristiwa dalam model. Yang perlu Anda lakukan, untuk melihat beberapa data, adalah menulis beberapa kode untuk mengisi model. Pertama, navigasikan ke MainWindow.xaml.cs dan tambahkan anggota data ke kelas MainWindow untuk konteks data. Objek ini, yang telah dihasilkan untuk Anda, bertindak seperti kontrol yang melacak perubahan dan peristiwa dalam model. Anda juga akan menambahkan anggota data CollectionViewSource untuk pelanggan dan pesanan, dan logika inisialisasi konstruktor terkait ke konstruktor MainWindow()yang ada. Bagian atas kelas akan terlihat seperti ini:

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

    Jika belum ada, tambahkan using arahan untuk System.Data.Entity untuk membawa Load metode ekstensi ke dalam cakupan:

    using System.Data.Entity;
    

    Sekarang, gulir ke bawah dan temukan Window_Loaded penanganan aktivitas. Perhatikan bahwa Visual Studio telah menambahkan objek CollectionViewSource. Ini mewakili objek NorthwindEntities yang Anda pilih saat membuat model. Anda sudah menambahkannya, jadi Anda tidak membutuhkannya di sini. Mari kita ganti kode Window_Loaded sehingga metode sekarang terlihat seperti ini:

    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. Tekan F5. Anda akan melihat detail untuk pelanggan pertama yang diambil ke dalam CollectionViewSource. Anda juga akan melihat pesanannya di kisi data. Pemformatannya tidak bagus, jadi mari kita perbaiki itu. Anda juga dapat membuat cara untuk melihat rekaman lain dan melakukan operasi dasar membuat, membaca, memperbarui, dan menghapus (CRUD).

Sesuaikan desain halaman dan tambahkan kisi untuk pelanggan dan pesanan baru

Pengaturan default yang dihasilkan oleh Visual Studio tidak ideal untuk aplikasi Anda, jadi kami akan menyediakan XAML akhir di sini untuk disalin ke dalam kode Anda. Anda juga memerlukan beberapa "formulir" (yang sebenarnya Grid) untuk memungkinkan pengguna menambahkan pelanggan atau pesanan baru. Agar dapat menambahkan pelanggan dan pesanan baru, Anda memerlukan sekumpulan kotak teks terpisah yang tidak terikat data ke CollectionViewSource. Anda akan mengontrol kisi mana yang dilihat pengguna pada waktu tertentu dengan mengatur properti Terlihat dalam metode handler. Terakhir, Anda menambahkan tombol Hapus ke setiap baris di kisi Pesanan untuk memungkinkan pengguna menghapus urutan individual.

Pertama, tambahkan gaya ini ke Windows.Resources elemen di 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>

Selanjutnya, ganti seluruh Kisi luar dengan markup ini:

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

Menambahkan tombol untuk menavigasi, menambahkan, memperbarui, dan menghapus

Dalam aplikasi Formulir Windows, Anda mendapatkan objek BindingNavigator dengan tombol untuk menavigasi baris dalam database dan melakukan operasi CRUD dasar. WPF tidak menyediakan BindingNavigator, tetapi cukup mudah untuk membuatnya. Anda melakukannya dengan tombol di dalam horizontal StackPanel, dan mengaitkan tombol dengan Perintah yang terikat ke metode dalam kode di belakang.

Ada empat bagian untuk logika perintah: (1) perintah, (2) pengikatan, (3) tombol, dan (4) penangan perintah di code-behind.

Tambahkan perintah, pengikatan, dan tombol di XAML

  1. Pertama, tambahkan perintah dalam file MainWindow.xaml di dalam Windows.Resources elemen:

    <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 memetakan RoutedUICommand peristiwa ke metode dalam kode di belakang. Tambahkan CommandBindings elemen ini setelah Windows.Resources tag penutup:

    <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. Sekarang, tambahkan StackPanel dengan tombol navigasi, tambahkan, hapus, dan perbarui. Pertama, tambahkan gaya ini ke 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>
    

    Kedua, tempelkan kode ini tepat setelah RowDefinitions untuk elemen luar Grid, ke bagian atas halaman 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>
    

Tambahkan handler perintah ke kelas MainWindow

Kode di belakang minimal kecuali untuk metode tambahkan dan hapus. Navigasi dilakukan dengan memanggil metode pada properti Tampilan collectionViewSource. menunjukkan DeleteOrderCommandHandler cara melakukan penghapusan kaskade pada pesanan. Kita harus terlebih dahulu menghapus Order_Details yang terkait dengannya. menambahkan UpdateCommandHandler pelanggan baru atau memesan ke koleksi, atau hanya memperbarui pelanggan atau pesanan yang ada dengan perubahan yang dibuat pengguna dalam kotak teks.

Tambahkan metode handler ini ke kelas MainWindow di MainWindow.xaml.cs. Jika tabel CollectionViewSource untuk Pelanggan Anda memiliki nama yang berbeda, maka Anda perlu menyesuaikan nama di setiap metode ini:

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

Jalankan aplikasi

Untuk memulai penelusuran kesalahan, tekan F5. Anda akan melihat data pelanggan dan pesanan yang diisi di kisi, dan tombol navigasi akan berfungsi seperti yang diharapkan. Klik Terapkan untuk menambahkan pelanggan baru atau memesan ke model setelah Anda memasukkan data. Klik Batal untuk mundur dari pelanggan baru atau formulir pesanan baru tanpa menyimpan data. Anda dapat mengedit pelanggan dan pesanan yang ada langsung di kotak teks, dan perubahan tersebut ditulis ke model secara otomatis.