Komendanta
W aplikacji interfejsu użytkownika aplikacji wieloplatformowej platformy .NET (.NET MAUI), która używa wzorca Model-View-ViewModel (MVVM), powiązania danych są definiowane między właściwościami w modelu widoków, 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 widoków. 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. Model widoku może zawierać polecenia, które są metodami wykonywanymi w reakcji na określone działanie w widoku, takim jak Button kliknięcie. Powiązania danych są definiowane między tymi poleceniami a elementem Button.
Aby zezwolić na powiązanie danych między modelem a Button modelem widoków, Button definiuje dwie właściwości:
Command
typuSystem.Windows.Input.ICommand
CommandParameter
typuObject
Aby użyć interfejsu polecenia, należy zdefiniować powiązanie danych, które jest przeznaczone Command
dla właściwości, w której źródło jest właściwością Button w modelu widoków typu ICommand. Model viewmodel zawiera kod skojarzony z tą ICommand właściwością, która jest wykonywana po kliknięciu przycisku. Właściwość można ustawić CommandParameter
na dowolne dane, aby odróżnić wiele przycisków, jeśli wszystkie są powiązane z tą samą ICommand właściwością w modelu widoków.
Wiele innych widoków definiuje Command
również właściwości i CommandParameter
. Wszystkie te polecenia można obsłużyć w modelu widoków przy użyciu podejścia, które nie zależy od obiektu interfejsu użytkownika w widoku.
Polecenia
Interfejs ICommand jest 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 poleceń, model viewmodel powinien zawierać właściwości typu ICommand:
public ICommand MyCommand { private set; get; }
Model widoku musi również odwoływać się do klasy, która implementuje ICommand interfejs. 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ą.
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 wywoływane z poziomu modelu widoków. Po wywołaniu 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
.
Ostrzeżenie
Nie używaj IsEnabled
właściwości , Button jeśli używasz interfejsu poleceń.
Gdy model viewmodel definiuje właściwość typu ICommand, model widoku 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ść. Do zaimplementowania interfejsu można użyć klasy lub Command<T>
dołączonej Command
do programu ICommand .NET MAUI. Te klasy umożliwiają określenie treści Execute
metod i CanExecute
w konstruktorach klas.
Napiwek
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 Command
klasę, jeśli nie jest to wymagane.
Podstawowe polecenia
W poniższych przykładach przedstawiono podstawowe polecenia zaimplementowane w modelu viewmodel.
Klasa PersonViewModel
definiuje trzy właściwości o nazwie Name
, Age
i Skills
definiujące osobę:
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));
}
}
Klasa przedstawiona PersonCollectionViewModel
poniżej tworzy nowe obiekty typu PersonViewModel
i umożliwia użytkownikowi wypełnianie danych. W tym celu klasa definiuje IsEditing
, typu i PersonEdit
, typu bool
PersonViewModel
, właściwości. 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));
}
}
W tym przykładzie zmiany trzech ICommand właściwości i Persons
właściwość nie powodują PropertyChanged
zgłoszenia zdarzeń. Te właściwości są ustawiane podczas pierwszego tworzenia klasy i nie zmieniają się.
W poniższym przykładzie pokazano kod XAML, który korzysta z elementu 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>
W tym przykładzie właściwość strony BindingContext
jest ustawiona na PersonCollectionViewModel
wartość . Zawiera Grid element Button z tekstem Nowy 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 z SubmitCommand
właściwościami i CancelCommand
modelu viewmodel. Spowoduje to ListView wyświetlenie kolekcji osób, które zostały już wprowadzone:
Poniższy zrzut ekranu przedstawia przycisk Prześlij włączony po ustawieniu wieku:
Gdy użytkownik po raz pierwszy naciska przycisk Nowy , włącza formularz wpisu, ale wyłącza przycisk 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.
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
. Tę akcję i funkcję można zdefiniować jako funkcje lambda w konstruktorze Command
:
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 ICommandCommand
klasa definiuje również metodę o nazwie ChangeCanExecute
. Model widoku powinien wywołać 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 dla nowego PersonViewModel
obiektu wywołuje metodę ChangeCanExecute
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;
});
···
}
···
}
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 stanu początkowego.
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.
Uwaga
Nie jest konieczne zdefiniowanie execute
metod i canExecute
jako funkcji lambda. Można je zapisywać jako metody prywatne w modelu viewmodel i odwoływać się do nich w Command
konstruktorach. Jednak takie podejście może spowodować wiele metod, do których odwołuje się tylko raz w modelu widoku.
Używanie parametrów polecenia
Czasami jest to wygodne dla jednego lub kilku przycisków lub innych obiektów interfejsu użytkownika, aby współużytkować tę samą ICommand właściwość w modelu widoków. W takim przypadku można 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 podczas określania 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.
W poniższym przykładzie pokazano klawiaturę do wprowadzania liczb dziesiętnych:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.DecimalKeypadPage"
Title="Decimal Keyboard">
<ContentPage.BindingContext>
<local:DecimalKeypadViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<Style TargetType="Button">
<Setter Property="FontSize" Value="32" />
<Setter Property="BorderWidth" Value="1" />
<Setter Property="BorderColor" Value="Black" />
</Style>
</ContentPage.Resources>
<Grid WidthRequest="240"
HeightRequest="480"
ColumnDefinitions="80, 80, 80"
RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto"
ColumnSpacing="2"
RowSpacing="2"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label Text="{Binding Entry}"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
Margin="0,0,10,0"
FontSize="32"
LineBreakMode="HeadTruncation"
VerticalTextAlignment="Center"
HorizontalTextAlignment="End" />
<Button Text="CLEAR"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Command="{Binding ClearCommand}" />
<Button Text="⇦"
Grid.Row="1" Grid.Column="2"
Command="{Binding BackspaceCommand}" />
<Button Text="7"
Grid.Row="2" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="7" />
<Button Text="8"
Grid.Row="2" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="8" />
<Button Text="9"
Grid.Row="2" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="9" />
<Button Text="4"
Grid.Row="3" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="4" />
<Button Text="5"
Grid.Row="3" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="5" />
<Button Text="6"
Grid.Row="3" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="6" />
<Button Text="1"
Grid.Row="4" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="1" />
<Button Text="2"
Grid.Row="4" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="2" />
<Button Text="3"
Grid.Row="4" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="3" />
<Button Text="0"
Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
Command="{Binding DigitCommand}"
CommandParameter="0" />
<Button Text="·"
Grid.Row="5" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="." />
</Grid>
</ContentPage>
W tym przykładzie strona BindingContext
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 poleceniami w modelu widoków: ClearCommand
, BackspaceCommand
i DigitCommand
. 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
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:
Definiuje DecimalKeypadViewModel
właściwość typu string
i trzy właściwości typu ICommand:Entry
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 ClearCommand
elementowi jest zawsze włączony i 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
.
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
. Właściwość DigitCommand
jest ustawiona na wystąpienie Command<T>
klasy . W przypadku używania interfejsu poleceń z językiem XAML CommandParameter
właściwości są zwykle ciągami, które są typem 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.