Bagikan melalui


Komandan

Browse sample. Telusuri sampel

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:

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

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:

Person Entry.

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 NewCommandproperti , , SubmitCommanddan 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 ChangeCanExecuteICommand 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 canExecutetrue 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="&#x21E6;"
                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="&#x00B7;"
                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, , BackspaceCommanddan 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:

Decimal keyboard.

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 DigitCommandClearCommand. Ini memastikan bahwa tombol titik desimal dan backspace diaktifkan atau dinonaktifkan berdasarkan urutan digit yang dimasukkan saat ini.