目录

IValueConverter 和 IMultiValueConverter

目录

在WPF中,数据绑定是非常核心的概念,有时候 View 需要的数据格式和 ViewModel 中的数据格式不一致,此时便需要转换器(Converters)。

IValueConverter(单值转换器)

接口定义:

public interface IValueConverter
{
   object Convert(object value, Type targetType, object parameter, CultureInfo culture);
   object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}

此接口包含了两个方法:

  1. Convert:从源(ViewModel) –> 目标(View)。
  2. Convert:从目标(View) –> 源(ViewModel) (仅在双向绑定 Mode=TwoWay 且需要回写数据时使用,通常不需要实现)

参数—可以把转换器想象为“加工厂”,这四个参数就是加工时参考的说明书:

1.object value—待处理数据(或者说绑定源生成的值):

  • 含义:这是来自绑定的原始数据源。
  • 用途:最常用的参数,在下面的示例中,它就是 CheckBox 传过来的 bool 值。
  • 注意:此参数为 object 类型,所以在使用前通常需要进行类型转换(如(bool)value)

2.Type targetType—-目标类型(绑定目标属性的类型):

  • 含义:转换后的数据最终要赋给哪个属性的类型。
  • 用途:它可以让你的转换器更”聪明“。比如,同一个转化器,如果发现 targetType 是 Brush,它就返回颜色;如果是string,它就返回文字。
  • 场景:编写能够自动适应多种属性的通用转换器。

3.object parameter—自定义参数(要使用的转换器参数)

  • 含义:从 XAML 手动传进来的静态额外信息。
  • 用途:避免为每种微笑差异来写一个新的类。
  • 示例:在 XAML 中写 ConverterParameter=‘男|女’。在代码中:通过 parameter.ToString() 获取这个字符串,从而动态决定返回"男"还是"女"。

4.CultureInfo culture—语言/文化环境(要用在转换器中的区域性)

  • 含义:当前系统的区域语言设置(如中文 zh-CN或英文 en-US)。
  • 用途:处理与语言相关的转换,比如日期格式、货币符号。
  • 场景:如果软件需要做多语言国际化,这个参数能决定显示”Yes“还是”是“。

**常用用途:**用于将单个源数据转换为单个目标数据。

场景举例:

  • Boolean 转 Visibility :后台是 bool IsLoading,前台需要控制控件 Visibility=“Visible"或Collapsed。
  • 数值转颜色:库存数量 < 10 显示红色,否则显示绿色。
  • 文件路径转图片对象:后台是字符串路径,前台是 ImageSource。

demo—布尔值转中文"是/否”:

public class BoolToStringConverter : IValueConverter
{
    // 在此demo中:布尔值 --> 字符串(用于显示)
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool b) 
        {
            return b ? "是" : "否";
        }
        return "未知";
    }

    // 在此demo中:字符串 --> 布尔值(如果需要双向绑定)
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value?.ToString() == "是";
    }
}
<Window x:Class="demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="300">

    <Window.Resources>
        <local:BoolToStringConverter x:Key="BoolToStrConverter"/>
    </Window.Resources>
    
    <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
        <StackPanel>
            <GroupBox Header="输入层" Padding="10">
                <CheckBox x:Name="MyCheckBox" Content="是否激活状态?" IsChecked="True"/>
            </GroupBox>

            <GroupBox Header="展示转换层" Padding="10">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="当前状态显示为: "/>
                    <TextBlock Text="{Binding IsChecked, ElementName=MyCheckBox, Converter={StaticResource BoolToStrConverter}}"
                               FontWeight="Bold"
                               Foreground="DeepSkyBlue"/>
                </StackPanel>
            </GroupBox>
        </StackPanel>
    </Grid>
</Window>

IMultiValueConverter(多值转换器)

接口定义:

public interface IMultiValueConverter
{
   object Convert(object[] values, Type targetType, object parameter, CultureInfo culture);
   object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture);
}

此接口也包含两个方法,但参数略有不同:

  1. Convert:输入参数是 object[] values (数组),返回一个对象。
  2. ConvertBack:返回 object[] (将一个值拆分成多个值,极少使用)。

**用途:**用于将多个数据源合并/计算后,转换为单个目标数据。必须配合 Multibinding 使用。

场景举例:

  • 姓名合成:后台有 FirstName 和 LastName,前台显示全名。
  • RGB 调色板:后台有R、G、B 三个数值,前台显示一个 SolidColorBrush 颜色。
  • 按钮启用状态:只有当”用户名不为空“且”密码长度大于6“且”勾选了协议”时,登录按钮才可用(IsEnabled=True)

demo:

public class LoginEnableConverter : IMultiValueConverter
{
    // 这里 values 数组顺序对应 XAML 中 Binding 的顺序
    // values[0]: 用户名(string)
    // values[1]:密码长度(int)
    // values[2]:是否勾选协议(bool)
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        // 安全检查:确保所有值都不是 UnsetValue
        if (values.Any(v => v == System.Windows.DependencyProperty.UnsetValue || v == null))
        {
            return false;
        }

        string username = values[0].ToString();
        int passwordLength = 0;
        int.TryParse(values[1]?.ToString(), out passwordLength);
        bool isAgreed = (bool)values[2];

        // 执行业务逻辑判断
        bool isUsernameValid = !string.IsNullOrWhiteSpace(username);
        bool isPasswordValid = passwordLength > 6;
        bool isAgreementValid = isAgreed;

        // 同时满足三个条件,才返回 true
        return isUsernameValid && isPasswordValid && isAgreementValid;
    }

    // 多值转换通常不需要 ConvertBack
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
<Window
    x:Class="demo2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:demo2"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="400"
    Height="300"
    mc:Ignorable="d">

    <Window.Resources>
        <local:LoginEnableConverter x:Key="LoginEnableConverter" />
    </Window.Resources>

    <Grid Margin="30">
        <StackPanel>
            <TextBlock Text="用户名:" />
            <TextBox x:Name="TxtUser" />

            <TextBlock Text="密码(需大于6位):" />
            <PasswordBox x:Name="TxtPwd" PasswordChanged="TxtPwd_PasswordChanged" />
            <TextBlock
                x:Name="TxtPwdLen"
                Text="{Binding Tag, ElementName=TxtPwd}"
                Visibility="Collapsed" />

            <CheckBox x:Name="ChkAgree" Content="我已阅读并同意用户协议" />

            <Button
                Height="40"
                Background="LightSkyBlue"
                Content="立即登录">
                <Button.IsEnabled>
                    <MultiBinding Converter="{StaticResource LoginEnableConverter}">
                        <Binding
                            ElementName="TxtUser"
                            Path="Text"
                            UpdateSourceTrigger="PropertyChanged" />
                        <Binding ElementName="TxtPwdLen" Path="Text" />
                        <Binding ElementName="ChkAgree" Path="IsChecked" />
                    </MultiBinding>
                </Button.IsEnabled>
            </Button>

            <TextBlock
                HorizontalAlignment="Center"
                FontSize="10"
                Foreground="Gray"
                Text="提示:请完善信息以启用按钮" />
        </StackPanel>
    </Grid>
</Window>
 private void TxtPwd_PasswordChanged(object sender, RoutedEventArgs e)
 {
     var pb = sender as PasswordBox;
     TxtPwdLen.Text = pb.Password.Length.ToString();
 }

进阶技巧与注意事项

  1. ConvertBack 的处理

    • 大多数情况下,转换器只用于数据显示(OneWay),不需要回写。此时 ConvertBack可以直接抛出 throw new NotImplementedException(); 或者返回 Binding.DoNothing
  2. ConverterParameter (参数)

    • 两个接口的 Convert 方法都有 object parameter 参数。
    • 这允许你传入一个辅助参数。例如,你可以写一个通用的 “数值比较转换器”,在 XAML 中传入参数 “100”,逻辑变成 “如果值 > 100 返回 True”。
    • XAML 写法:ConverterParameter=100
  3. 处理异常值

    • Convert 方法中,接收到的 value 可能是 DependencyProperty.UnsetValue(比如绑定还没初始化完成)。
    • 最好加上 if (value == DependencyProperty.UnsetValue) return ... 的判断,或者使用 is 模式匹配来确保类型安全,防止程序崩溃。
  4. 调试

    • 如果绑定没生效,可以在 Output 窗口看绑定错误。
    • 或者在 Converter 内部打断点,看看 value 到底传进来了什么。

总结

  • 只有一个数据源要变个样显示?用 IValueConverter
  • 需要把好几个数据凑在一起决定一个结果?用 IMultiValueConverter