Xamarin.Forms Conversores de valor de associação
Normalmente, associações de dados transferem dados de uma propriedade de origem para uma propriedade de destino e, em alguns casos, da propriedade de destino para a propriedade de origem. Essa transferência é simples quando as propriedades de origem e de destino são do mesmo tipo ou quando um tipo pode ser convertido para outro por meio de uma conversão implícita. Quando não é esse o caso, é necessário realizar uma conversão de tipo.
No artigo Formatação de Cadeia de Caracteres, você viu como é possível usar a propriedade StringFormat
de uma associação de dados para converter qualquer tipo em uma cadeia de caracteres. Para outros tipos de conversões, você precisa escrever código especializado em uma classe que implementa a interface IValueConverter
. (A Plataforma Universal do Windows contém uma classe semelhante nomeada IValueConverter
no Windows.UI.Xaml.Data
namespace, mas isso IValueConverter
está no Xamarin.Forms
namespace.) As classes que implementam IValueConverter
são chamadas de conversores de valor, mas também são frequentemente chamadas de conversores de associação ou conversores de valor de associação.
A interface IValueConverter
Digamos que você queira definir uma associação de dados em que a propriedade de origem é do tipo int
, mas a propriedade de destino é um bool
. Você quer que essa associação de dados produza um valor de false
quando a origem do inteiro for igual a 0 e, caso contrário, true
.
É possível fazer isso com uma classe que implemente a interface IValueConverter
:
public class IntToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value != 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 1 : 0;
}
}
Você define uma instância dessa classe como a propriedade Converter
da classe Binding
ou como a propriedade Converter
da extensão de marcação Binding
. Essa classe se torna parte da associação de dados.
O método Convert
é chamado quando dados são passados da origem para o destino nas associações OneWay
ou TwoWay
. O parâmetro value
é o objeto ou o valor da origem da associação de dados. O método deve retornar um valor com o tipo do destino da associação de dados. O método mostrado aqui converte o parâmetro value
para um int
e, em seguida, o compara com 0 para um valor retornado de bool
.
O método ConvertBack
é chamado quando dados são passados do destino para a origem nas associações TwoWay
ou OneWayToSource
. ConvertBack
realiza a conversão oposta: ele pressupõe que o parâmetro value
é um bool
do destino e o converte em um valor retornado de int
para a fonte.
Se a associação de dados também incluir uma configuração de StringFormat
, o conversor de valor será invocado antes que o resultado seja formatado como uma cadeia de caracteres.
A página Habilitar Botões no exemplo demonstra como usar esse conversor de valor em uma associação de dados. É criada uma instância de IntToBoolConverter
no dicionário de recursos da página. Em seguida, ele é referenciado com uma extensão de marcação StaticResource
para definir a propriedade Converter
nas duas associações de dados. É muito comum compartilhar conversores de dados entre várias associações de dados na página:
<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.EnableButtonsPage"
Title="Enable Buttons">
<ContentPage.Resources>
<ResourceDictionary>
<local:IntToBoolConverter x:Key="intToBool" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Padding="10, 0">
<Entry x:Name="entry1"
Text=""
Placeholder="enter search term"
VerticalOptions="CenterAndExpand" />
<Button Text="Search"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
IsEnabled="{Binding Source={x:Reference entry1},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
<Entry x:Name="entry2"
Text=""
Placeholder="enter destination"
VerticalOptions="CenterAndExpand" />
<Button Text="Submit"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
IsEnabled="{Binding Source={x:Reference entry2},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
</StackLayout>
</ContentPage>
Se um conversor de valor for usado em várias páginas de seu aplicativo, você poderá criar uma instância dele no dicionário de recursos no arquivo App.xaml.
A página Habilitar Botões demonstra uma necessidade comum quando um Button
executa uma operação com base no texto que o usuário digita em uma exibição de Entry
. Se nada tiver sido digitado no Entry
, o Button
deverá ser desabilitado. Cada Button
contém uma associação de dados em sua propriedade IsEnabled
. A origem da associação de dados é a propriedade Length
da propriedade Text
do Entry
correspondente. Se essa propriedade Length
não for 0, o conversor de valor retornará true
e Button
será habilitado:
Observe que a propriedade Text
em cada Entry
é inicializada como uma cadeia de caracteres vazia. A propriedade Text
é null
por padrão e a associação de dados não funcionará nesse caso.
Alguns conversores de valor são escritos especificamente para aplicativos específicos, enquanto outros são generalizados. Se você souber que um conversor de valor será usado apenas em associações de OneWay
, o método ConvertBack
poderá simplesmente retornar null
.
O método Convert
mostrado acima supõe implicitamente que o argumento value
é do tipo int
e que o valor retornado deve ser do tipo bool
. Da mesma forma, o método ConvertBack
supõe que o argumento value
é do tipo bool
e que o valor retornado é int
. Se não for esse o caso, ocorrerá uma exceção de runtime.
Você pode escrever conversores de valor de forma que eles sejam mais generalizados e aceitem vários tipos de dados diferentes. Os métodos Convert
e ConvertBack
podem usar os operadores as
ou is
com o parâmetro value
ou podem chamar GetType
nesse parâmetro para determinar seu tipo e, em seguida, fazer algo apropriado. O tipo esperado do valor retornado de cada método é determinado pelo parâmetro targetType
. Às vezes, conversores de valor são usados com associações de dados com tipos de destino diferentes. O conversor de valor pode usar o argumento targetType
para executar uma conversão para o tipo correto.
Se a conversão que está sendo executada for diferente para culturas diferentes, use o parâmetro culture
para essa finalidade. O argumento parameter
para Convert
e ConvertBack
é abordado posteriormente neste artigo.
Propriedades do conversor de associação
As classes do conversor de valor podem ter propriedades e parâmetros genéricos. Este conversor de valor específico converte um bool
da origem para um objeto do tipo T
para o destino:
public class BoolToObjectConverter<T> : IValueConverter
{
public T TrueObject { set; get; }
public T FalseObject { set; get; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? TrueObject : FalseObject;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((T)value).Equals(TrueObject);
}
}
A página Alternar Indicadores demonstra como ele pode ser usado para exibir o valor de uma exibição de Switch
. Embora seja comum instanciar conversores de valor como recursos em um dicionário de recursos, esta página demonstra uma alternativa: cada conversor de valor é instanciado entre marcas do elemento de propriedade Binding.Converter
. O x:TypeArguments
indica o argumento genérico e TrueObject
e FalseObject
são configurados como objetos desse tipo:
<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.SwitchIndicatorsPage"
Title="Switch Indicators">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="18" />
<Setter Property="VerticalOptions" Value="Center" />
</Style>
<Style TargetType="Switch">
<Setter Property="VerticalOptions" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Padding="10, 0">
<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Subscribe?" />
<Switch x:Name="switch1" />
<Label>
<Label.Text>
<Binding Source="{x:Reference switch1}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="x:String"
TrueObject="Of course!"
FalseObject="No way!" />
</Binding.Converter>
</Binding>
</Label.Text>
</Label>
</StackLayout>
<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Allow popups?" />
<Switch x:Name="switch2" />
<Label>
<Label.Text>
<Binding Source="{x:Reference switch2}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="x:String"
TrueObject="Yes"
FalseObject="No" />
</Binding.Converter>
</Binding>
</Label.Text>
<Label.TextColor>
<Binding Source="{x:Reference switch2}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="Color"
TrueObject="Green"
FalseObject="Red" />
</Binding.Converter>
</Binding>
</Label.TextColor>
</Label>
</StackLayout>
<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Learn more?" />
<Switch x:Name="switch3" />
<Label FontSize="18"
VerticalOptions="Center">
<Label.Style>
<Binding Source="{x:Reference switch3}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="Style">
<local:BoolToObjectConverter.TrueObject>
<Style TargetType="Label">
<Setter Property="Text" Value="Indubitably!" />
<Setter Property="FontAttributes" Value="Italic, Bold" />
<Setter Property="TextColor" Value="Green" />
</Style>
</local:BoolToObjectConverter.TrueObject>
<local:BoolToObjectConverter.FalseObject>
<Style TargetType="Label">
<Setter Property="Text" Value="Maybe later" />
<Setter Property="FontAttributes" Value="None" />
<Setter Property="TextColor" Value="Red" />
</Style>
</local:BoolToObjectConverter.FalseObject>
</local:BoolToObjectConverter>
</Binding.Converter>
</Binding>
</Label.Style>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
No último dos três pares de Switch
e Label
, o argumento genérico é definido como Style
e objetos Style
inteiros são fornecidos para os valores de TrueObject
e FalseObject
. Eles substituem o estilo implícito de Label
definido no dicionário de recursos, portanto, as propriedades do estilo são atribuídas explicitamente ao Label
. Ativar/desativar o Switch
faz com que o Label
correspondente reflita a alteração:
Também é possível usar Triggers
para implementar alterações semelhantes na interface do usuário com base em outras exibições.
Parâmetros do conversor de associação
A classe Binding
define uma propriedade ConverterParameter
e a extensão de marcação Binding
também define uma propriedade ConverterParameter
. Se essa propriedade for definida, o valor será passado para os métodos Convert
e ConvertBack
como o argumento parameter
. Mesmo se a instância do conversor de valor for compartilhada entre várias associações de dados, o ConverterParameter
poderá ser diferente para realizar conversões de alguma forma diferentes.
O uso de ConverterParameter
é demonstrado com um programa de seleção de cor. Nesse caso, o RgbColorViewModel
tem três propriedades do tipo double
denominadas Red
, Green
, e Blue
que ele usa para construir um valor de Color
:
public class RgbColorViewModel : INotifyPropertyChanged
{
Color color;
string name;
public event PropertyChangedEventHandler PropertyChanged;
public double Red
{
set
{
if (color.R != value)
{
Color = new Color(value, color.G, color.B);
}
}
get
{
return color.R;
}
}
public double Green
{
set
{
if (color.G != value)
{
Color = new Color(color.R, value, color.B);
}
}
get
{
return color.G;
}
}
public double Blue
{
set
{
if (color.B != value)
{
Color = new Color(color.R, color.G, value);
}
}
get
{
return color.B;
}
}
public Color Color
{
set
{
if (color != value)
{
color = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Red"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Green"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Blue"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));
Name = NamedColor.GetNearestColorName(color);
}
}
get
{
return color;
}
}
public string Name
{
private set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
get
{
return name;
}
}
}
As propriedades Red
, Green
e Blue
variam entre 0 e 1. No entanto, talvez você prefira que os componentes sejam exibidos como valores hexadecimais de dois dígitos.
Para exibi-los como valores hexadecimais em XAML, eles devem ser multiplicados por 255, convertidos em um inteiro e, em seguida, formatados com uma especificação de "X2" na propriedade StringFormat
. As duas primeiras tarefas (multiplicar por 255 e converter em um número inteiro) podem ser realizadas pelo conversor de valor. Para tornar o conversor de valor tão generalizado quanto possível, o fator de multiplicação pode ser especificado com a propriedade ConverterParameter
, o que significa que ele insere os métodos Convert
e ConvertBack
como o argumento parameter
:
public class DoubleToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)Math.Round((double)value * GetParameter(parameter));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value / GetParameter(parameter);
}
double GetParameter(object parameter)
{
if (parameter is double)
return (double)parameter;
else if (parameter is int)
return (int)parameter;
else if (parameter is string)
return double.Parse((string)parameter);
return 1;
}
}
O Convert
é convertido de um double
para int
ao multiplicar pelo valor de parameter
; o ConvertBack
divide o argumento value
inteiro pelo parameter
e retorna um resultado double
. (No programa mostrado abaixo, o conversor de valor é usado apenas para formatação da cadeia de caracteres e, portanto, ConvertBack
não é usado.)
O tipo do argumento parameter
provavelmente será diferente dependendo de a associação de dados estar definida no código ou em XAML. Se a propriedade ConverterParameter
de Binding
estiver definida no código, será provável que ela esteja definida como um valor numérico:
binding.ConverterParameter = 255;
A propriedade ConverterParameter
é do tipo Object
, portanto, o compilador de C# interpreta o literal 255 como um número inteiro e define a propriedade com esse valor.
Em XAML, no entanto, é provável que o ConverterParameter
esteja definido desta forma:
<Label Text="{Binding Red,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Red = {0:X2}'}" />
O 255 é semelhante a um número, mas como ConverterParameter
é do tipo Object
, o analisador de XAML trata o 255 como uma cadeia de caracteres.
Por esse motivo, o conversor de valor mostrado acima inclui um método GetParameter
separado que lida com os casos de parameter
ser do tipo double
, int
ou string
.
A página Seletor de Cor RGB cria uma instância de DoubleToIntConverter
em seu dicionário de recursos após a definição de dois estilos implícitos:
<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.RgbColorSelectorPage"
Title="RGB Color Selector">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Slider">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<local:DoubleToIntConverter x:Key="doubleToInt" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<StackLayout.BindingContext>
<local:RgbColorViewModel Color="Gray" />
</StackLayout.BindingContext>
<BoxView Color="{Binding Color}"
VerticalOptions="FillAndExpand" />
<StackLayout Margin="10, 0">
<Label Text="{Binding Name}" />
<Slider Value="{Binding Red}" />
<Label Text="{Binding Red,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Red = {0:X2}'}" />
<Slider Value="{Binding Green}" />
<Label Text="{Binding Green,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Green = {0:X2}'}" />
<Slider Value="{Binding Blue}" />
<Label>
<Label.Text>
<Binding Path="Blue"
StringFormat="Blue = {0:X2}"
Converter="{StaticResource doubleToInt}">
<Binding.ConverterParameter>
<x:Double>255</x:Double>
</Binding.ConverterParameter>
</Binding>
</Label.Text>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
Os valores das propriedades Red
e Green
são exibidos com uma extensão de marcação Binding
. A propriedade Blue
, no entanto, instancia a classe Binding
para demonstrar como um valor de double
explícito pode ser definido como a propriedade ConverterParameter
.
Eis o resultado: