65.9K
CodeProject 正在变化。 阅读更多。
Home

可绑定的转换器、转换器参数和 StringFormat

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (12投票s)

2012年9月17日

CPOL

5分钟阅读

viewsIcon

80232

downloadIcon

1601

如何绕过 Binding 的 Converter、ConvertParameter 和 StringFormat 无法指定为动态 Bindings 的限制。

引言

在本文中,我们开发了克服 WPF 和 Silverlight 5 中 ConverterParameter 无法指定为动态 Binding 的限制的方法。这些解决方案是对文章 Bindingable Converter Parameter 以及其他博客和论坛中描述的解决方案的替代方案。本文提出的解决方案的特点是:

  • 辅助代码不多
  • 现有实现 IValueConverter 的转换器无需修改即可使用
  • 在数据模板中有效
  • 支持 RelativeSource  
  • 每个元素每种可绑定 ConvertParameter 的绑定数量没有限制。
  • 支持双向绑定
  • 不仅支持绑定到 ConverterParameter,也支持绑定到 Converter
  • 支持绑定到 StringFormat 2.0 版新增
  • Silverlight 5 支持(请参阅最后一节)

背景

对于 WPF 和 Windows 中的 Binding,可以使用转换器来自定义外观。为了将额外信息传递给转换器,可以使用 ConverterParameter。但是,ConverterConverterParameter 都不能指定为 Binding,许多开发人员认为这是一个限制。一些博客和论坛主题建议创建一个派生自 DependencyObject 的转换器。然后,转换器本身可以具有在 XAML 中分配给绑定的依赖属性。但是,这在动态上下文和数据模板中不起作用。

正如在一些地方指出的,MultiBinding 在许多情况下可能是一个不错的选择。在本文中,我们将从这个角度入手,开发一些辅助工具使其更加容易。

步骤 1:在 WPF 中使用 MultiBinding

正如在一些博客和论坛中所述,我们应该首先考虑简单地使用 MultiBinding,将转换器参数绑定指定为第二个绑定,如下所示:

<TextBox>
  <TextBox.Text>
     <MultiBinding Converter="{StaticResource WrappedTestValueConverter}" >
        <Binding Path="FirstName" />
        <Binding Path="ConcatSign" />
     </MultiBinding>
  </TextBox.Text>
</TextBox>

在这种情况下,我们还必须更改 Converter 以实现 IMultiValueConverter 接口。最简单的方法是将 IMultiValueConverter 实现添加到原始转换器中,如下所示:

#region IMultiValueConverter Members

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
  return Convert(values[0], targetType, values[1], culture);
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
   return new object[] { ConvertBack(value, targetTypes[0], parameter, culture) };
}

#endregion

上面的代码调用原始 IValueConverterConvertConvertBack 方法。

如果您不想或无法更改原始转换器,可以使用通用的“适配器”IMultiValueConverter,它只需将转换委托给原始 IValueConverter 实现。下面是一个该适配器的通用实现:

[ContentProperty("Converter")]
public class MultiValueConverterAdapter : IMultiValueConverter
{
    public IValueConverter Converter { get; set; }

    private object lastParameter;

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (Converter == null) return values[0]; // Required for VS design-time
        if (values.Length > 1) lastParameter = values[1]; 
        return Converter.Convert(values[0], targetType, lastParameter, culture);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        if (Converter == null) return new object[] { value }; // Required for VS design-time

        return new object[] { Converter.ConvertBack(value, targetTypes[0], lastParameter, culture) };
    }
}

要使用适配器,您可以在资源部分简单地将原始 IValueConverter 实例声明包装起来,如下所示:

<local:MultiValueConverterAdapter x:Key="WrappedTestValueConverter">
 <local:TestValueConverter />
</local:MultiValueConverterAdapter>

通过此解决方案,我们无需在 XAML 代码中使用任何自定义绑定,只需标准的 MultiBinding,它也可以在模板和样式中使用。所有必需的更改都在转换器端完成。使用通用适配器,我们根本不会修改原始转换器。

此方法的一个限制是它不能完全支持双向绑定。问题在于作为第二个值传递给 Convert 方法的参数在调用 ConvertBack 时将不可用。如果参数未在反向转换中使用,这不会成为问题。为了部分规避此问题,可以存储 Convert 中的参数并在 ConvertBack 中重用,如上面 MultiValueConverterAdapter 实现所示。但是,这在同一转换器实例用于多个 XAML 元素的情况下不起作用,例如在数据模板化的列表中。有关解决方案,请继续阅读。

步骤 2:一个新的标记扩展

为了解决共享转换器实例在双向场景中导致的问题,我们可以创建一个新的简单标记扩展。这样,我们还可以获得更易于理解的 XAML 代码,并且 XAML 作者不必像上面那样包装值转换器资源声明。

自定义标记扩展的完整实现如下所示:

public class ConverterBindableBinding : MarkupExtension
{
  public Binding Binding { get; set; }
  public IValueConverter Converter { get; set; }
  public Binding ConverterParameterBinding { get; set; }

  public override object ProvideValue(IServiceProvider serviceProvider)
  {
     MultiBinding multiBinding = new MultiBinding();
     multiBinding.Bindings.Add(Binding);
     multiBinding.Bindings.Add(ConverterParameterBinding);
     MultiValueConverterAdapter adapter = new MultiValueConverterAdapter();
     adapter.Converter = Converter;
     multiBinding.Converter = adapter; 
     return multiBinding.ProvideValue(serviceProvider);
   }
}

对于标记扩展,每当要将扩展应用于 XAML 元素时,都会调用 ProvideValue。在此方法中,我们所做的只是以编程方式创建一个新的 MultiBinding,将原始绑定和转换器参数作为子绑定,并创建一个新的、干净的 MultiValueConverterAdapter 实例。您会看到这与我们上面在 XAML 中所做的非常相似,但不同之处在于,每次使用绑定时,我们都会获得适配器的新实例。

现在,我们可以用这个标记扩展替换上面的 MultiBinding XAML 代码,如下所示:

<TextBox>
  <TextBox.Text>
    <local:ConverterBindableBinding Binding="{Binding FirstName}"
                                       Converter="{StaticResource TestValueConverter}" 
                                       ConverterParameterBinding="{Binding ConcatSign}" />
  </TextBox.Text>
</TextBox>

您可以看到,我们可以使用原始的 TestValueConverter,而无需对其源代码或资源定义进行任何更改。此外,通过此解决方案,与上面的 MultiBinding 解决方案相比,更容易理解 ConcatSign 绑定用作 ConverterParameter 的源。因此,即使您没有双向绑定问题,您很可能也想使用此解决方案。

在使用此标记扩展的 Style 上下文中仍然存在一个限制,因为在这种情况下,将在应用样式的所有位置使用在 ProvideValue 方法中创建的 MultiBindingMultiValueConverterAdapter 的相同实例。因此,如果您需要使用 ConvertBack 实现中的转换器参数的双向绑定,请不要在样式中使用它。

可绑定的转换器

上述解决方案现在可以轻松扩展,以支持绑定到 Converter,而不仅仅是 ConverterParameter。我们只需在 ConverterBindableBinding 中添加一个新的属性 ConverterBinding,并将其作为第三个绑定传递给 MultiConverterValueAdapter。有关详细信息,请参阅提交的源代码。

支持 StringFormat(2.0 版新增)

在新版本(2.0)中,我还添加了对 StringFormat 的支持。您甚至可以使用 StringFormatBinding 属性动态设置字符串格式。为此,我创建了一个新的 BindableBinding 类,其用法如下:

<TextBox>

  <TextBox.Text>

     <local:BindableBinding Path="FirstName"

                            Converter="{StaticResource TestValueConverter}"

                            ConverterParameterBinding="{Binding ConcatSign}"

                            StringFormatBinding="{Binding CurrentStringFormat}"/>

   </TextBox.Text>

</TextBox>

Silverlight 5 解决方案

上述解决方案适用于 WPF。对于 Silverlight 5,您可以使用我在文章“Silverlight 5 中的 MultiBinding”中提出的解决方案。这个 MultiBinding 实现开箱即用地支持可绑定的 ConverterParameterConverter。与 WPF 的 MultiBinding 不同,它还直接支持 IValueConverter 的使用,因此不需要适配器转换器。在这种情况下,XAML 代码将如下所示:

<TextBox Text="{z:MultiBinding Converter={StaticResource TestValueConverter} 
               ConverterParameter={Binding ConcatSign} Binding1={Binding FirstName} }" /> 

此 Silverlight 解决方案也可以在 Style 中使用,而不会在进行双向绑定时出现共享资源的麻烦。

历史

  • 2012 年 9 月 17 日 - 提交第一个版本。
  • 2014 年 9 月 19 日 - 提交 2.0 版,支持 StringFormat。
© . All rights reserved.