Parte 4. Conceitos básicos da associação de dados
As associações de dados permitem que as propriedades de dois objetos sejam vinculadas para que uma alteração em um cause uma alteração no outro. Essa é uma ferramenta muito valiosa e, embora as associações de dados possam ser definidas inteiramente no código, o XAML fornece atalhos e conveniência. Consequentemente, uma das extensões de marcação mais importantes é Xamarin.Forms a Binding.
Associações de dados
As associações de dados conectam propriedades de dois objetos, chamados de origem e destino. No código, duas etapas são necessárias: a BindingContext
propriedade do objeto de destino deve ser definida como o objeto de origem e o SetBinding
método (geralmente usado em conjunto com a Binding
classe) deve ser chamado no objeto de destino para associar uma propriedade desse objeto a uma propriedade do objeto de origem.
A propriedade de destino deve ser uma propriedade associável, o que significa que o objeto de destino deve derivar de BindableObject
. A documentação online Xamarin.Forms indica quais propriedades são propriedades associáveis. Uma propriedade de Label
tal como Text
está associada à propriedade TextProperty
associável .
Na marcação, você também deve executar as mesmas duas etapas necessárias no código, exceto que a Binding
extensão de marcação substitui a SetBinding
chamada e a Binding
classe.
No entanto, quando você define associações de dados em XAML, há várias maneiras de definir o BindingContext
do objeto de destino. Às vezes, ele é definido a partir do arquivo code-behind, às vezes usando uma StaticResource
extensão de marcação ou x:Static
e, às vezes, como o conteúdo de marcas de elemento de BindingContext
propriedade.
As associações são usadas com mais frequência para conectar os visuais de um programa a um modelo de dados subjacente, geralmente em uma realização da arquitetura de aplicativo MVVM (Model-View-ViewModel), conforme discutido na Parte 5. De associações de dados para MVVM, mas outros cenários são possíveis.
Associações de exibição para exibição
Você pode definir associações de dados para vincular propriedades de dois modos de exibição na mesma página. Nesse caso, você define o BindingContext
do objeto de destino usando a x:Reference
extensão de marcação.
Aqui está um arquivo XAML que contém um Slider
e dois Label
modos de exibição, um dos quais é girado Slider
pelo valor e outro que exibe o Slider
valor:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderBindingsPage"
Title="Slider Bindings Page">
<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
<Label BindingContext="{x:Reference slider}"
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
O Slider
contém um x:Name
atributo que é referenciado pelas duas Label
exibições usando a extensão de x:Reference
marcação.
A x:Reference
extensão de associação define uma propriedade chamada Name
para definir o nome do elemento referenciado, neste caso slider
. No entanto, a ReferenceExtension
classe que define a extensão de x:Reference
marcação também define um ContentProperty
atributo para Name
, o que significa que ela não é explicitamente necessária. Apenas para variar, o primeiro x:Reference
inclui "Name=", mas o segundo não:
BindingContext="{x:Reference Name=slider}"
…
BindingContext="{x:Reference slider}"
A Binding
extensão de marcação em si pode ter várias propriedades, assim como a BindingBase
classe and Binding
. O ContentProperty
for Binding
é Path
, mas a parte "Path=" da extensão de marcação pode ser omitida se o caminho for o primeiro item na extensão de Binding
marcação. O primeiro exemplo tem "Path=", mas o segundo exemplo o omite:
Rotation="{Binding Path=Value}"
…
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
As propriedades podem estar todas em uma linha ou separadas em várias linhas:
Text="{Binding Value,
StringFormat='The angle is {0:F0} degrees'}"
Faça o que for conveniente.
Observe a StringFormat
propriedade na segunda Binding
extensão de marcação. No Xamarin.Forms, as ligações não executam nenhuma conversão de tipo implícita e, se você precisar exibir um objeto que não seja de cadeia de caracteres como uma cadeia de caracteres, deverá fornecer um conversor de tipo ou usar StringFormat
. Nos bastidores, o método estático String.Format
é usado para implementar StringFormat
o . Isso é potencialmente um problema, pois as especificações de formatação do .NET envolvem chaves, que também são usadas para delimitar extensões de marcação. Isso cria um risco de confundir o analisador XAML. Para evitar isso, coloque toda a string de formatação entre aspas simples:
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
Aqui está o programa em execução:
O modo de vinculação
Uma única exibição pode ter associações de dados em várias de suas propriedades. No entanto, cada exibição pode ter apenas um BindingContext
, portanto, várias associações de dados nessa exibição devem fazer referência a propriedades do mesmo objeto.
A solução para este e outros problemas envolve a Mode
propriedade, que é definida como um membro da BindingMode
enumeração:
Default
OneWay
— os valores são transferidos da origem para o destinoOneWayToSource
— os valores são transferidos do destino para a fonteTwoWay
— os valores são transferidos nos dois sentidos entre a origem e o destinoOneTime
— os dados vão da origem ao destino, mas apenas quando asBindingContext
alterações
O programa a seguir demonstra um uso comum dos OneWayToSource
modos de associação e TwoWay
. Quatro Slider
modos de exibição destinam-se a controlar as Scale
propriedades , Rotate
, RotateX
e RotateY
de um Label
. A princípio, parece que essas quatro propriedades do devem Label
ser destinos de vinculação de dados porque cada uma está sendo definida por um Slider
. No entanto, o BindingContext
of Label
pode ser apenas um objeto e há quatro controles deslizantes diferentes.
Por esse motivo, todas as associações são definidas de maneiras aparentemente invertidas: O BindingContext
de cada um dos quatro controles deslizantes é definido como , Label
e as associações são definidas nas Value
propriedades dos controles deslizantes. Usando os OneWayToSource
modos e TwoWay
, essas Value
propriedades podem definir as propriedades de origem, que são as Scale
propriedades , Rotate
, RotateX
, e RotateY
do Label
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderTransformsPage"
Padding="5"
Title="Slider Transforms Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Scaled and rotated Label -->
<Label x:Name="label"
Text="TEXT"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<!-- Slider and identifying Label for Scale -->
<Slider x:Name="scaleSlider"
BindingContext="{x:Reference label}"
Grid.Row="1" Grid.Column="0"
Maximum="10"
Value="{Binding Scale, Mode=TwoWay}" />
<Label BindingContext="{x:Reference scaleSlider}"
Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
Grid.Row="1" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for Rotation -->
<Slider x:Name="rotationSlider"
BindingContext="{x:Reference label}"
Grid.Row="2" Grid.Column="0"
Maximum="360"
Value="{Binding Rotation, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationSlider}"
Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
Grid.Row="2" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for RotationX -->
<Slider x:Name="rotationXSlider"
BindingContext="{x:Reference label}"
Grid.Row="3" Grid.Column="0"
Maximum="360"
Value="{Binding RotationX, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationXSlider}"
Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
Grid.Row="3" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for RotationY -->
<Slider x:Name="rotationYSlider"
BindingContext="{x:Reference label}"
Grid.Row="4" Grid.Column="0"
Maximum="360"
Value="{Binding RotationY, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationYSlider}"
Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
Grid.Row="4" Grid.Column="1"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>
As ligações em três das Slider
visualizações são OneWayToSource
, o que significa que o Slider
valor causa uma alteração na propriedade de seu BindingContext
, que é o Label
nome label
. Esses três Slider
modos de exibição causam alterações no Rotate
, RotateX
e RotateY
nas propriedades do Label
.
No entanto, a associação para a Scale
propriedade é TwoWay
. Isso ocorre porque a Scale
propriedade tem um valor padrão de 1 e o uso de uma TwoWay
associação faz com que o Slider
valor inicial seja definido como 1 em vez de 0. Se essa associação fosse OneWayToSource
, a Scale
propriedade seria inicialmente definida como 0 do Slider
valor padrão. Eles Label
não seriam visíveis e isso poderia causar alguma confusão ao usuário.
Observação
A VisualElement
classe também tem ScaleX
propriedades e ScaleY
, que dimensionam o eixo x e o VisualElement
eixo y, respectivamente.
Encadernações e coleções
Nada ilustra melhor o poder do XAML e das associações de dados do que um modelo ListView
.
ListView
define uma ItemsSource
propriedade do tipo IEnumerable
e exibe os itens dessa coleção. Esses itens podem ser objetos de qualquer tipo. Por padrão, ListView
usa o ToString
método de cada item para exibir esse item. Às vezes, isso é exatamente o que você deseja, mas, em muitos casos, ToString
retorna apenas o nome de classe totalmente qualificado do objeto.
No entanto, os itens da ListView
coleção podem ser exibidos da maneira que você desejar por meio do uso de um modelo, que envolve uma classe derivada de Cell
. O modelo é clonado para cada item no , e as ListView
associações de dados que foram definidas no modelo são transferidas para os clones individuais.
Muitas vezes, você desejará criar uma célula personalizada para esses itens usando a ViewCell
classe. Esse processo é um pouco confuso no código, mas em XAML ele se torna muito simples.
Incluída no projeto XamlSamples está uma classe chamada NamedColor
. Cada NamedColor
objeto tem Name
propriedades do tipo string
, FriendlyName
e uma Color
propriedade do tipo Color
. Além disso, NamedColor
possui 141 campos estáticos somente leitura do tipo Color
correspondentes às cores definidas na Xamarin.FormsColor
classe. Um construtor estático cria uma IEnumerable<NamedColor>
coleção que contém NamedColor
objetos correspondentes a esses campos estáticos e a atribui à sua propriedade estática All
pública.
Definir a propriedade estática NamedColor.All
como ItemsSource
a ListView
é fácil usando a x:Static
extensão de marcação:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">
<ListView ItemsSource="{x:Static local:NamedColor.All}" />
</ContentPage>
A exibição resultante estabelece que os itens são realmente do tipo XamlSamples.NamedColor
:
Não é muita informação, mas é ListView
rolável e selecionável.
Para definir um modelo para os itens, você desejará dividir a ItemTemplate
propriedade como um elemento de propriedade e defini-la como um DataTemplate
, que faz referência a um ViewCell
. Para a View
propriedade do ViewCell
você pode definir um layout de uma ou mais vistas para exibir cada item. Aqui está um exemplo simples:
<ListView ItemsSource="{x:Static local:NamedColor.All}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Label Text="{Binding FriendlyName}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Observação
A fonte de ligação para células e filhos de células é a ListView.ItemsSource
coleção.
O Label
elemento é definido como a View
propriedade do ViewCell
. (As ViewCell.View
tags não são necessárias porque a View
propriedade é a propriedade content de ViewCell
.) Essa marcação exibe a FriendlyName
propriedade de cada NamedColor
objeto:
Muito melhor. Agora, tudo o que é necessário é enfeitar o modelo de item com mais informações e a cor real. Para dar suporte a esse modelo, alguns valores e objetos foram definidos no dicionário de recursos da página:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">
<ContentPage.Resources>
<ResourceDictionary>
<OnPlatform x:Key="boxSize"
x:TypeArguments="x:Double">
<On Platform="iOS, Android, UWP" Value="50" />
</OnPlatform>
<OnPlatform x:Key="rowHeight"
x:TypeArguments="x:Int32">
<On Platform="iOS, Android, UWP" Value="60" />
</OnPlatform>
<local:DoubleToIntConverter x:Key="intConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView ItemsSource="{x:Static local:NamedColor.All}"
RowHeight="{StaticResource rowHeight}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="5, 5, 0, 5"
Orientation="Horizontal"
Spacing="15">
<BoxView WidthRequest="{StaticResource boxSize}"
HeightRequest="{StaticResource boxSize}"
Color="{Binding Color}" />
<StackLayout Padding="5, 0, 0, 0"
VerticalOptions="Center">
<Label Text="{Binding FriendlyName}"
FontAttributes="Bold"
FontSize="Medium" />
<StackLayout Orientation="Horizontal"
Spacing="0">
<Label Text="{Binding Color.R,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
<Label Text="{Binding Color.G,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', G={0:X2}'}" />
<Label Text="{Binding Color.B,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', B={0:X2}'}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Observe o uso de OnPlatform
para definir o tamanho de a BoxView
e a altura das ListView
linhas. Embora os valores para todas as plataformas sejam os mesmos, a marcação pode ser facilmente adaptada para outros valores para ajustar a exibição.
Conversores de associação de valor
O arquivo XAML de demonstração ListView anterior exibe as propriedades individuais R
, G
e B
da Xamarin.FormsColor
estrutura. Essas propriedades são do tipo double
e variam de 0 a 1. Se você deseja exibir os valores hexadecimais, não pode simplesmente usar StringFormat
com uma especificação de formatação "X2". Isso só funciona para números inteiros e, além disso, os double
valores precisam ser multiplicados por 255.
Este pequeno problema foi resolvido com um conversor de valor, também chamado de conversor de ligação. Essa é uma classe que implementa a interface, o IValueConverter
que significa que ela tem dois métodos chamados Convert
e ConvertBack
. O Convert
método é chamado quando um valor é transferido da origem para o destino; o ConvertBack
método é chamado para transferências do destino para a origem em OneWayToSource
ou TwoWay
ligações:
using System;
using System.Globalization;
using Xamarin.Forms;
namespace XamlSamples
{
class DoubleToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
double multiplier;
if (!Double.TryParse(parameter as string, out multiplier))
multiplier = 1;
return (int)Math.Round(multiplier * (double)value);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
double divider;
if (!Double.TryParse(parameter as string, out divider))
divider = 1;
return ((double)(int)value) / divider;
}
}
}
O ConvertBack
método não desempenha um papel neste programa porque as ligações são apenas unidirecionais da origem para o destino.
Uma associação faz referência a um conversor de associação com a Converter
propriedade. Um conversor de associação também pode aceitar um parâmetro especificado com a ConverterParameter
propriedade. Para alguma versatilidade, é assim que o multiplicador é especificado. O conversor de associação verifica o parâmetro do conversor em busca de um valor válido double
.
O conversor é instanciado no dicionário de recursos para que possa ser compartilhado entre várias ligações:
<local:DoubleToIntConverter x:Key="intConverter" />
Três associações de dados fazem referência a essa única instância. Observe que a extensão de Binding
marcação contém uma extensão de marcação incorporada StaticResource
:
<Label Text="{Binding Color.R,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
Aqui está o resultado:
O ListView
é bastante sofisticado no tratamento de alterações que podem ocorrer dinamicamente nos dados subjacentes, mas somente se você executar determinadas etapas. Se a coleção de itens atribuídos à ItemsSource
propriedade das alterações durante o ListView
tempo de execução, ou seja, se os itens puderem ser adicionados ou removidos da coleção, use uma ObservableCollection
classe para esses itens. ObservableCollection
implementa a INotifyCollectionChanged
interface e ListView
instalará um manipulador para o CollectionChanged
evento.
Se as propriedades dos próprios itens forem alteradas durante o runtime, os itens na coleção deverão implementar a INotifyPropertyChanged
interface e sinalizar alterações nos valores de propriedade usando o PropertyChanged
evento. Isso é demonstrado na próxima parte desta série, Parte 5. Da associação de dados ao MVVM.
Resumo
As associações de dados fornecem um mecanismo poderoso para vincular propriedades entre dois objetos em uma página ou entre objetos visuais e dados subjacentes. Mas quando o aplicativo começa a trabalhar com fontes de dados, um padrão de arquitetura de aplicativo popular começa a surgir como um paradigma útil. Isso é abordado na Parte 5. De associações de dados para MVVM.