类型不匹配的数据绑定
有时,所使用的数据与显示数据的控件属性的数据类型不匹配。 例如,你可能有一个货币值存储在要显示在 Label
控件上的 decimal
类型中,其格式为货币。 一个更复杂的示例是模块中提供的天气应用。 图像应根据天气预报的 Sunny
或 Cloudy
枚举值显示,但你无法将源的枚举值绑定到目标的图像属性。 本单元着眼于转换数据的方法。
字符串格式设置
常见的类型不匹配是想要显示为格式字符串的内部类型。 就像要显示 DateTime
值的一部分,或者想要将 decimal
类型格式化为货币时一样。
假设你想要显示账单上的到期金额,并且你的数据对象上有此属性:
public decimal BillAmount { get; set; }
欠款金额最终为 22.0304。 可以使用两个标签控件来显示一些文本和美元金额,如以下代码片段所示:
<HorizontalStackLayout>
<Label Text="You owe" Margin="0,0,5,0" />
<Label Text="{Binding BillAmount}" />
<Label Text="to the bank" Margin="5,0,0,0" />
</HorizontalStackLayout>
这会向 UI 输出一个类似于 You owe 22.0304 to the bank
的字符串,但它缺少货币符号,并且根据本地货币,小数位数可能过多或过少。 通常,你会在代码中使用“C”(或货币)格式说明符将值作为字符串处理,如下所示:
string formattedBillAmount = string.Format("{0:C}", BillAmount);
但是要在数据绑定中使用格式设置,必须让数据对象提供格式化字符串作为不同的属性,或者以某种方式拦截它并自行设置格式。 幸运的是,.NET MAUI 绑定提供了一种使用 StringFormat
绑定属性格式化字符串的方法。 格式字符串遵循与 String.Format
方法相同的规则。 使用单引号将格式字符串括起来,这样 XAML 分析器就不会因为大括号而混淆了。 字符串格式参数 0
是绑定处理的属性值。
<Label Text="{Binding BillAmount, StringFormat='You owe {0:C} to the bank'}" />
请考虑以下 XAML,它演示了显示 BillAmount
的两种方式:
<VerticalStackLayout Padding="10">
<HorizontalStackLayout>
<Label Text="You owe" Margin="0,0,5,0" />
<Label Text="{Binding BillAmount}" />
<Label Text="to the bank" Margin="5,0,0,0" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="{Binding BillAmount, StringFormat='You owe {0:C} to the bank'}" />
</HorizontalStackLayout>
</VerticalStackLayout>
下图说明了 XAML 输出在屏幕上生成的内容:
使用 StringFormat
绑定属性的 XAML 比其他 XAML 更简单,并且你有权访问 .NET 强大的字符串格式化系统。
自定义类型转换
当将值显示为字符串时,StringFormat
绑定属性很方便,但当你想要从其他类型转换 Color
或 Image
等内容时,则不太方便。 在这些情况下,需要编写自定义转换代码。
假设你提示用户选择密码,并且希望在 UI 中使用颜色来指示密码的强度。 强度分为三个等级:弱、良、强。 强度取决于密码中包含的字符数。 为了向用户提供有关其密码强度的即时反馈,需要包含密码的 Entry
控件的背景根据强度进行更改。 下图展示了这三个强度级别:弱、良和强。
在屏幕截图中的三个输入控件中,第一个输入了四个字符并具有红色背景。 第二个输入了九个字符,并具有黄色背景。 最后一个输入控件有 15 个字符,具有蓝色背景。
这些级别分配给 Strength
枚举:
private enum Strength
{
Weak,
Good,
Strong
}
数据对象被分配为页面的 BindingContext
,其中包含带有 PasswordStrength
属性的密码强度。 当用户输入密码时,PasswordStrength
属性会根据密码的长度进行更改。 由于数据对象包含 PasswordStrength
属性,因此将该属性绑定到 Entry
控件的 BackgroundColor
:
<Entry BackgroundColor="{Binding PasswordStrength} ... />
但这里有一个问题。 PasswordStrength
的类型为 Strength
,而 BackgroundColor
的类型为 Color
。 这些类型彼此不兼容。 .NET MAUI 提供了一种解决这些类型不匹配问题的方法,即绑定的 Converter
属性。
绑定转换器,顾名思义,就是在绑定源和目标之间进行转换。 转换器通过 IValueConverter
接口实施。
实现 IValueConverter
你将转换逻辑创建到实施 IValueConverter
接口的类中。 通常,这些类的名称以名称 Converter
结尾,以明确其用途。
IValueConverter
接口定义两种方法:
Convert
- 从绑定源的属性转换为绑定目标的 UI 属性。ConvertBack
- 从绑定目标的 UI 属性转换回绑定源的属性。这种方法很少使用,在这种情况下也不会使用。 大多数转换器会抛出
NotSupportedException
来指示不支持此转换。
下面是接口的协定:
public interface IValueConverter
{
object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture);
object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture);
}
回想一下,我们正在使用的场景是将 Entry.BackgroundColor
属性绑定到数据对象的 PasswordStrength
属性,该属性是一个 Strength
枚举。
<Entry BackgroundColor="{Binding PasswordStrength} ... />
Strength
枚举需要转换为 Color
。 因此,转换器需要评估提供的 Strength
值,并返回不同的 Color
。 下面的代码演示了这种转换:
namespace MyProject.Converters;
class StrengthToColorConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return (Strength)value! switch
{
Strength.Weak => Colors.OrangeRed,
Strength.Good => Colors.Yellow,
Strength.Strong => Colors.LightBlue,
_ => Colors.LightBlue
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
throw new NotImplementedException();
}
让我们分解一下这段代码:
Convert
方法有四个参数。 通常可以放弃最后两个参数,除非出于特定原因需要使用它们。value
参数包含传入值。 在此示例中,它是一个Strength
枚举值。- 忽略
targetType
参数。 但是,可以使用此参数来验证转换器所使用的属性类型是否为Color
。 为了简单起见,本示例中省略了这一点。 - switch 表达式用于根据
Strength
值返回不同的颜色。
在 XAML 中使用转换器
创建转换器类后,需要创建它的实例并在绑定中引用它。 实例化转换器的标准方法是在根元素的资源字典中。
首先,将 XML 命名空间映射到包含转换器的 .NET 命名空间。 在以下代码示例中,cvt
XML 命名空间映射到 MyProject.Converters
.NET 命名空间:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cvt="clr-namespace:MyProject.Converters"
...
接下来,在 ContentPage.Resources
中创建一个实例:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cvt="clr-namespace:MyProject.Converters"
x:Class="MyProject.PasswordExample">
<ContentPage.Resources>
<cvt:StrengthToColorConverter x:Key="StrengthToColorConverter" />
</ContentPage.Resources>
现在,转换器的实例位于资源字典中,其密钥为 StrengthToColorConverter
,恰好与类型同名。 这是命名转换器的典型方式,因为你通常只有一个在整个 XAML 中重复使用的转换器。 如果出于某种原因需要多个转换器实例,则它们之间的密钥必须不同。
最后,在绑定时引用转换器。 由于转换器位于资源字典中,因此可以使用 {StaticResource}
标记扩展来引用它。 该转换器被分配给 Converter
绑定属性:
<Entry BackgroundColor="{Binding PasswordStrength, Converter={StaticResource StrengthToColorConverter}}" ... />