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 INotifyPropertyChanged
klasy 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:
Command
typuSystem.Windows.Input.ICommand
CommandParameter
typuObject
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:
MenuItem
i dlatego,ToolbarItem
, który pochodzi zMenuItem
TextCell
i dlatego,ImageCell
, który pochodzi zTextCell
TapGestureRecognizer
SearchBar
SearchCommand
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 Button
CanExecute
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 Command
Xamarin.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
, Age
i 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 PersonCollectionViewModel
wartość . 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 PersonViewModel
elementu 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 ListView
przez 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:
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 NewCommand
właściwości , SubmitCommand
i 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 IsEditing
true
, i wywołanie RefreshCanExecutes
metody zdefiniowanej po konstruktorze.
Oprócz implementowania interfejsu ICommand
Command
klasa definiuje również metodę o nazwie ChangeCanExecute
. Model ViewModel powinien wywoływać ChangeCanExecute
ICommand
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 NewCommand
RefreshCanExecutes
metody , NewCommand
właściwość pobiera wywołanie metody ChangeCanExecute
i Button
wywołuje canExecute
metodę , która zwraca false
teraz, ponieważ IsEditing
właściwość ma teraz true
wartość .
Procedura PropertyChanged
obsługi nowego PersonViewModel
obiektu wywołuje metodę ChangeCanExecute
SubmitCommand
. 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 execute
Submit usuwa program obsługi zmienionej właściwości z PersonViewModel
obiektu , dodaje obiekt do Persons
kolekcji i zwraca wszystko do warunków początkowych.
Funkcja execute
przycisku 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 CommandParameter
klasy , najłatwiej jest użyć klasy ogólnej Command<T>
do określenia typu obiektu ustawionego na CommandParameter
wartość . 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
, BackspaceCommand
i 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="⇦"
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>
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:
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ę execute
RefreshCanExecutes
, 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 null
wartość . BindingContext
Jeśli parametr jest ustawiony w konstruktorze przed NavigateCommand
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.