Bagikan melalui


Xamarin.Forms Antarmuka Perintah

Dalam arsitektur Model-View-ViewModel (MVVM), pengikatan data ditentukan antara properti di ViewModel, yang umumnya merupakan kelas yang berasal dari INotifyPropertyChanged, dan properti dalam Tampilan, yang umumnya 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 itu sendiri 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 di ViewModel jenis ICommand. ViewModel berisi kode yang ICommand terkait dengan properti yang dijalankan saat tombol diklik. Anda dapat mengatur CommandParameter ke data arbitrer untuk membedakan antara beberapa tombol jika semuanya terikat ke properti yang sama ICommand di ViewModel.

Properti Command dan CommandParameter juga didefinisikan oleh kelas-kelas berikut:

SearchBarSearchCommand menentukan properti jenis ICommand dan SearchCommandParameter properti. Properti RefreshCommand juga ListView berjenis ICommand.

Semua perintah ini dapat ditangani dalam ViewModel dengan cara yang tidak bergantung pada objek antarmuka pengguna tertentu dalam Tampilan.

Antarmuka ICommand

Antarmuka System.Windows.Input.ICommand bukan bagian Xamarin.Formsdari . Ini didefinisikan sebagai gantinya di namespace 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 berisi properti jenis ICommand:

public ICommand MyCommand { private set; get; }

ViewModel juga harus mereferensikan kelas yang mengimplementasikan ICommand antarmuka. Kelas ini akan segera dijelaskan. Di 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. Itulah bagian paling sederhana dari antarmuka perintah.

Metode CanExecute ini lebih kompleks. 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 ini diaktifkan dari dalam ViewModel. Ketika peristiwa itu dipicu, Button panggilan CanExecute lagi. mengaktifkan Button dirinya sendiri jika CanExecute mengembalikan true dan menonaktifkan dirinya sendiri jika CanExecute mengembalikan false.

Penting

Jangan gunakan IsEnabled properti Button jika Anda menggunakan antarmuka perintah.

Kelas 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 menulis kelas seperti itu sendiri, atau Anda dapat menggunakan kelas yang ditulis orang lain. Karena ICommand merupakan bagian dari Microsoft Windows, ini telah digunakan selama bertahun-tahun dengan aplikasi Windows MVVM. Menggunakan kelas Windows yang mengimplementasikan ICommand memungkinkan Anda berbagi ViewModel antara aplikasi dan Xamarin.Forms aplikasi Windows.

Jika berbagi ViewModels antara Windows dan Xamarin.Forms bukan masalah, maka Anda dapat menggunakan kelas atau Command<T> yang Command disertakan untuk Xamarin.Forms mengimplementasikan ICommand antarmuka. Kelas-kelas ini memungkinkan Anda menentukan isi Execute metode dan CanExecute di konstruktor kelas. Gunakan Command<T> saat Anda menggunakan CommandParameter properti untuk membedakan antara beberapa tampilan yang terikat ke properti yang sama ICommand , dan kelas yang lebih Command sederhana ketika itu bukan persyaratan.

Perintah Dasar

Halaman Entri Orang dalam program sampel menunjukkan beberapa perintah sederhana yang diterapkan dalam ViewModel.

PersonViewModel menentukan tiga properti bernama Name, Age, dan Skills yang menentukan seseorang. Kelas ini tidak berisi properti apa punICommand:

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

Yang PersonCollectionViewModel ditunjukkan di bawah ini membuat objek baru jenis PersonViewModel dan memungkinkan pengguna untuk mengisi data. Untuk tujuan itu, kelas mendefinisikan properti IsEditing jenis bool dan PersonEdit jenis PersonViewModel. 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));
    }
}

Daftar singkatan ini tidak termasuk konstruktor kelas, yang merupakan tempat tiga properti jenis ICommand didefinisikan, yang akan segera ditampilkan. Perhatikan bahwa perubahan pada tiga properti jenis ICommand dan Persons properti tidak mengakibatkan peristiwa diaktifkan PropertyChanged . Properti ini semua diatur ketika kelas pertama kali dibuat dan tidak berubah setelahnya.

Sebelum memeriksa konstruktor PersonCollectionViewModel kelas , mari kita lihat file XAML untuk program Entri Orang. Ini berisi dengan propertinya BindingContext yang Grid 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. ListView Final menampilkan kumpulan orang yang sudah dimasukkan:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.PersonEntryPage"
             Title="Person Entry">
    <Grid Margin="10">
        <Grid.BindingContext>
            <local:PersonCollectionViewModel />
        </Grid.BindingContext>

        <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="CenterAndExpand" />

            <Button Text="Cancel"
                    Grid.Column="1"
                    Command="{Binding CancelCommand}"
                    VerticalOptions="CenterAndExpand" />
        </Grid>

        <!-- List of Persons -->
        <ListView Grid.Row="3"
                  ItemsSource="{Binding Persons}" />
    </Grid>
</ContentPage>

Berikut cara kerjanya: Pengguna terlebih dahulu menekan tombol Baru . Ini memungkinkan 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.

Layar iOS di sebelah kiri menampilkan tata letak sebelum usia yang valid dimasukkan. Layar Android menampilkan tombol Kirim diaktifkan setelah usia ditetapkan:

Entri Orang

Program ini tidak memiliki fasilitas untuk mengedit entri yang ada, dan tidak menyimpan entri ketika Anda menavigasi jauh dari halaman.

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 . Paling mudah untuk menentukan tindakan dan fungsi ini sebagai fungsi lambda tepat di Command konstruktor. Berikut adalah definisi Command objek untuk NewCommand properti :

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 Anda 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. Berikut adalah cara properti perintah tersebut diimplementasikan:

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.

Fungsi execute untuk Kirim menghapus handler yang diubah properti dari PersonViewModel, menambahkan objek ke Persons koleksi, dan mengembalikan semuanya ke kondisi awal.

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.

Teknik ini dapat disesuaikan dengan skenario yang lebih kompleks: Properti di PersonCollectionViewModel dapat terikat ke SelectedItem properti ListView untuk mengedit item yang ada, dan tombol Hapus dapat ditambahkan untuk menghapus item tersebut.

Tidak perlu mendefinisikan execute metode dan canExecute sebagai fungsi lambda. Anda dapat menulisnya sebagai metode privat reguler di ViewModel dan mereferensikannya di Command konstruktor. Namun, pendekatan ini cenderung menghasilkan banyak metode yang hanya direferensikan 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 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 menggunakan CommandParameter, paling mudah menggunakan kelas generik Command<T> untuk menentukan jenis objek yang diatur ke CommandParameter. Metode dan canExecute yang Anda tentukan memiliki parameter jenis tersebutexecute.

Halaman Keyboard Desimal mengilustrasikan teknik ini dengan menunjukkan cara mengimplementasikan keypad untuk memasukkan angka desimal. untuk BindingContextGrid adalah .DecimalKeypadViewModel Properti Entry ViewModel ini terikat ke Text properti dari Label. Button Semua objek terikat ke berbagai perintah di ViewModel: ClearCommand, , BackspaceCommanddan DigitCommand:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.DecimalKeypadPage"
             Title="Decimal Keyboard">

    <Grid WidthRequest="240"
          HeightRequest="480"
          ColumnSpacing="2"
          RowSpacing="2"
          HorizontalOptions="Center"
          VerticalOptions="Center">

        <Grid.BindingContext>
            <local:DecimalKeypadViewModel />
        </Grid.BindingContext>

        <Grid.Resources>
            <ResourceDictionary>
                <Style TargetType="Button">
                    <Setter Property="FontSize" Value="32" />
                    <Setter Property="BorderWidth" Value="1" />
                    <Setter Property="BorderColor" Value="Black" />
                </Style>
            </ResourceDictionary>
        </Grid.Resources>

        <Label Text="{Binding Entry}"
               Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
               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>

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.

Berikut adalah program yang sedang berjalan:

Papan Tombol Desimal

Perhatikan bahwa tombol untuk titik desimal di ketiga cuplikan layar dinonaktifkan karena angka yang dimasukkan sudah berisi titik desimal.

DecimalKeypadViewModel menentukan Entry properti jenis string (yang merupakan satu-satunya properti yang memicu PropertyChanged peristiwa) 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 hanya 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.

Logika untuk memasukkan angka dan backspacing sedikit rumit karena jika tidak ada digit yang dimasukkan, maka Entry properti adalah string "0". Jika pengguna mengetikan lebih banyak nol, maka masih Entry berisi hanya satu nol. Jika pengguna mengetik digit lain, digit tersebut menggantikan nol. Tetapi jika pengguna mengetikan titik desimal sebelum digit lain, maka Entry adalah string "0.".

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 dapat diatur ke instans kelas regulerCommand, tetapi lebih mudah untuk menggunakan Command<T> kelas generik. Saat menggunakan antarmuka perintah dengan XAML, CommandParameter properti biasanya berupa string, dan itulah 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.

Perintah Asinkron untuk Menu Navigasi

Perintah nyaman untuk menerapkan menu navigasi. Berikut adalah bagian dari MainPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.MainPage"
             Title="Data Binding Demos"
             Padding="10">
    <TableView Intent="Menu">
        <TableRoot>
            <TableSection Title="Basic Bindings">

                <TextCell Text="Basic Code Binding"
                          Detail="Define a data-binding in code"
                          Command="{Binding NavigateCommand}"
                          CommandParameter="{x:Type local:BasicCodeBindingPage}" />

                <TextCell Text="Basic XAML Binding"
                          Detail="Define a data-binding in XAML"
                          Command="{Binding NavigateCommand}"
                          CommandParameter="{x:Type local:BasicXamlBindingPage}" />

                <TextCell Text="Alternative Code Binding"
                          Detail="Define a data-binding in code without a BindingContext"
                          Command="{Binding NavigateCommand}"
                          CommandParameter="{x:Type local:AlternativeCodeBindingPage}" />

                ···

            </TableSection>
        </TableRoot>
    </TableView>
</ContentPage>

Saat menggunakan perintah dengan XAML, CommandParameter properti biasanya diatur ke string. Namun, dalam hal ini, ekstensi markup XAML digunakan sehingga CommandParameter berjenis System.Type.

Setiap Command properti terikat ke properti bernama NavigateCommand. Properti tersebut didefinisikan dalam file code-behind, MainPage.xaml.cs:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        NavigateCommand = new Command<Type>(
            async (Type pageType) =>
            {
                Page page = (Page)Activator.CreateInstance(pageType);
                await Navigation.PushAsync(page);
            });

        BindingContext = this;
    }

    public ICommand NavigateCommand { private set; get; }
}

Konstruktor mengatur NavigateCommand properti ke execute metode yang membuat instans System.Type parameter dan kemudian menavigasi ke dalamnya. PushAsync Karena panggilan memerlukan await operator, execute metode harus ditandai sebagai asinkron. Ini dicapai dengan async kata kunci sebelum daftar parameter.

Konstruktor juga mengatur BindingContext halaman ke dirinya sendiri sehingga pengikatan mereferensikan NavigateCommand di kelas ini.

Urutan kode dalam konstruktor ini membuat perbedaan: InitializeComponent Panggilan menyebabkan XAML diurai, tetapi pada saat itu pengikatan ke properti bernama NavigateCommand tidak dapat diselesaikan karena BindingContext diatur ke null. BindingContext Jika diatur dalam konstruktor sebelumNavigateCommand diatur, maka pengikatan dapat diselesaikan ketika BindingContext diatur, tetapi pada saat itu, NavigateCommand masih null. Pengaturan NavigateCommand setelah BindingContext tidak akan berpengaruh pada pengikatan karena perubahan untuk NavigateCommand tidak mengaktifkan PropertyChanged peristiwa, dan pengikatan tidak tahu bahwa NavigateCommand sekarang valid.

Mengatur dan NavigateCommandBindingContext (dalam urutan apa pun) sebelum panggilan ke InitializeComponent akan berfungsi karena kedua komponen pengikatan diatur ketika pengurai XAML menemukan definisi pengikatan.

Pengikatan data terkadang bisa rumit, tetapi seperti yang telah Anda lihat dalam rangkaian artikel ini, mereka kuat dan serbaguna, dan sangat membantu mengatur kode Anda dengan memisahkan logika yang mendasar dari antarmuka pengguna.