Pelatihan
Modul
Menyesuaikan tata letak di halaman .NET MAUI XAML - Training
Buat antarmuka pengguna yang konsisten di berbagai perangkat menggunakan StackLayout dan Grid.
Browser ini sudah tidak didukung.
Mutakhirkan ke Microsoft Edge untuk memanfaatkan fitur, pembaruan keamanan, dan dukungan teknis terkini.
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.
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.
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.
Instal database sampel Northwind dengan mengikuti langkah-langkah berikut:
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.
Salin skrip Transact-SQL Northwind ke clipboard Anda. Skrip T-SQL ini membuat database Northwind dari awal dan mengisinya dengan data.
Tempelkan skrip T-SQL ke editor kueri, lalu pilih tombol Jalankan.
Setelah beberapa saat, kueri selesai berjalan dan database Northwind dibuat.
Tambahkan koneksi baru untuk Northwind.
Di Visual Studio, buat proyek Aplikasi C# WPF (.NET Framework) baru.
Tambahkan paket NuGet untuk Entity Framework 6. Di Penjelajah Solusi, pilih simpul proyek. Di menu utama, pilih Project>Kelola Paket NuGet.
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.
Sekarang Anda dapat menggunakan Visual Studio untuk membuat model berdasarkan database Northwind.
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.
Panggil Northwind_model
model dan pilih Tambahkan. Wizard Model Data Entitas terbuka. Pilih EF Designer dari database, lalu pilih Berikutnya.
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.
Jika diminta, pilih versi Kerangka Kerja Entitas yang Anda gunakan.
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.
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 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.
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
.)
Ganti dua kemunculan ICollection dengan ObservableCollection<T>.
Ganti kemunculan pertama HashSet<T> dengan ObservableCollection<T> di sekitar baris 51. Jangan ganti kemunculan kedua HashSet.
Ganti satu-satunya kemunculan System.Collections.Generic (sekitar baris 431) dengan System.Collections.ObjectModel.
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.
Dimungkinkan untuk menulis kode pengikatan data Anda sendiri, tetapi jauh lebih mudah untuk membiarkan Visual Studio melakukannya untuk Anda.
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:
Perluas node untuk proyek Anda, dan pilih Customer. Sumber Pesanan dihasilkan secara otomatis dari properti navigasi Pesanan dalam Pelanggan.
Klik Selesai.
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:
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
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.
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
.
<Grid DataContext="{StaticResource customerViewSource}">
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:
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:
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:
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:
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;
}
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).
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:
<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:
<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>
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.
Pertama, tambahkan perintah di file MainWindow.xaml di dalam elemen Windows.Resources
:
<RoutedUICommand x:Key="FirstCommand" Text="First"/>
<RoutedUICommand x:Key="LastCommand" Text="Last"/>
<RoutedUICommand x:Key="NextCommand" Text="Next"/>
<RoutedUICommand x:Key="PreviousCommand" Text="Previous"/>
<RoutedUICommand x:Key="DeleteCustomerCommand" Text="Delete Customer"/>
<RoutedUICommand x:Key="DeleteOrderCommand" Text="Delete Order"/>
<RoutedUICommand x:Key="UpdateCommand" Text="Update"/>
<RoutedUICommand x:Key="AddCommand" Text="Add"/>
<RoutedUICommand x:Key="CancelCommand" Text="Cancel"/>
CommandBinding memetakan peristiwa RoutedUICommand
ke metode dalam kode di belakang. Tambahkan elemen CommandBindings
ini setelah tag penutup Windows.Resources
:
<Window.CommandBindings>
<CommandBinding Command="{StaticResource FirstCommand}" Executed="FirstCommandHandler"/>
<CommandBinding Command="{StaticResource LastCommand}" Executed="LastCommandHandler"/>
<CommandBinding Command="{StaticResource NextCommand}" Executed="NextCommandHandler"/>
<CommandBinding Command="{StaticResource PreviousCommand}" Executed="PreviousCommandHandler"/>
<CommandBinding Command="{StaticResource DeleteCustomerCommand}" Executed="DeleteCustomerCommandHandler"/>
<CommandBinding Command="{StaticResource DeleteOrderCommand}" Executed="DeleteOrderCommandHandler"/>
<CommandBinding Command="{StaticResource UpdateCommand}" Executed="UpdateCommandHandler"/>
<CommandBinding Command="{StaticResource AddCommand}" Executed="AddCommandHandler"/>
<CommandBinding Command="{StaticResource CancelCommand}" Executed="CancelCommandHandler"/>
</Window.CommandBindings>
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 Grid
luar, 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>
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:
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);
}
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.
Pelatihan
Modul
Menyesuaikan tata letak di halaman .NET MAUI XAML - Training
Buat antarmuka pengguna yang konsisten di berbagai perangkat menggunakan StackLayout dan Grid.