Enlace de convertidores de valores
Los enlaces de datos de .NET Multi-platform App UI (.NET MAUI) normalmente transfieren datos desde una propiedad de origen a una propiedad de destino y, en algunos casos, desde la propiedad de destino a la propiedad de origen. Esta transferencia es sencilla cuando las propiedades de origen y destino son del mismo tipo, o cuando un tipo se puede convertir al otro mediante una conversión implícita. Cuando no es así, debe realizarse una conversión de tipos.
En el artículo String Formatting (Formato de cadena), has visto cómo puedes usar la propiedad StringFormat
de un enlace de datos para convertir cualquier tipo en una cadena. Para otros tipos de conversiones, deberá escribir código especializado en una clase que implementa la interfaz de IValueConverter. Las clases que implementan IValueConverter se denominan convertidores de valores, pero también se denominan a menudo convertidores de enlaces o convertidores de valores de enlace.
Enlace de convertidores de valores
Suponga que desea definir un enlace de datos cuya propiedad de origen es del tipo int
pero la propiedad de destino es un bool
. Desea que este enlace de datos genere un valor false
cuando el origen del entero es igual a 0 y true
en caso contrario. Puedes hacerlo con una clase que implementa la interfaz 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;
}
}
Luego establece una instancia de esta clase en la propiedad Converter
de la clase Binding
o en la propiedad Converter
de la extensión de marcado Binding
. Esta clase se convierte en parte del enlace de datos.
Se llama al método Convert
cuando los datos se mueven desde el origen al destino en los enlaces OneWay
o TwoWay
. El parámetro value
es el objeto o el valor del origen de enlace de datos. El método debe devolver un valor del tipo del destino de enlace de datos. El método que se muestra aquí convierte el parámetro value
a un int
y después lo compara con 0 para un valor devuelto bool
.
Se llama al método ConvertBack
cuando los datos se mueven desde el destino al origen en los enlaces TwoWay
o OneWayToSource
. ConvertBack
realiza la conversión opuesta: supone que el parámetro value
es un bool
desde el destino y lo convierte en un valor devuelto int
para el origen.
Nota:
Si el enlace de datos también incluye una configuración StringFormat
, se invoca el convertidor de valores antes de que se le dé formato de cadena al resultado.
En el ejemplo siguiente se muestra cómo usar este convertidor de valores en un enlace de datos:
<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.EnableButtonsPage"
Title="Enable Buttons">
<ContentPage.Resources>
<local:IntToBoolConverter x:Key="intToBool" />
</ContentPage.Resources>
<StackLayout Padding="10, 0">
<Entry x:Name="entry1"
Text=""
Placeholder="enter search term"
VerticalOptions="Center" />
<Button x:DataType="Entry"
Text="Search"
HorizontalOptions="Center"
VerticalOptions="Center"
IsEnabled="{Binding Source={x:Reference entry1},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
<Entry x:Name="entry2"
Text=""
Placeholder="enter destination"
VerticalOptions="Center" />
<Button x:DataType="Entry"
Text="Submit"
HorizontalOptions="Center"
VerticalOptions="Center"
IsEnabled="{Binding Source={x:Reference entry2},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
</StackLayout>
</ContentPage>
En este ejemplo, se crea una instancia de IntToBoolConverter
en el diccionario de recursos de la página. Después se le hace referencia con una extensión de marcado StaticResource
para establecer la propiedad Converter
en dos enlaces de datos. Es muy común compartir los convertidores de tipos de datos entre varios enlaces de datos en la página. Si se usa un convertidor de valores en varias páginas de la aplicación, puedes crear una instancia de él en el diccionario de recursos en el nivel de aplicación.
Este ejemplo demuestra una necesidad común cuando un Button realiza una operación basada en texto que el usuario escribe en un vista Entry. La propiedad Text
de cada Entry se inicializa en una cadena vacía, porque la propiedad Text
es null
de forma predeterminada y el enlace de datos no funcionará en ese caso. Si no se ha escrito nada en el Entry, el Button debe deshabilitarse. Cada Button contiene un enlace de datos en su propiedad IsEnabled
. El origen de enlace de datos es la propiedad Length
de la propiedad Text
de la Entry correspondiente. Si esa propiedad Length
no es 0, el convertidor de valores devuelve true
y se habilita el Button:
Nota:
Si sabe que un convertidor de valores solo se usará en los enlaces OneWay
, el método ConvertBack
puede devolver simplemente null
.
El método Convert
mostrado anteriormente supone implícitamente que el argumento value
es de tipo int
y el valor devuelto debe ser de tipo bool
. De forma similar, el método ConvertBack
supone que el argumento value
es de tipo bool
y el valor devuelto es int
. Si no es así, se producirá una excepción en tiempo de ejecución.
Puede escribir los convertidores de valores para que sean más generalizados y acepten diferentes tipos de datos. Los métodos Convert
y ConvertBack
pueden usar los operadores as
o is
con el parámetro value
, o pueden llamar a GetType
en ese parámetro para determinar su tipo y después realizar algo adecuado. El tipo esperado del valor devuelto de cada método viene dado por el parámetro targetType
. A veces, los convertidores de valores se usan con enlaces de datos de diferentes tipos de destino. En este caso, el convertidor de valores puede usar el argumento targetType
para realizar una conversión para el tipo correcto.
Si la conversión que se realiza es diferente para distintas referencias culturales, utilice el parámetro culture
para este propósito.
Propiedades del convertidor de enlaces
Las clases de convertidor de valores pueden tener propiedades y parámetros genéricos. El convertidor de valores siguiente convierte un elemento bool
desde el origen a un objeto de tipo T
para el destino:
public class BoolToObjectConverter<T> : IValueConverter
{
public T TrueObject { get; set; }
public T FalseObject { get; set; }
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);
}
}
En el ejemplo siguiente se muestra cómo se puede usar este convertidor para mostrar el valor de una vista Switch. Aunque es común crear instancias de los convertidores de valores como recursos en un diccionario de recursos, este ejemplo muestra una alternativa: Aquí se crea una instancia de cada convertidor de valores entre etiquetas de elemento de propiedad Binding.Converter
. El x:TypeArguments
indica el argumento genérico, y TrueObject
y FalseObject
se establecen en objetos de ese tipo:
<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.SwitchIndicatorsPage"
Title="Switch Indicators">
<ContentPage.Resources>
<Style TargetType="Label">
<Setter Property="FontSize" Value="18" />
<Setter Property="VerticalOptions" Value="Center" />
</Style>
<Style TargetType="Switch">
<Setter Property="VerticalOptions" Value="Center" />
</Style>
</ContentPage.Resources>
<StackLayout Padding="10, 0">
<StackLayout Orientation="Horizontal"
VerticalOptions="Center">
<Label Text="Subscribe?" />
<Switch x:Name="switch1" />
<Label>
<Label.Text>
<Binding x:DataType="Switch"
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="Center">
<Label Text="Allow popups?" />
<Switch x:Name="switch2" />
<Label>
<Label.Text>
<Binding x:DataType="Switch"
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 x:DataType="Switch"
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="Center">
<Label Text="Learn more?" />
<Switch x:Name="switch3" />
<Label FontSize="18"
VerticalOptions="Center">
<Label.Style>
<Binding x:DataType="Switch"
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>
En este ejemplo, en el último de los tres pares Switch y Label, el argumento genérico se establece en Style y se proporcionan objetos Style completos para los valores de TrueObject
y FalseObject
. Esto ignora el estilo implícito para Label que se establece en el diccionario de recursos, por lo que las propiedades de ese estilo están asignadas explícitamente a la Label. Activar o desactivar el Switch hace que el correspondiente Label refleje el cambio:
Nota:
También es posible usar desencadenantes para implementar cambios similares en la interfaz de usuario en función de otras vistas. Para obtener más información, consulta Desencadenadores.
Parámetros de convertidor de tipos de enlace
La clase Binding
define una propiedad ConverterParameter
y la extensión de marcado Binding
también define una propiedad ConverterParameter
. Si se establece esta propiedad, el valor se pasa a los métodos Convert
y ConvertBack
como el argumento parameter
. Incluso si la instancia del convertidor de valor se comparte entre varios enlaces de datos, el ConverterParameter
puede ser diferente para realizar conversiones algo diferentes.
El uso de la propiedad ConverterParameter
se muestra con un programa de selección de color. En el siguiente ejemplo se muestra el RgbColorViewModel
, que tiene tres propiedades de tipo float
denominadas Red
, Green
y Blue
que usa para construir un valor Color:
public class RgbColorViewModel : INotifyPropertyChanged
{
Color color;
string name;
public event PropertyChangedEventHandler PropertyChanged;
public float Red
{
get { return color.Red; }
set
{
if (color.Red != value)
{
Color = new Color(value, color.Green, color.Blue);
}
}
}
public float Green
{
get { return color.Green; }
set
{
if (color.Green != value)
{
Color = new Color(color.Red, value, color.Blue);
}
}
}
public float Blue
{
get { return color.Blue; }
set
{
if (color.Blue != value)
{
Color = new Color(color.Red, color.Green, value);
}
}
}
public Color Color
{
get { return 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);
}
}
}
public string Name
{
get { return name; }
private set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
}
Los valores de las propiedades Red
, Green
y Blue
oscilan entre 0 y 1. Con todo, es preferible que los componentes se muestren como valores hexadecimales de dos dígitos. Para mostrar estos elementos como valores hexadecimales en XAML, deben multiplicarse por 255, convertirse en un entero y después debe aplicárseles formato con la especificación de "X2" en la propiedad StringFormat
. La multiplicación por 255 y la conversión en un entero pueden controlarse mediante el convertidor de valores. Para hacer el convertidor de valores tan generalizado como sea posible, puede especificarse el factor de multiplicación con la propiedad ConverterParameter
, lo que significa que introduce los métodos Convert
y ConvertBack
como el argumento parameter
:
public class FloatToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)Math.Round((float)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 float)
return (float)parameter;
else if (parameter is int)
return (int)parameter;
else if (parameter is string)
return float.Parse((string)parameter);
return 1;
}
}
En este ejemplo, el método Convert
convierte de float
a int
mientras se multiplica por el valor parameter
. El método ConvertBack
divide el argumento value
entero por parameter
y devuelve un resultado float
.
Es probable que el tipo del argumento parameter
sea diferente en función de si se ha definido el enlace de datos en el código o en XAML. Si la propiedad ConverterParameter
de Binding
se establece en código, es probable que se establezca en un valor numérico:
binding.ConverterParameter = 255;
La propiedad ConverterParameter
es de tipo Object
, por lo que el compilador C# interpreta el literal 255 como un entero y establece la propiedad en ese valor.
Pero en XAML es probable que ConverterParameter
se establezca de este modo:
<Label Text="{Binding Red,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Red = {0:X2}'}" />
El 255 parece un número, pero dado que ConverterParameter
es de tipo Object
, el analizador XAML trata el 255 como una cadena. Por ese motivo, el convertidor de valores incluye un método GetParameter
independiente que controla los casos para parameter
de tipo float
, int
o string
.
En el ejemplo XAML siguiente se crean instancias de FloatToIntConverter
en su diccionario de recursos:
<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.RgbColorSelectorPage"
Title="RGB Color Selector"
x:DataType="local:RgbColorViewModel">
<ContentPage.BindingContext>
<local:RgbColorViewModel Color="Gray" />
</ContentPage.BindingContext>
<ContentPage.Resources>
<Style TargetType="Slider">
<Setter Property="VerticalOptions" Value="Center" />
</Style>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<local:FloatToIntConverter x:Key="floatToInt" />
</ContentPage.Resources>
<StackLayout Margin="20">
<BoxView Color="{Binding Color}"
HeightRequest="100"
WidthRequest="100"
HorizontalOptions="Center" />
<StackLayout Margin="10, 0">
<Label Text="{Binding Name}" />
<Slider Value="{Binding Red}" />
<Label Text="{Binding Red,
Converter={StaticResource floatToInt},
ConverterParameter=255,
StringFormat='Red = {0:X2}'}" />
<Slider Value="{Binding Green}" />
<Label Text="{Binding Green,
Converter={StaticResource floatToInt},
ConverterParameter=255,
StringFormat='Green = {0:X2}'}" />
<Slider Value="{Binding Blue}" />
<Label>
<Label.Text>
<Binding Path="Blue"
StringFormat="Blue = {0:X2}"
Converter="{StaticResource floatToInt}">
<Binding.ConverterParameter>
<x:Single>255</x:Single>
</Binding.ConverterParameter>
</Binding>
</Label.Text>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
Los valores de las propiedades Red
y Green
se muestran con una extensión de marcado Binding
. Empero, la propiedad Blue
crea una instancia de la clase Binding
para demostrar cómo un valor float
explícito puede establecerse en la propiedad ConverterParameter
.