Udostępnij za pośrednictwem


Interfejs Xamarin.Forms polecenia

W architekturze Model-View-ViewModel (MVVM) powiązania danych są definiowane między właściwościami w modelu ViewModel, która jest zazwyczaj klasą pochodzącą z INotifyPropertyChangedklasy i właściwości w widoku, która jest zazwyczaj plikiem XAML. Czasami aplikacja musi wykraczać poza te powiązania właściwości, wymagając od użytkownika zainicjowania poleceń, które mają wpływ na coś w modelu ViewModel. Te polecenia są zwykle sygnalizowane przez kliknięcia przycisków lub naciśnięcia palcem i tradycyjnie są przetwarzane w pliku za pomocą kodu w programie obsługi dla Clicked zdarzenia Button lub Tapped zdarzenia TapGestureRecognizer.

Interfejs wiersza polecenia zapewnia alternatywne podejście do implementowania poleceń, które są znacznie lepiej dostosowane do architektury MVVM. Sam model ViewModel może zawierać polecenia, które są metodami wykonywanymi w reakcji na określone działanie w widoku, na przykład Button kliknięcie. Powiązania danych są definiowane między tymi poleceniami a elementem Button.

Aby zezwolić na powiązanie danych między elementem a a Button modelem ViewModel, Button definiuje dwie właściwości:

Aby użyć interfejsu poleceń, należy zdefiniować powiązanie danych, które jest obiektem docelowym Command właściwości, w której źródło jest właściwością Button w modelu ViewModel typu ICommand. Model ViewModel zawiera kod skojarzony z tą ICommand właściwością, która jest wykonywana po kliknięciu przycisku. Możesz ustawić dowolne CommandParameter dane, aby odróżnić wiele przycisków, jeśli wszystkie są powiązane z tą samą ICommand właściwością w modelu ViewModel.

Właściwości Command i CommandParameter są również definiowane przez następujące klasy:

SearchBarSearchCommand definiuje właściwość typu ICommand i SearchCommandParameter właściwości. Właściwość RefreshCommand właściwości ListView jest również typu ICommand.

Wszystkie te polecenia można obsługiwać w modelu ViewModel w sposób, który nie zależy od określonego obiektu interfejsu użytkownika w widoku.

Interfejs ICommand

Interfejs System.Windows.Input.ICommand nie jest częścią elementu Xamarin.Forms. Jest on zdefiniowany w przestrzeni nazw System.Windows.Input i składa się z dwóch metod i jednego zdarzenia:

public interface ICommand
{
    public void Execute (Object parameter);

    public bool CanExecute (Object parameter);

    public event EventHandler CanExecuteChanged;
}

Aby użyć interfejsu polecenia, model ViewModel zawiera właściwości typu ICommand:

public ICommand MyCommand { private set; get; }

Model ViewModel musi również odwoływać się do klasy, która implementuje ICommand interfejs. Ta klasa zostanie wkrótce opisana. W widoku Command właściwość obiektu Button jest powiązana z tym właściwością:

<Button Text="Execute command"
        Command="{Binding MyCommand}" />

Gdy użytkownik naciśnie metodę Button, Button wywołuje metodę Execute w ICommand obiekcie powiązanym z jego Command właściwością. Jest to najprostsza część interfejsu wiersza polecenia.

Metoda CanExecute jest bardziej złożona. Gdy powiązanie jest najpierw zdefiniowane we Command właściwości Button, a powiązanie danych zmienia się w jakiś sposób, Button metoda CanExecute wywołuje metodę ICommand w obiekcie . Jeśli CanExecute funkcja zwróci wartość false, funkcja zostanie wyłączona Button . Oznacza to, że określone polecenie jest obecnie niedostępne lub nieprawidłowe.

Element Button dołącza również program obsługi w CanExecuteChanged przypadku zdarzenia ICommand. Zdarzenie jest wyzwalane z poziomu modelu ViewModel. Po wyzwoleniu tego zdarzenia wywołania ButtonCanExecute ponownie. Funkcja Button włącza się w przypadku CanExecute zwracania true i wyłącza się, jeśli CanExecute zwraca wartość false.

Ważne

Nie używaj IsEnabled właściwości , Button jeśli używasz interfejsu poleceń.

Klasa poleceń

Gdy model ViewModel definiuje właściwość typu ICommand, model ViewModel musi również zawierać lub odwoływać się do klasy, która implementuje ICommand interfejs. Ta klasa musi zawierać lub odwoływać się do Execute metod i CanExecute oraz uruchamiać CanExecuteChanged zdarzenie za każdym razem, gdy CanExecute metoda może zwrócić inną wartość.

Możesz napisać taką klasę samodzielnie lub użyć klasy, którą napisał ktoś inny. Ponieważ ICommand jest częścią systemu Microsoft Windows, jest używany od lat w aplikacjach MVVM systemu Windows. Użycie klasy systemu Windows, która implementuje ICommand , umożliwia udostępnianie modelu ViewModels między aplikacjami i Xamarin.Forms aplikacjami systemu Windows.

Jeśli udostępnianie modelu ViewModels między systemem Windows i Xamarin.Forms nie jest problemem, możesz użyć klasy lub Command<T> dołączonej CommandXamarin.Forms do implementacji interfejsuICommand. Te klasy umożliwiają określenie treści Execute metod i CanExecute w konstruktorach klas. Użyj Command<T> właściwości , aby CommandParameter odróżnić wiele widoków powiązanych z tą samą ICommand właściwością i prostszą Command klasę, gdy nie jest to wymagane.

Podstawowe polecenia

Strona Wpis osoby w przykładowym programie pokazuje kilka prostych poleceń zaimplementowanych w modelu ViewModel.

Definiuje PersonViewModel trzy właściwości o nazwie Name, Agei Skills definiujące osobę. Ta klasa nie zawiera żadnych ICommand właściwości:

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

Pokazany PersonCollectionViewModel poniżej tworzy nowe obiekty typu PersonViewModel i umożliwia użytkownikowi wypełnianie danych. W tym celu klasa definiuje właściwości IsEditing typu bool i PersonEdit typu PersonViewModel. Ponadto klasa definiuje trzy właściwości typu ICommand i właściwość o nazwie Persons typu 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));
    }
}

Ta skrócona lista nie zawiera konstruktora klasy, który jest miejscem, w którym zdefiniowano trzy właściwości typu ICommand , które zostaną wyświetlone wkrótce. Zwróć uwagę, że zmiany w trzech właściwościach typu ICommand i Persons właściwość nie powodują PropertyChanged wyzwolenia zdarzeń. Wszystkie te właściwości są ustawiane podczas pierwszego tworzenia klasy i nie zmieniają się później.

Przed zbadaniem konstruktora PersonCollectionViewModel klasy przyjrzyjmy się plikowi XAML programu Person Entry . Zawiera obiekt Grid z właściwością BindingContext ustawioną na PersonCollectionViewModelwartość . Zawiera Grid element Button z tekstem New z właściwością Command powiązaną NewCommand z właściwością w modelu ViewModel, formularzem wpisu z właściwościami powiązanymi IsEditing z właściwością, a także właściwościami PersonViewModelelementu i dwoma przyciskami powiązanymi SubmitCommand z właściwościami i CancelCommand modelu ViewModel. W finale ListView zostanie wyświetlona kolekcja osób, które zostały już wprowadzone:

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

Oto jak to działa: użytkownik najpierw naciska przycisk Nowy . Spowoduje to włączenie formularza wpisu, ale spowoduje wyłączenie przycisku Nowy . Następnie użytkownik wprowadza nazwę, wiek i umiejętności. W dowolnym momencie edytowania użytkownik może nacisnąć przycisk Anuluj , aby rozpocząć od nowa. Tylko wtedy, gdy wprowadzono nazwę i prawidłowy wiek, jest włączony przycisk Prześlij . Naciśnięcie tego przycisku Prześlij spowoduje przeniesienie osoby do kolekcji wyświetlanej ListViewprzez element . Po naciśnięciu przycisku Anuluj lub Prześlij formularz wpisu zostanie wyczyszczone, a przycisk Nowy zostanie ponownie włączony.

Ekran systemu iOS po lewej stronie pokazuje układ przed wejściem prawidłowego wieku. Na ekranie systemu Android jest wyświetlany przycisk Prześlij włączony po ustawieniu wieku:

Wpis osoby

Program nie ma żadnego obiektu do edytowania istniejących wpisów i nie zapisuje wpisów podczas przechodzenia z dala od strony.

Cała logika przycisków Nowy, Prześlij i Anuluj jest obsługiwana PersonCollectionViewModel za pomocą definicji NewCommandwłaściwości , SubmitCommandi CancelCommand . Konstruktor zestawu PersonCollectionViewModel tych trzech właściwości do obiektów typu Command.

Konstruktor Command klasy umożliwia przekazywanie argumentów typu Action i Func<bool> odpowiadających metodom Execute i CanExecute . Najłatwiej jest zdefiniować te akcje i funkcje jako funkcje lambda bezpośrednio w konstruktorze Command . Oto definicja Command obiektu dla NewCommand właściwości:

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

    ···

}

Gdy użytkownik kliknie przycisk Nowy , execute funkcja przekazana do konstruktora Command zostanie wykonana. Spowoduje to utworzenie nowego PersonViewModel obiektu, ustawienie procedury obsługi dla zdarzenia tego obiektu PropertyChanged , ustawienie na IsEditingtrue, i wywołanie RefreshCanExecutes metody zdefiniowanej po konstruktorze.

Oprócz implementowania interfejsu ICommandCommand klasa definiuje również metodę o nazwie ChangeCanExecute. Model ViewModel powinien wywoływać ChangeCanExecuteICommand właściwość za każdym razem, gdy coś się stanie, co może zmienić wartość CanExecute zwracaną metody. Wywołanie metody powoduje, że ChangeCanExecute klasa uruchamia metodę CanExecuteChanged .Command Element Button zawiera procedurę obsługi dla tego zdarzenia i odpowiada, wywołując CanExecute ponownie, a następnie włączając się na podstawie zwracanej wartości tej metody.

execute Gdy metoda wywołania NewCommandRefreshCanExecutesmetody , NewCommand właściwość pobiera wywołanie metody ChangeCanExecutei Button wywołuje canExecute metodę , która zwraca false teraz, ponieważ IsEditing właściwość ma teraz truewartość .

Procedura PropertyChanged obsługi nowego PersonViewModel obiektu wywołuje metodę ChangeCanExecuteSubmitCommand. Oto jak ta właściwość polecenia jest implementowana:

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

        ···
    }

    ···

}

Funkcja canExecute dla SubmitCommand elementu jest wywoływana za każdym razem, gdy w edytowanym obiekcie jest zmieniana PersonViewModel właściwość. Zwraca true tylko wtedy, Age gdy Name właściwość ma co najmniej jeden znak i jest większa niż 0. W tym czasie przycisk Prześlij zostanie włączony.

Funkcja executeSubmit usuwa program obsługi zmienionej właściwości z PersonViewModelobiektu , dodaje obiekt do Persons kolekcji i zwraca wszystko do warunków początkowych.

Funkcja executeprzycisku Anuluj wykonuje wszystko, co przycisk Prześlij , z wyjątkiem dodania obiektu do kolekcji:

public class PersonCollectionViewModel : INotifyPropertyChanged
{

    ···

    public PersonCollectionViewModel()
    {

        ···

        CancelCommand = new Command(
            execute: () =>
            {
                PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
                PersonEdit = null;
                IsEditing = false;
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return IsEditing;
            });
    }

    ···

}

Metoda canExecute zwraca true wartość w dowolnym momencie PersonViewModel , gdy element jest edytowany.

Te techniki można dostosować do bardziej złożonych scenariuszy: właściwość in PersonCollectionViewModel może być powiązana SelectedItem z właściwością ListView elementu do edycji istniejących elementów, a przycisk Usuń można dodać, aby usunąć te elementy.

Nie jest konieczne zdefiniowanie execute metod i canExecute jako funkcji lambda. Można je zapisywać jako zwykłe metody prywatne w modelu ViewModel i odwoływać się do nich w Command konstruktorach. Jednak takie podejście zwykle prowadzi do wielu metod, do których odwołuje się tylko raz w modelu ViewModel.

Używanie parametrów polecenia

Czasami jest to wygodne dla jednego lub kilku przycisków (lub innych obiektów interfejsu użytkownika) do współużytkowania tej samej ICommand właściwości w modelu ViewModel. W tym przypadku należy użyć CommandParameter właściwości , aby odróżnić przyciski.

Możesz nadal używać Command klasy dla tych właściwości udostępnionych ICommand . Klasa definiuje alternatywny konstruktor , który akceptuje execute metody i canExecute z parametrami typu Object. W ten sposób CommandParameter metoda jest przekazywana do tych metod.

Jednak w przypadku korzystania z CommandParameterklasy , najłatwiej jest użyć klasy ogólnej Command<T> do określenia typu obiektu ustawionego na CommandParameterwartość . Metody execute i canExecute , które określisz, mają parametry tego typu.

Strona Klawiatura dziesiętna ilustruje tę technikę, pokazując, jak zaimplementować klawiaturę do wprowadzania liczb dziesiętnych. Element BindingContext dla elementu Grid to .DecimalKeypadViewModel Właściwość Entry tego modelu ViewModel jest powiązana z Text właściwością Label. Button Wszystkie obiekty są powiązane z różnymi poleceniami w modelu ViewModel: ClearCommand, BackspaceCommandi 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 przycisków dla 10 cyfr i punkt dziesiętny współużytkuje powiązanie z DigitCommand. Rozróżniane CommandParameter są te przyciski. Wartość ustawiona na CommandParameter wartość jest zazwyczaj taka sama jak tekst wyświetlany przez przycisk z wyjątkiem przecinka dziesiętnego, który dla celów jasności jest wyświetlany z środkowym znakiem kropki.

Oto program w działaniu:

Klawiatura dziesiętna

Zwróć uwagę, że przycisk dla punktu dziesiętnego we wszystkich trzech zrzutach ekranu jest wyłączony, ponieważ wprowadzona liczba zawiera już punkt dziesiętny.

Definiuje DecimalKeypadViewModel właściwość typu string (która jest jedyną Entry właściwością, która wyzwala PropertyChanged zdarzenie) i trzy właściwości typu 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; }
}

Przycisk odpowiadający elementowi ClearCommand jest zawsze włączony i po prostu ustawia wpis z powrotem na "0":

public class DecimalKeypadViewModel : INotifyPropertyChanged
{

    ···

    public DecimalKeypadViewModel()
    {
        ClearCommand = new Command(
            execute: () =>
            {
                Entry = "0";
                RefreshCanExecutes();
            });

        ···

    }

    void RefreshCanExecutes()
    {
        ((Command)BackspaceCommand).ChangeCanExecute();
        ((Command)DigitCommand).ChangeCanExecute();
    }

    ···

}

Ponieważ przycisk jest zawsze włączony, nie jest konieczne określenie canExecute argumentu w konstruktorze Command .

Logika wprowadzania liczb i wycofywania jest nieco trudna, ponieważ jeśli nie wprowadzono cyfr, Entry właściwość jest ciągiem "0". Jeśli użytkownik wpisze więcej zer, Entry nadal zawiera tylko jedno zero. Jeśli użytkownik wpisze dowolną inną cyfrę, ta cyfra zastępuje zero. Jeśli jednak użytkownik wpisze punkt dziesiętny przed inną cyfrą, Entry to ciąg "0".

Przycisk Backspace jest włączony tylko wtedy, gdy długość wpisu jest większa niż 1 lub jeśli Entry nie jest równa ciągu "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 execute funkcji przycisku Backspace gwarantuje, że Entry element jest co najmniej ciągiem "0".

Właściwość DigitCommand jest powiązana z 11 przyciskami, z których każda identyfikuje się z właściwością CommandParameter . Element DigitCommand może być ustawiony na wystąpienie klasy regularnej Command , ale łatwiej jest użyć Command<T> klasy ogólnej. W przypadku używania interfejsu poleceń z językiem XAML CommandParameter właściwości są zwykle ciągami i jest to typ argumentu ogólnego. Funkcje execute i canExecute mają argumenty typu 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("."));
            });
    }

    ···

}

Metoda execute dołącza argument ciągu do Entry właściwości . Jeśli jednak wynik zaczyna się od zera (ale nie zera i punktu dziesiętnego), to początkowe zero musi zostać usunięte przy użyciu Substring funkcji .

canExecute Metoda zwraca tylko false wtedy, gdy argument jest punktem dziesiętnym (wskazującym, że punkt dziesiętny jest naciskany) i Entry zawiera już punkt dziesiętny.

Wszystkie metody wywołają metodę executeRefreshCanExecutes, która następnie wywołuje metodę ChangeCanExecute zarówno , jak DigitCommand i ClearCommand. Dzięki temu przyciski punktów dziesiętnych i backspace są włączone lub wyłączone w oparciu o bieżącą sekwencję wprowadzonych cyfr.

Asynchroniczne polecenie dla menu nawigacji

Polecenie jest wygodne do implementowania menu nawigacji. Oto część pliku 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>

W przypadku używania poleceń z językiem XAML CommandParameter właściwości są zwykle ustawiane na ciągi. W takim przypadku jest jednak używane rozszerzenie znaczników XAML, aby CommandParameter typ System.Type.

Każda Command właściwość jest powiązana z właściwością o nazwie NavigateCommand. Ta właściwość jest definiowana w pliku za pomocą kodu, 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 ustawia NavigateCommand właściwość na metodę execute , która tworzy wystąpienie parametru System.Type , a następnie przechodzi do niego. Ponieważ wywołanie PushAsync wymaga await operatora, execute metoda musi być oflagowana jako asynchroniczna. Jest to realizowane za pomocą słowa kluczowego async przed listą parametrów.

Konstruktor ustawia również wartość BindingContext strony na samą siebie, tak aby powiązania odwoły się do NavigateCommand klasy w tej klasie.

Kolejność kodu w tym konstruktorze ma różnicę: InitializeComponent Wywołanie powoduje przeanalizowanie kodu XAML, ale w tym czasie powiązanie z właściwością o nazwie NavigateCommand nie może zostać rozpoznane, ponieważ BindingContext jest ustawione na nullwartość . BindingContext Jeśli parametr jest ustawiony w konstruktorze przedNavigateCommand ustawieniem, powiązanie można rozwiązać, gdy BindingContext jest ustawione, ale w tym czasie NavigateCommand jest nadal null. Ustawienie NavigateCommand po BindingContext nie będzie miało wpływu na powiązanie, ponieważ zmiana NavigateCommand nie powoduje wyzwolenia PropertyChanged zdarzenia, a powiązanie nie wie, że NavigateCommand jest teraz prawidłowe.

Ustawienie zarówno , NavigateCommand jak i BindingContext (w dowolnej kolejności) przed wywołaniem InitializeComponent metody będzie działać, ponieważ oba składniki powiązania są ustawiane, gdy analizator XAML napotka definicję powiązania.

Powiązania danych mogą czasami być trudne, ale jak pokazano w tej serii artykułów, są one zaawansowane i uniwersalne i pomagają znacznie zorganizować kod, oddzielając podstawową logikę od interfejsu użytkownika.