Komandan
Dalam aplikasi .NET Multi-platform App UI (.NET MAUI) yang menggunakan pola Model-View-ViewModel (MVVM), pengikatan data didefinisikan di antara properti di viewmodel, yang biasanya merupakan kelas yang berasal dari INotifyPropertyChanged
, dan properti dalam tampilan, yang biasanya merupakan file XAML. Terkadang aplikasi memiliki kebutuhan yang melampaui pengikatan properti ini dengan mengharuskan pengguna untuk memulai perintah yang memengaruhi sesuatu di viewmodel. Perintah ini umumnya disinyalir oleh klik tombol atau ketukan jari, dan secara tradisional diproses dalam file code-behind dalam handler untuk Clicked
peristiwa Button atau Tapped
peristiwa TapGestureRecognizer.
Antarmuka perintah menyediakan pendekatan alternatif untuk menerapkan perintah yang jauh lebih cocok untuk arsitektur MVVM. Viewmodel dapat berisi perintah, yang merupakan metode yang dijalankan sebagai reaksi terhadap aktivitas tertentu dalam tampilan seperti Button klik. Pengikatan data didefinisikan antara perintah ini dan Button.
Untuk mengizinkan pengikatan data antara dan Button viewmodel, Button menentukan dua properti:
Command
dari jenisSystem.Windows.Input.ICommand
CommandParameter
dari jenisObject
Untuk menggunakan antarmuka perintah, Anda menentukan pengikatan data yang menargetkan Command
properti Button tempat sumber adalah properti dalam viewmodel jenis ICommand. Viewmodel berisi kode yang ICommand terkait dengan properti yang dijalankan saat tombol diklik. Anda dapat mengatur CommandParameter
properti ke data arbitrer untuk membedakan antara beberapa tombol jika semuanya terikat ke properti yang sama ICommand di viewmodel.
Banyak tampilan lain juga mendefinisikan Command
dan CommandParameter
properti. Semua perintah ini dapat ditangani dalam viewmodel menggunakan pendekatan yang tidak bergantung pada objek antarmuka pengguna dalam tampilan.
ICommands
Antarmuka ICommand didefinisikan dalam namespace Layanan System.Windows.Input , dan terdiri dari dua metode dan satu peristiwa:
public interface ICommand
{
public void Execute (Object parameter);
public bool CanExecute (Object parameter);
public event EventHandler CanExecuteChanged;
}
Untuk menggunakan antarmuka perintah, viewmodel Anda harus berisi properti jenis ICommand:
public ICommand MyCommand { private set; get; }
Viewmodel juga harus mereferensikan kelas yang mengimplementasikan ICommand antarmuka. Dalam tampilan, Command
properti terikat Button ke properti tersebut:
<Button Text="Execute command"
Command="{Binding MyCommand}" />
Ketika pengguna menekan Button, pengguna memanggil Execute
metode dalam objek yang ICommand terikat ke propertinya Command
Button.
Ketika pengikatan pertama kali didefinisikan pada Command
properti Button, dan ketika pengikatan data berubah dalam beberapa cara, Button memanggil CanExecute
metode dalam ICommand objek. Jika CanExecute
mengembalikan false
, maka menonaktifkan Button dirinya sendiri. Ini menunjukkan bahwa perintah tertentu saat ini tidak tersedia atau tidak valid.
juga Button melampirkan handler pada CanExecuteChanged
peristiwa ICommand. Peristiwa dimunculkan dari dalam viewmodel. Ketika peristiwa itu dinaikkan, Button panggilan CanExecute
lagi. mengaktifkan Button dirinya sendiri jika CanExecute
mengembalikan true
dan menonaktifkan dirinya sendiri jika CanExecute
mengembalikan false
.
Peringatan
Jangan gunakan IsEnabled
properti Button jika Anda menggunakan antarmuka perintah.
Saat viewmodel Anda menentukan properti jenis ICommand, viewmodel juga harus berisi atau mereferensikan kelas yang mengimplementasikan ICommand antarmuka. Kelas ini harus berisi atau mereferensikan Execute
metode dan CanExecute
, dan menembakkan CanExecuteChanged
peristiwa setiap kali CanExecute
metode mungkin mengembalikan nilai yang berbeda. Anda dapat menggunakan kelas atau Command<T>
yang Command
disertakan dalam .NET MAUI untuk mengimplementasikan ICommand antarmuka. Kelas-kelas ini memungkinkan Anda menentukan isi Execute
metode dan CanExecute
di konstruktor kelas.
Tip
Gunakan Command<T>
saat Anda menggunakan CommandParameter
properti untuk membedakan antara beberapa tampilan yang terikat ke properti yang sama ICommand , dan Command
kelas saat itu bukan persyaratan.
Perintah dasar
Contoh berikut menunjukkan perintah dasar yang diterapkan dalam viewmodel.
Kelas PersonViewModel
mendefinisikan tiga properti bernama Name
, Age
, dan Skills
yang menentukan seseorang:
public class PersonViewModel : INotifyPropertyChanged
{
string name;
double age;
string skills;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
set { SetProperty(ref name, value); }
get { return name; }
}
public double Age
{
set { SetProperty(ref age, value); }
get { return age; }
}
public string Skills
{
set { SetProperty(ref skills, value); }
get { return skills; }
}
public override string ToString()
{
return Name + ", age " + Age;
}
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Kelas PersonCollectionViewModel
yang ditunjukkan di bawah ini membuat objek baru jenis PersonViewModel
dan memungkinkan pengguna untuk mengisi data. Untuk tujuan itu, kelas mendefinisikan IsEditing
, dari jenis bool
, dan PersonEdit
, dari jenis PersonViewModel
, properti. Selain itu, kelas mendefinisikan tiga properti jenis ICommand dan properti bernama Persons
jenis IList<PersonViewModel>
:
public class PersonCollectionViewModel : INotifyPropertyChanged
{
PersonViewModel personEdit;
bool isEditing;
public event PropertyChangedEventHandler PropertyChanged;
···
public bool IsEditing
{
private set { SetProperty(ref isEditing, value); }
get { return isEditing; }
}
public PersonViewModel PersonEdit
{
set { SetProperty(ref personEdit, value); }
get { return personEdit; }
}
public ICommand NewCommand { private set; get; }
public ICommand SubmitCommand { private set; get; }
public ICommand CancelCommand { private set; get; }
public IList<PersonViewModel> Persons { get; } = new ObservableCollection<PersonViewModel>();
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Dalam contoh ini, perubahan pada tiga ICommand properti dan Persons
properti tidak mengakibatkan PropertyChanged
peristiwa dinaikkan. Properti ini semua diatur ketika kelas pertama kali dibuat dan tidak berubah.
Contoh berikut menunjukkan XAML yang menggunakan PersonCollectionViewModel
:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.PersonEntryPage"
Title="Person Entry">
<ContentPage.BindingContext>
<local:PersonCollectionViewModel />
</ContentPage.BindingContext>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- New Button -->
<Button Text="New"
Grid.Row="0"
Command="{Binding NewCommand}"
HorizontalOptions="Start" />
<!-- Entry Form -->
<Grid Grid.Row="1"
IsEnabled="{Binding IsEditing}">
<Grid BindingContext="{Binding PersonEdit}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="Name: " Grid.Row="0" Grid.Column="0" />
<Entry Text="{Binding Name}"
Grid.Row="0" Grid.Column="1" />
<Label Text="Age: " Grid.Row="1" Grid.Column="0" />
<StackLayout Orientation="Horizontal"
Grid.Row="1" Grid.Column="1">
<Stepper Value="{Binding Age}"
Maximum="100" />
<Label Text="{Binding Age, StringFormat='{0} years old'}"
VerticalOptions="Center" />
</StackLayout>
<Label Text="Skills: " Grid.Row="2" Grid.Column="0" />
<Entry Text="{Binding Skills}"
Grid.Row="2" Grid.Column="1" />
</Grid>
</Grid>
<!-- Submit and Cancel Buttons -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Submit"
Grid.Column="0"
Command="{Binding SubmitCommand}"
VerticalOptions="Center" />
<Button Text="Cancel"
Grid.Column="1"
Command="{Binding CancelCommand}"
VerticalOptions="Center" />
</Grid>
<!-- List of Persons -->
<ListView Grid.Row="3"
ItemsSource="{Binding Persons}" />
</Grid>
</ContentPage>
Dalam contoh ini, properti halaman BindingContext
diatur ke PersonCollectionViewModel
. Grid berisi Button dengan teks Baru dengan properti terikat Command
ke NewCommand
properti di viewmodel, formulir entri dengan properti yang terikat ke IsEditing
properti, serta properti PersonViewModel
, dan dua tombol lagi yang terikat ke SubmitCommand
properti dan CancelCommand
viewmodel. Menampilkan ListView kumpulan orang yang sudah dimasukkan:
Cuplikan layar berikut menunjukkan tombol Kirim diaktifkan setelah usia ditetapkan:
Ketika pengguna pertama kali menekan tombol Baru , ini mengaktifkan formulir entri tetapi menonaktifkan tombol Baru . Pengguna kemudian memasukkan nama, usia, dan keterampilan. Kapan saja selama pengeditan, pengguna dapat menekan tombol Batal untuk memulai kembali. Hanya ketika nama dan usia yang valid telah dimasukkan adalah tombol Kirim diaktifkan. Menekan tombol Kirim ini akan mentransfer orang ke koleksi yang ditampilkan oleh ListView. Setelah tombol Batalkan atau Kirim ditekan, formulir entri dikosongkan dan tombol Baru diaktifkan lagi.
Semua logika untuk tombol Baru, Kirim, dan Batal ditangani PersonCollectionViewModel
melalui definisi NewCommand
properti , , SubmitCommand
dan CancelCommand
. Konstruktor dari PersonCollectionViewModel
set ketiga properti ini ke objek jenis Command
.
Konstruktor Command
kelas memungkinkan Anda untuk meneruskan argumen jenis Action
dan Func<bool>
sesuai dengan Execute
metode dan CanExecute
. Tindakan dan fungsi ini dapat didefinisikan sebagai fungsi lambda dalam Command
konstruktor:
public class PersonCollectionViewModel : INotifyPropertyChanged
{
···
public PersonCollectionViewModel()
{
NewCommand = new Command(
execute: () =>
{
PersonEdit = new PersonViewModel();
PersonEdit.PropertyChanged += OnPersonEditPropertyChanged;
IsEditing = true;
RefreshCanExecutes();
},
canExecute: () =>
{
return !IsEditing;
});
···
}
void OnPersonEditPropertyChanged(object sender, PropertyChangedEventArgs args)
{
(SubmitCommand as Command).ChangeCanExecute();
}
void RefreshCanExecutes()
{
(NewCommand as Command).ChangeCanExecute();
(SubmitCommand as Command).ChangeCanExecute();
(CancelCommand as Command).ChangeCanExecute();
}
···
}
Ketika pengguna mengklik tombol Baru , fungsi yang execute
diteruskan ke Command
konstruktor dijalankan. Ini membuat objek baru PersonViewModel
, mengatur handler pada peristiwa objek tersebut PropertyChanged
, diatur IsEditing
ke true
, dan memanggil metode yang RefreshCanExecutes
ditentukan setelah konstruktor.
Selain mengimplementasikan ICommand antarmuka, Command
kelas juga mendefinisikan metode bernama ChangeCanExecute
. Viewmodel harus memanggil ChangeCanExecute
ICommand properti setiap kali terjadi sesuatu yang mungkin mengubah nilai CanExecute
pengembalian metode. Panggilan untuk ChangeCanExecute
menyebabkan Command
kelas menembakkan CanExecuteChanged
metode . telah melampirkan handler untuk peristiwa tersebut Button dan merespons dengan memanggil CanExecute
lagi, lalu mengaktifkan dirinya sendiri berdasarkan nilai pengembalian metode tersebut.
execute
Ketika metode NewCommand
panggilan RefreshCanExecutes
, NewCommand
properti mendapatkan panggilan ke ChangeCanExecute
, dan Button memanggil canExecute
metode , yang sekarang kembali false
karena IsEditing
properti sekarang true
.
Handler PropertyChanged
untuk objek baru PersonViewModel
memanggil ChangeCanExecute
metode :SubmitCommand
public class PersonCollectionViewModel : INotifyPropertyChanged
{
···
public PersonCollectionViewModel()
{
···
SubmitCommand = new Command(
execute: () =>
{
Persons.Add(PersonEdit);
PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
PersonEdit = null;
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
{
return PersonEdit != null &&
PersonEdit.Name != null &&
PersonEdit.Name.Length > 1 &&
PersonEdit.Age > 0;
});
···
}
···
}
Fungsi canExecute
untuk SubmitCommand
dipanggil setiap kali ada properti yang diubah dalam objek yang PersonViewModel
sedang diedit. Ini hanya mengembalikan true
ketika Name
properti setidaknya memiliki panjang satu karakter, dan Age
lebih besar dari 0. Pada saat itu, tombol Kirim menjadi diaktifkan.
execute
Fungsi untuk Kirim menghapus handler yang diubah properti dari PersonViewModel
, menambahkan objek ke Persons
koleksi, dan mengembalikan semuanya ke status awalnya.
Fungsi execute
untuk tombol Batal melakukan semua yang dilakukan tombol Kirim kecuali menambahkan objek ke koleksi:
public class PersonCollectionViewModel : INotifyPropertyChanged
{
···
public PersonCollectionViewModel()
{
···
CancelCommand = new Command(
execute: () =>
{
PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
PersonEdit = null;
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
{
return IsEditing;
});
}
···
}
Metode mengembalikan canExecute
true
kapan saja yang PersonViewModel
sedang diedit.
Catatan
Tidak perlu mendefinisikan execute
metode dan canExecute
sebagai fungsi lambda. Anda dapat menulisnya sebagai metode privat di viewmodel dan mereferensikannya di Command
konstruktor. Namun, pendekatan ini dapat menghasilkan banyak metode yang direferensikan hanya sekali di viewmodel.
Menggunakan parameter Perintah
Terkadang nyaman untuk satu atau beberapa tombol, atau objek antarmuka pengguna lainnya, untuk berbagi properti yang sama ICommand di viewmodel. Dalam hal ini, Anda dapat menggunakan CommandParameter
properti untuk membedakan antara tombol.
Anda dapat terus menggunakan Command
kelas untuk properti bersama ICommand ini. Kelas mendefinisikan konstruktor alternatif yang menerima execute
dan canExecute
metode dengan parameter jenis Object
. Ini adalah bagaimana diteruskan CommandParameter
ke metode ini. Namun, saat menentukan CommandParameter
, paling mudah untuk menggunakan kelas generik Command<T>
untuk menentukan jenis objek yang diatur ke CommandParameter
. Metode dan canExecute
yang Anda tentukan memiliki parameter jenis tersebutexecute
.
Contoh berikut menunjukkan keyboard untuk memasukkan angka desimal:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.DecimalKeypadPage"
Title="Decimal Keyboard">
<ContentPage.BindingContext>
<local:DecimalKeypadViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<Style TargetType="Button">
<Setter Property="FontSize" Value="32" />
<Setter Property="BorderWidth" Value="1" />
<Setter Property="BorderColor" Value="Black" />
</Style>
</ContentPage.Resources>
<Grid WidthRequest="240"
HeightRequest="480"
ColumnDefinitions="80, 80, 80"
RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto"
ColumnSpacing="2"
RowSpacing="2"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label Text="{Binding Entry}"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
Margin="0,0,10,0"
FontSize="32"
LineBreakMode="HeadTruncation"
VerticalTextAlignment="Center"
HorizontalTextAlignment="End" />
<Button Text="CLEAR"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Command="{Binding ClearCommand}" />
<Button Text="⇦"
Grid.Row="1" Grid.Column="2"
Command="{Binding BackspaceCommand}" />
<Button Text="7"
Grid.Row="2" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="7" />
<Button Text="8"
Grid.Row="2" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="8" />
<Button Text="9"
Grid.Row="2" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="9" />
<Button Text="4"
Grid.Row="3" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="4" />
<Button Text="5"
Grid.Row="3" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="5" />
<Button Text="6"
Grid.Row="3" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="6" />
<Button Text="1"
Grid.Row="4" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="1" />
<Button Text="2"
Grid.Row="4" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="2" />
<Button Text="3"
Grid.Row="4" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="3" />
<Button Text="0"
Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
Command="{Binding DigitCommand}"
CommandParameter="0" />
<Button Text="·"
Grid.Row="5" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="." />
</Grid>
</ContentPage>
Dalam contoh ini, halaman BindingContext
adalah DecimalKeypadViewModel
. Properti Entry viewmodel ini terikat ke Text
properti dari Label. Button Semua objek terikat ke perintah di viewmodel: ClearCommand
, , BackspaceCommand
dan DigitCommand
. 11 tombol untuk 10 digit dan titik desimal berbagi pengikatan ke DigitCommand
. Pembeda CommandParameter
antara tombol-tombol ini. Nilai yang diatur ke CommandParameter
umumnya sama dengan teks yang ditampilkan oleh tombol kecuali untuk titik desimal, yang untuk tujuan kejelasan ditampilkan dengan karakter titik tengah:
DecimalKeypadViewModel
mendefinisikan Entry properti jenis string
dan tiga properti jenis ICommand:
public class DecimalKeypadViewModel : INotifyPropertyChanged
{
string entry = "0";
public event PropertyChangedEventHandler PropertyChanged;
···
public string Entry
{
private set
{
if (entry != value)
{
entry = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Entry"));
}
}
get
{
return entry;
}
}
public ICommand ClearCommand { private set; get; }
public ICommand BackspaceCommand { private set; get; }
public ICommand DigitCommand { private set; get; }
}
Tombol yang ClearCommand
sesuai dengan selalu diaktifkan dan mengatur entri kembali ke "0":
public class DecimalKeypadViewModel : INotifyPropertyChanged
{
···
public DecimalKeypadViewModel()
{
ClearCommand = new Command(
execute: () =>
{
Entry = "0";
RefreshCanExecutes();
});
···
}
void RefreshCanExecutes()
{
((Command)BackspaceCommand).ChangeCanExecute();
((Command)DigitCommand).ChangeCanExecute();
}
···
}
Karena tombol selalu diaktifkan, tidak perlu menentukan canExecute
argumen di Command
konstruktor.
Tombol Backspace diaktifkan hanya ketika panjang entri lebih besar dari 1, atau jika Entry tidak sama dengan string "0":
public class DecimalKeypadViewModel : INotifyPropertyChanged
{
···
public DecimalKeypadViewModel()
{
···
BackspaceCommand = new Command(
execute: () =>
{
Entry = Entry.Substring(0, Entry.Length - 1);
if (Entry == "")
{
Entry = "0";
}
RefreshCanExecutes();
},
canExecute: () =>
{
return Entry.Length > 1 || Entry != "0";
});
···
}
···
}
Logika untuk execute
fungsi untuk tombol Backspace memastikan bahwa Entry setidaknya adalah string "0".
Properti DigitCommand
terikat ke 11 tombol, yang masing-masing mengidentifikasi dirinya dengan CommandParameter
properti . DigitCommand
diatur ke instans Command<T>
kelas. Saat menggunakan antarmuka perintah dengan XAML, CommandParameter
properti biasanya berupa string, yang merupakan jenis argumen generik. Fungsi execute
dan canExecute
kemudian memiliki argumen jenis string
:
public class DecimalKeypadViewModel : INotifyPropertyChanged
{
···
public DecimalKeypadViewModel()
{
···
DigitCommand = new Command<string>(
execute: (string arg) =>
{
Entry += arg;
if (Entry.StartsWith("0") && !Entry.StartsWith("0."))
{
Entry = Entry.Substring(1);
}
RefreshCanExecutes();
},
canExecute: (string arg) =>
{
return !(arg == "." && Entry.Contains("."));
});
}
···
}
Metode menambahkan execute
argumen string ke Entry properti . Namun, jika hasilnya dimulai dengan nol (tetapi bukan nol dan titik desimal) maka nol awal tersebut harus dihapus menggunakan Substring
fungsi . Metode canExecute
ini hanya mengembalikan false
jika argumen adalah titik desimal (menunjukkan bahwa titik desimal sedang ditekan) dan Entry sudah berisi titik desimal. execute
Semua metode memanggil RefreshCanExecutes
, yang kemudian memanggil ChangeCanExecute
untuk dan DigitCommand
ClearCommand
. Ini memastikan bahwa tombol titik desimal dan backspace diaktifkan atau dinonaktifkan berdasarkan urutan digit yang dimasukkan saat ini.
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk