A Xamarin.Forms interface de comando
Na arquitetura MVVM (Model-View-ViewModel), as associações de dados são definidas entre propriedades no ViewModel, que geralmente é uma classe derivada de INotifyPropertyChanged
, e propriedades no View, que geralmente é o arquivo XAML. Às vezes, um aplicativo tem necessidades que vão além dessas associações de propriedade ao exigir que o usuário inicie os comandos que afetam algo no ViewModel. Esses comandos geralmente são sinalizados por cliques de botões ou toques de dedos e são tradicionalmente processados no arquivo code-behind em um manipulador para o evento Clicked
do Button
ou o evento Tapped
de um TapGestureRecognizer
.
A interface de comando oferece uma abordagem alternativa à implementação de comandos, que é bem mais adequada à arquitetura MVVM. O próprio ViewModel pode conter comandos, que são métodos executados em reação a uma atividade específica no View, como um clique Button
. Associações de dados são definidas entre esses comandos e o Button
.
Para permitir uma associação de dados entre um Button
e um ViewModel, o Button
define duas propriedades:
Command
do tipoSystem.Windows.Input.ICommand
CommandParameter
do tipoObject
Para usar a interface de comando, defina uma associação de dados que direcione a propriedade Command
do Button
, em que a origem é uma propriedade no ViewModel do tipo ICommand
. O ViewModel contém um código associado a essa propriedade ICommand
executada quando se clica no botão. É possível definir CommandParameter
como dados arbitrários para distinguir entre vários botões se eles forem todos associados à mesma propriedade ICommand
no ViewModel.
As propriedades Command
e CommandParameter
também são definidas pelas seguintes classes:
MenuItem
eToolbarItem
, portanto, derivado deMenuItem
TextCell
eImageCell
, portanto, derivado deTextCell
TapGestureRecognizer
SearchBar
define uma propriedade SearchCommand
do tipo ICommand
e uma propriedade SearchCommandParameter
. A propriedade RefreshCommand
de ListView
também é do tipo ICommand
.
Todos esses comandos podem ser manipulados dentro de um ViewModel, de forma que ele não dependa do objeto específico da interface do usuário no View.
A interface ICommand
A System.Windows.Input.ICommand
interface não faz parte do Xamarin.Forms. Ela é definida no lugar do namespace System.Windows.Input e consiste em dois métodos e um evento:
public interface ICommand
{
public void Execute (Object parameter);
public bool CanExecute (Object parameter);
public event EventHandler CanExecuteChanged;
}
Para usar a interface de comando, o ViewModel contém as propriedades do tipo ICommand
:
public ICommand MyCommand { private set; get; }
O ViewModel também deve referenciar uma classe que implementa a interface ICommand
. Essa classe será descrita em breve. No View, a propriedade Command
de um Button
é associada a essa propriedade:
<Button Text="Execute command"
Command="{Binding MyCommand}" />
Quando o usuário pressiona o Button
, o Button
chama o método Execute
no objeto ICommand
associado a sua propriedade Command
. Essa é a parte mais simples da interface de comando.
O método CanExecute
é mais complexo. Quando a associação é definida na propriedade Command
do Button
pela primeira vez e quando a associação de dados é alterada de alguma forma, o Button
chama o método CanExecute
no objeto ICommand
. Se CanExecute
retornar false
, então o Button
se desabilitará. Isso indica que o comando específico não está disponível no momento ou é inválido.
O Button
também anexa um manipulador no evento CanExecuteChanged
de ICommand
. O evento é acionado de dentro do ViewModel. Quando o evento é acionado, o Button
chama o CanExecute
novamente. O Button
habilita a si mesmo se o CanExecute
retorna true
e desabilita a si mesmo se CanExecute
retorna false
.
Importante
Não use a propriedade IsEnabled
de Button
se estiver usando a interface de comando.
A classe de comando
Quando o ViewModel define uma propriedade do tipo ICommand
, ele também deve conter ou referenciar uma classe que implementa a interface ICommand
. Essa classe deverá conter ou referenciar os métodos Execute
e CanExecute
e acionar o evento CanExecuteChanged
sempre que o método CanExecute
puder retornar um valor diferente.
É possível gravar essa classe sozinho ou usar uma classe que alguém tenha gravado. Como o ICommand
faz parte do Microsoft Windows, ele tem sido usado por anos com aplicativos da MVVM do Windows. O uso de uma classe Windows implementa ICommand
permite que você compartilhe seus ViewModels entre aplicativos e Xamarin.Forms aplicativos do Windows.
Se o compartilhamento de ViewModels entre Windows e Xamarin.Forms não for uma preocupação, você poderá usar a Command
classe or Command<T>
incluída para Xamarin.Forms implementar a ICommand
interface. Essas classes permitem que você especifique os corpos dos métodos Execute
e CanExecute
em construtores de classe. Use Command<T>
quando você usar a propriedade CommandParameter
para distinguir entre vários modos de exibição associados à mesma propriedade ICommand
, e a classe Command
mais simples quando isso não for um requisito.
Comando básico
A página Entrada de Pessoa no programa de exemplo demonstra alguns comandos simples implementados em um ViewModel.
O PersonViewModel
define três propriedades denominadas Name
, Age
e Skills
que definem uma pessoa. Essa classe não contém nenhuma propriedade ICommand
:
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));
}
}
O PersonCollectionViewModel
mostrado a seguir cria novos objetos do tipo PersonViewModel
e permite que o usuário preencha os dados. Para essa finalidade, a classe define propriedades IsEditing
do tipo bool
e PersonEdit
do tipo PersonViewModel
. Além disso, a classe define três propriedades do tipo ICommand
e uma propriedade chamada Persons
do tipo 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));
}
}
Essa listagem abreviada não inclui o construtor da classe, que é onde as três propriedades do tipo ICommand
são definidas, que serão mostradas em breve. Observe que as alterações nas três propriedades do tipo ICommand
e a propriedade Persons
não faz os eventos PropertyChanged
serem acionados. Todas essas propriedades são definidas quando a classe é criada pela primeira vez e não são alteradas depois disso.
Antes de examinar o construtor da classe PersonCollectionViewModel
, vamos examinar se há o programa Entrada de pessoa no arquivo XAML. Ele contém um Grid
com sua propriedade BindingContext
definida como o PersonCollectionViewModel
. O Grid
contém um Button
com o texto Novo com sua propriedade Command
associada à propriedade NewCommand
no ViewModel, um formulário de entrada com propriedades associadas à propriedade IsEditing
, bem como propriedades de PersonViewModel
e mais dois botões associados às propriedades SubmitCommand
e CancelCommand
do ViewModel. O último ListView
exibe a coleção de pessoas já que já entraram:
<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>
Veja como ele funciona: o usuário primeiro pressiona o botão Novo. Isso habilita o formulário de entrada, mas desabilita o botão Novo. O usuário insere, então, um nome, idade e habilidades. A qualquer momento durante a edição, o usuário pode pressionar o botão Cancelar para recomeçar. Somente depois que um nome e uma idade válida forem inseridos, o botão Enviar será habilitado. Pressionar esse botão Enviar transfere a pessoa para a coleção exibida pelo ListView
. Depois que o botão Cancelar ou Enviar é pressionado, o formulário de entrada é limpo e o botão Novo é habilitado novamente.
A tela do iOS à esquerda mostra o layout antes que uma idade válida seja inserida. A tela do Android mostra o botão Enviar ativado após a definição de uma idade:
O programa não tem nenhum recurso para editar entradas existentes nem salva as entradas quando você sai da página.
Toda a lógica dos botões Novo, Enviar, e Cancelar é tratado em PersonCollectionViewModel
por meio das definições das propriedades NewCommand
, SubmitCommand
e CancelCommand
. O construtor do PersonCollectionViewModel
define essas três propriedades para objetos do tipo Command
.
Um construtor da classe Command
permite que você passe argumentos do tipo Action
e Func<bool>
correspondentes aos métodos Execute
e CanExecute
. É mais fácil definir essas ações e funções como funções lambda direto no construtor Command
. Veja a definição do objeto Command
para a propriedade NewCommand
:
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();
}
···
}
Quando o usuário clica no botão Novo, a função execute
passada para o construtor Command
é executada. Isso cria um novo objeto PersonViewModel
, define um manipulador no evento PropertyChanged
desse objeto, define IsEditing
como true
e chama o método RefreshCanExecutes
definido após o construtor.
Além de implementar a interface ICommand
, a classe Command
também define um método chamado ChangeCanExecute
. O ViewModel deve chamar ChangeCanExecute
para uma propriedade ICommand
sempre que acontecer qualquer coisa que possa alterar o valor retornado do método CanExecute
. Uma chamada ao ChangeCanExecute
faz a classe Command
acionar o método CanExecuteChanged
. O Button
anexou um manipulador para o evento, responde chamando CanExecute
novamente e permitindo que se baseie no valor retornado desse método.
Quando o método execute
de NewCommand
chama RefreshCanExecutes
, a propriedade NewCommand
obtém uma chamada a ChangeCanExecute
, e o Button
chama o método canExecute
, que agora retorna false
porque a propriedade IsEditing
agora é true
.
O manipulador PropertyChanged
do novo objeto PersonViewModel
chama o método ChangeCanExecute
de SubmitCommand
. Veja como essa propriedade de comando é implementada:
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;
});
···
}
···
}
A função canExecute
para SubmitCommand
é chamada sempre que há uma propriedade alterada no objeto PersonViewModel
que está sendo editado. Ela retorna true
somente quando a propriedade Name
tem pelo menos um caractere de comprimento e Age
é maior que 0. Nesse momento, o botão Enviar fica habilitado.
A função execute
para Enviar remove o manipulador de propriedade alterada do PersonViewModel
, adiciona o objeto à coleção Persons
e retorna tudo para as condições iniciais.
A função execute
para o botão Cancelar faz tudo o que o botão Enviar faz, exceto adicionar o objeto à coleção:
public class PersonCollectionViewModel : INotifyPropertyChanged
{
···
public PersonCollectionViewModel()
{
···
CancelCommand = new Command(
execute: () =>
{
PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
PersonEdit = null;
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
{
return IsEditing;
});
}
···
}
O método canExecute
retorna true
a qualquer momento que um PersonViewModel
está sendo editado.
Essas técnicas poderiam ser adaptadas para cenários mais complexos: uma propriedade no PersonCollectionViewModel
poderia estar associada à propriedade SelectedItem
do ListView
para editar itens existentes e um botão Excluir poderia ser adicionado para excluir esses itens.
Não é necessário definir os métodos execute
e canExecute
como funções lambda. É possível gravá-los como métodos privados regulares no ViewModel e referenciá-los nos construtores Command
. No entanto, essa abordagem tende a resultar em uma grande quantidade de métodos referenciados apenas uma vez no ViewModel.
Uso de parâmetros de comando
Às vezes, é conveniente para um ou mais botões (ou outros objetos da interface do usuário) compartilhar a mesma propriedade ICommand
no ViewModel. Nesse caso, você usa a propriedade CommandParameter
para distinguir entre os botões.
É possível continuar usando a classe Command
para essas propriedades ICommand
compartilhadas. A classe define um construtor alternativo que aceita os métodos execute
e canExecute
com parâmetros do tipo Object
. Isso é como o CommandParameter
é passado para esses métodos.
No entanto, ao usar CommandParameter
, é mais fácil de usar a classe Command<T>
genérica para especificar o tipo do objeto definido como CommandParameter
. Os métodos execute
e canExecute
que você especificar têm parâmetros desse tipo.
A página Teclado decimal ilustra essa técnica mostrando como implementar um teclado numérico para inserir números decimais. O BindingContext
para o Grid
é um DecimalKeypadViewModel
. A propriedade Entry
desse ViewModel está associada à propriedade Text
de um Label
. Todos os objetos Button
estão associados a vários comandos no ViewModel: ClearCommand
, BackspaceCommand
e 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>
Os 11 botões para os 10 dígitos e o ponto decimal compartilham uma associação a DigitCommand
. O CommandParameter
faz distinção entre esses botões. O valor definido como CommandParameter
geralmente é o mesmo que o texto exibido pelo botão, exceto para o ponto decimal, que, para fins de esclarecimento, é exibido com um caractere de ponto no meio.
Veja o programa em ação:
Observe que o botão para o ponto decimal em todas as três capturas de tela está desabilitado, porque o número inserido já contém um ponto decimal.
O DecimalKeypadViewModel
define uma propriedade Entry
do tipo string
(que é a única propriedade que dispara um evento PropertyChanged
) e três propriedades do tipo 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; }
}
O botão correspondente ao ClearCommand
está sempre habilitado e simplesmente define a entrada de volta para "0":
public class DecimalKeypadViewModel : INotifyPropertyChanged
{
···
public DecimalKeypadViewModel()
{
ClearCommand = new Command(
execute: () =>
{
Entry = "0";
RefreshCanExecutes();
});
···
}
void RefreshCanExecutes()
{
((Command)BackspaceCommand).ChangeCanExecute();
((Command)DigitCommand).ChangeCanExecute();
}
···
}
Como o botão sempre está habilitado, não é necessário especificar um argumento canExecute
no construtor Command
.
A lógica para inserir números e backspace é um pouco complicada, porque se nenhum dígito foi inserido, a propriedade Entry
será a cadeia de caracteres "0". Se o usuário digitar mais zeros, então o Entry
ainda conterá apenas um zero. Se o usuário digita qualquer outro dígito, esse dígito substitui o zero. Mas se o usuário digita um ponto decimal antes de qualquer outro dígito, então Entry
é a cadeia de caracteres "0".
O botão Backspace é habilitado apenas quando o comprimento da entrada é maior que 1 ou se Entry
não é igual à cadeia de caracteres "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";
});
···
}
···
}
A lógica para a função execute
do botão Backspace garante que o Entry
é pelo menos uma cadeia de caracteres de comprimento "0".
A propriedade DigitCommand
está associada a 11 botões, cada um dos quais identifica-se com a propriedade CommandParameter
. O DigitCommand
poderia ser definido como uma instância da classe Command
regular, mas é mais fácil usar a classe genérica Command<T>
. Ao usar a interface de comando com o XAML, as propriedades CommandParameter
geralmente são cadeias de caracteres e esse é o tipo de argumento genérico. As funções execute
e canExecute
têm argumentos do tipo 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("."));
});
}
···
}
O método execute
acrescenta o argumento de cadeia de caracteres à propriedade Entry
. No entanto, se o resultado começar com um zero (mas não um zero e um ponto decimal), esse zero inicial deverá ser removido usando a função Substring
.
O método canExecute
retornará false
somente se o argumento for o ponto decimal (que indica que o ponto decimal está sendo pressionado) e Entry
já contém um ponto decimal.
Todos os métodos execute
chamam RefreshCanExecutes
, que chama ChangeCanExecute
para DigitCommand
e ClearCommand
. Isso garante que o ponto decimal e os botões backspace estão habilitados ou desabilitados com base na sequência atual de dígitos inseridos.
Comando assíncrono para menus de navegação
O comando é conveniente para implementar menus de navegação. Veja uma parte do 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>
Ao usar comandos com XAML, as propriedades CommandParameter
geralmente são definidas como cadeias de caracteres. No entanto, nesse caso, uma extensão de marcação XAML é usada para que o CommandParameter
seja do tipo System.Type
.
Cada propriedade Command
está associada a uma propriedade chamada NavigateCommand
. Se a propriedade estiver definida no arquivo 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; }
}
O construtor define a propriedade NavigateCommand
como um método execute
que cria a instância do parâmetro System.Type
e navega até ela. Como a chamada PushAsync
requer um operador await
, o método execute
deve ser sinalizado como assíncrono. Isso é feito com a palavra-chave async
antes da lista de parâmetros.
O construtor também define o BindingContext
da página como a si mesmo para que as associações referenciem o NavigateCommand
nessa classe.
A ordem do código nesse construtor faz a diferença: a chamada InitializeComponent
faz o XAML ser analisado, mas, nesse momento, a associação a uma propriedade denominada NavigateCommand
não pode ser resolvida, porque BindingContext
é definido como null
. Se o BindingContext
for definido no construtor antes de NavigateCommand
ser definido, a associação poderá ser resolvida quando BindingContext
for definida, mas nesse momento, NavigateCommand
ainda null
é . Definir NavigateCommand
após BindingContext
não terá efeito na associação, porque uma alteração no NavigateCommand
não dispara um evento PropertyChanged
, e a associação não sabe que NavigateCommand
é válido agora.
Definir NavigateCommand
e BindingContext
(em qualquer ordem) antes da chamada a InitializeComponent
funcionará, porque os componentes da associação são definidos quando o analisador XAML encontra a definição da associação.
Às vezes, as associações de dados podem ser complicadas, mas, como você viu nessa série de artigos, elas são poderosas e versáteis e ajudam bastante a organizar seu código separando a lógica subjacente da interface do usuário.