Baca dalam bahasa Inggris

Bagikan melalui


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 utama-detail, dan juga memiliki Navigator Binding kustom dengan tombol untuk Pindah Berikutnya, Pindah Sebelumnya, Pindah ke awal, Pindah ke akhir, Perbarui dan Hapus.

Artikel ini berfokus pada penggunaan alat data di Visual Studio, dan tidak mencoba menjelaskan teknologi yang mendasar secara mendalam. Ini mengasumsikan bahwa Anda memiliki keakraban dasar dengan XAML, Kerangka Kerja Entitas, 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

Contoh 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 Penginstal Visual Studio. Di Penginstal Visual Studio, Anda dapat menginstal SQL Server Express LocalDB sebagai bagian dari penyimpanan data dan memproses beban kerja, 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 penyimpanan data dan memproses beban kerja di Penginstal Visual Studio.) Perluas simpul SQL Server. Klik kanan pada instans LocalDB Anda dan pilih Kueri Baru.

      Jendela editor kueri terbuka.

    2. Salin skrip Transact-SQL Northwind 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.

Konfigurasi proyek

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

  2. Tambahkan paket NuGet untuk Entity Framework 6. Di Penjelajah Solusi, pilih simpul proyek. Di menu utama, pilih Project>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 Kerangka Kerja Entitas.

    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 simpul 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 Northwind_model model dan pilih Tambahkan. Wizard Model Data Entitas terbuka. Pilih EF Designer dari database, lalu pilih 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 Baru, lalu dalam kotak dialog Pilih Sumber Data, pilih Microsoft SQL Server, pilih Lanjutkan dan dalam kotak dialog Properti Koneksi, 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 mana, prosedur tersimpan, dan objek database lainnya yang akan disertakan dalam model Kerangka Kerja Entitas. Perluas simpul dbo pada tampilan pohon dan pilih Pelanggan, Pesanan, dan Detail Pesanan. Biarkan default dicentang dan klik Selesai.

    Cuplikan layar memilih Objek database untuk model.

  6. Wizard menghasilkan kelas C# yang mewakili model Kerangka Kerja Entitas. Kelas-kelasnya adalah kelas C# lama biasa dan kelas tersebut adalah apa yang kami ikatkan ke antarmuka pengguna WPF. File .edmx menjelaskan 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 pada database. Anda dapat melihat semua file ini di Penjelajah Solusi di bawah simpul Northwind_model:

    Cuplikan layar memperlihatkan file model Kerangka Kerja Entitas Penjelajah Solusi.

    Cuplikan layar yang memperlihatkan file model Entity Framework di Penjelajah Solusi

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

  7. File .tt adalah 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 file .edmx.)

  8. Tekan F5 atau Ctrl+F5 untuk membangun dan menjalankan proyek. Ketika aplikasi pertama kali dijalankan, kelas model dapat dilihat oleh penyihir pengaturan 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 pengikatan data Anda sendiri, tetapi jauh lebih mudah untuk membiarkan Visual Studio melakukannya untuk Anda.

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

    Cuplikan layar Wizard Konfigurasi Sumber Data dengan Sumber Objek.

  2. Perluas node untuk proyek Anda, dan pilih Customer. Sumber Pesanan dihasilkan secara otomatis dari properti navigasi Pesanan dalam 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 di 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 dapat mengubahnya nanti. Sekarang tambahkan tiga definisi baris ini ke kisi utama, satu baris untuk tombol navigasi, satu untuk detail pelanggan, dan satu untuk kisi yang menunjukkan pesanan mereka:

    XAML
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
  5. Sekarang buka MainWindow.xaml sehingga Anda dapat melihatnya di desainer. Ini menyebabkan jendela Sumber Data muncul sebagai opsi di margin jendela Visual Studio di samping kotak alat . Klik pada 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 desainer tahu Anda ingin masuk ke baris tengah. Jika Anda salah menempatkannya, Anda dapat menentukan baris secara manual nanti di XAML (Grid.Row="1"). Secara default, pengendali ditempatkan secara vertikal dalam elemen grid, namun sekarang, Anda dapat mengaturnya sesuka Anda pada formulir. Misalnya, mungkin masuk akal untuk meletakkan kotak teks Nama di atas, di atas alamat. Aplikasi sampel untuk artikel ini mengurutkan ulang dan menata ulang bidang 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 Grid baru di baris 1 (baris tengah) dari Grid induk. Grid induk memiliki atribut DataContext yang mengacu pada CollectionViewSource yang telah ditambahkan ke elemen Windows.Resources. Mengingat konteks data tersebut, ketika kotak teks pertama mengikat ke Alamat, nama tersebut dipetakan ke properti Address di objek Customer yang saat ini ada di CollectionViewSource.

    XAML
    <Grid DataContext="{StaticResource customerViewSource}">
    
  6. Ketika pelanggan terlihat di bagian atas jendela, Anda ingin melihat pesanan mereka di bagian bawah. Anda menampilkan pesanan dalam tampilan grid 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 yang diseret dan dijatuhkan sebagai kisi.

    Cuplikan layar memperlihatkan kelas Pesanan yang digeser dan ditempatkan sebagai grid.

  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 yang ada MainWindow(). Siswa terbaik di kelas seharusnya terlihat seperti ini:

    C#
    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 arahan using untuk System.Data.Entity untuk membawa metode ekstensi Load ke dalam cakupan:

    C#
    using System.Data.Entity;
    

    Sekarang, gulir ke bawah dan temukan pengendali acara Window_Loaded. 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 di Window_Loaded sehingga metode sekarang terlihat seperti ini:

    C#
    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 harus melihat detail untuk pelanggan pertama yang dimasukkan ke dalam CollectionViewSource. Anda juga akan melihat pesanan mereka di tabel data. Pemformatannya tidak bagus, jadi mari kita perbaiki. 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 adalah 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 menyetel properti Visible dalam metode pengolah. Terakhir, Anda menambahkan tombol Hapus ke setiap baris di tabel Pesanan untuk memungkinkan pengguna menghapus pesanan individual.

Pertama, tambahkan gaya ini ke elemen Windows.Resources di MainWindow.xaml:

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 Grid luar dengan markup ini:

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

Di aplikasi Windows Forms, 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 melakukan ini dengan tombol-tombol yang berada di dalam StackPanel yang diatur secara horisontal, dan mengaitkan tombol-tombol tersebut dengan Command yang terhubung pada metode-metode di dalam kode di belakang.

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

Menambahkan perintah, pengikatan, dan tombol di XAML

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

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

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

    XAML
    <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 Grid luar, ke bagian atas halaman XAML:

    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>
    

Menambahkan penangan perintah pada kelas MainWindow

Code-belakang minimal kecuali untuk metode tambah dan hapus. Navigasi dilakukan dengan memanggil metode pada properti Tampilan CollectionViewSource. DeleteOrderCommandHandler menunjukkan cara melakukan penghapusan bertingkat pada pesanan. Kita harus terlebih dahulu menghapus Order_Details yang terkait dengannya. UpdateCommandHandler menambahkan pelanggan baru atau pesanan ke dalam koleksi, atau sebaliknya hanya memperbarui pelanggan atau pesanan yang sudah 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:

C#
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 debug, tekan F5. Anda akan melihat data pelanggan dan pesanan yang diisi di kisi, dan tombol navigasi harus berfungsi seperti yang diharapkan. Klik Commit untuk menambahkan pelanggan baru atau pesanan ke model setelah Anda memasukkan data. Klik Batalkan untuk mundur dari pelanggan baru atau formulir pesanan baru tanpa menyimpan data. Anda dapat melakukan pengeditan pada pelanggan dan pesanan yang sudah ada langsung di kotak teks, dan perubahan tersebut disimpan ke model secara otomatis.