2010-06-23 55 views
9

我有一个基于几个DataTemplate元素生成的窗体。其中一个DataTemplate中的元素创建一个TextBox列一类,看起来像这样的:WPF绑定和动态分配StringFormat属性

public class MyTextBoxClass 
{ 
    public object Value { get;set;} 
    //other properties left out for brevity's sake 
    public string FormatString { get;set;} 
} 

我需要一种方法来“绑定”在FormatString属性的绑定的“的StringFormat”属性的值。到目前为止,我有:

<DataTemplate DataType="{x:Type vm:MyTextBoxClass}"> 
<TextBox Text="{Binding Path=Value, StringFormat={Binding Path=FormatString}" /> 
</DataTemplate> 

然而,由于的StringFormat不是依赖属性,我不能绑定到它。

我接下来的想法是创建一个值转换器,并在ConverterParameter中传递FormatString属性的值,但我遇到了同样的问题 - ConverterParameter不是DependencyProperty。

所以,现在我转向你,所以。我如何动态设置绑定的StringFormat;更具体地说,在文本框?

我宁愿让XAML为我做这些工作,这样我就可以避免使用代码隐藏。我正在使用MVVM模式,并希望保持视图模型和视图之间的界限尽可能不模糊。

谢谢!

回答

2

一种方法可能是创建一个继承TextBox的类,并在该类中创建自己的依赖项属性,该属性在设置时委派给StringFormat。因此,不要在XAML中使用TextBox,而是使用继承的文本框并在绑定中设置您自己的依赖项属性。

+1

这是一个很好的建议。我得看看这个。我很希望有一个解决方案不涉及自定义控件,但我肯定对它开放。我会在一点研究后再回来看看。 – 2010-06-24 15:32:39

+0

我试图做同样的事情,但我不知道如何设置附加的属性来处理这个问题。我发布了一个新问题:http://stackoverflow.com/q/24119097/65461 – 2014-06-09 11:40:07

1

只需将文本框绑定到MyTextBoxClass的实例而不是MyTextBoxClass.Value,然后使用valueconverter从value和formatstring中创建一个字符串。

另一种解决方案是使用这将结合价值和formatString的多值转换器。

第一种解决方案不支持对属性进行更改,即如果value或formatstring发生更改,则不会像调用多值转换器并直接绑定属性那样调用值转换器。

+0

绑定到MyTextBoxClass实例是我尝试过的,但ValueConverter中的ConvertBack方法将成为一个问题,因为有许多很多属性我没有一个TextBox对象的地方。所以,我会从TextBox返回一个不完整的对象。 我会看看多值转换器。但是,FormatString不是可绑定的,因为它是一个依赖属性,所以我不确定这会起作用。 – 2010-06-24 15:24:21

+0

这应该如何工作?当使用数据绑定更新文本框时,使用FormatString格式化文本。当用户更新文本框时,他可以输入任何可能与FormatString格式不一致的文本。这可以吗?你确定你不想使用蒙面文本框吗?此外,FormatString与其他公共属性一样可绑定。 – 2010-06-25 13:24:29

+0

“FormatString与任何其他公共属性一样可绑定”解释为什么然后你会得到一个错误,说“a'Binding'不能在'Binding'类型的'StringFormat'属性上设置。'绑定'只能是设置在DependencyObject的DependencyProperty上。“ – jpierson 2013-02-19 11:07:53

1

可以创建一个附加的行为,该行为可以用具有指定的FormatString的绑定替换该绑定。如果FormatString依赖项属性,那么绑定将再次被更新。如果绑定已更新,则FormatString将重新应用于该绑定。

我可以认为你将不得不处理的唯一两件棘手的事情。一个问题是,是否要为FormatString创建两个相互配合的属性,并且需要应用FormatString的绑定(例如TextBox.Text)的TargetProperty(或者您可以假设您的交易属性取决于目标控制类型。另一个问题可能是复制现有绑定并稍微修改它,因为可能还包括自定义绑定的各种绑定类型。

重要的是要考虑到所有这些只能实现从数据到控件的格式化。至于我可以发现使用MultiBinding和自定义MultiValueConverter同时使用原始值和FormatString并生成期望的输出仍然遭受同样的问题,主要是因为ConvertBack方法只给出了输出字符串,你会预计将破译FormatString和它的原始值,而这在当时几乎是不可能的。

其余的解决方案,应该双向格式和非格式的工作将是以下几点:

  • 写扩展文本框已经像雅各布克里斯滕森所需的格式行为提出的自定义控件。
  • 编写一个从DependencyObject或FrameworkElement派生的自定义值转换器,并在其上具有FormatString DependencyProperty。如果你想去DependencyObject路由,我相信你可以使用OneWayToSource绑定和“虚拟分支”技术将值推送到FormatString属性中。另一种更简单的方法可能是继承FrameworkElement,并将值转换器与其他控件一起放入可视化树中,以便在ElementName需要时将其绑定到可视树。
  • 使用附加的行为类似于我在本文顶部提到的行为,但不是设置FormatString而是设置两个附加属性,一个用于自定义值转换器,另一个用于传递给值转换器的参数。然后,不是修改原始绑定来添加FormatString,而是将转换器和转换器参数添加到绑定中。就我个人而言,我认为这个选项会产生最可读和直观的结果,因为附加的行为往往更加干净,但仍然足够灵活,可用于各种场合,而不仅仅是一个TextBox。
2

此代码(来自DefaultValueConverter.cs @ referencesource.microsoft.com启发)适用于双向绑定到文本框或类似的控制,只要于formatString离开源属性的的ToString()的版本中,可以被转换回的状态。 (也就是说像“#,0.00”这样的格式是可以的,因为“1,234.56”可以被解析回来,但是FormatString =“Some Prefix Text#,0.00”将会转换为无法解析的“Some Prefix Text 1,234.56”。)

XAML:

<TextBox> 
    <TextBox.Text> 
     <MultiBinding Converter="{StaticResource ToStringFormatConverter}" 
       ValidatesOnDataErrors="True" NotifyOnValidationError="True" TargetNullValue=""> 
      <Binding Path="Property" TargetNullValue="" /> 
      <Binding Path="PropertyStringFormat" Mode="OneWay" /> 
     </MultiBinding> 
    </TextBox.Text> 
</TextBox> 

注意复制TargetNullValue如果源属性可以为空。

C#:

/// <summary> 
/// Allow a binding where the StringFormat is also bound to a property (and can vary). 
/// </summary> 
public class ToStringFormatConverter : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (values.Length == 1) 
      return System.Convert.ChangeType(values[0], targetType, culture); 
     if (values.Length >= 2 && values[0] is IFormattable) 
      return (values[0] as IFormattable).ToString((string)values[1], culture); 
     return null; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    { 
     var targetType = targetTypes[0]; 
     var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType); 
     if (nullableUnderlyingType != null) { 
      if (value == null) 
       return new[] { (object)null }; 
      targetType = nullableUnderlyingType; 
     } 
     try { 
      object parsedValue = ToStringFormatConverter.TryParse(value, targetType, culture); 
      return parsedValue != DependencyProperty.UnsetValue 
       ? new[] { parsedValue } 
       : new[] { System.Convert.ChangeType(value, targetType, culture) }; 
     } catch { 
      return null; 
     } 
    } 

    // Some types have Parse methods that are more successful than their type converters at converting strings 
    private static object TryParse(object value, Type targetType, CultureInfo culture) 
    { 
     object result = DependencyProperty.UnsetValue; 
     string stringValue = value as string; 

     if (stringValue != null) { 
      try { 
       MethodInfo mi; 
       if (culture != null 
        && (mi = targetType.GetMethod("Parse", 
         BindingFlags.Public | BindingFlags.Static, null, 
         new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider) }, null)) 
        != null) { 
        result = mi.Invoke(null, new object[] { stringValue, NumberStyles.Any, culture }); 
       } 
       else if (culture != null 
        && (mi = targetType.GetMethod("Parse", 
         BindingFlags.Public | BindingFlags.Static, null, 
         new[] { typeof(string), typeof(IFormatProvider) }, null)) 
        != null) { 
        result = mi.Invoke(null, new object[] { stringValue, culture }); 
       } 
       else if ((mi = targetType.GetMethod("Parse", 
         BindingFlags.Public | BindingFlags.Static, null, 
         new[] { typeof(string) }, null)) 
        != null) { 
        result = mi.Invoke(null, new object[] { stringValue }); 
       } 
      } catch (TargetInvocationException) { 
      } 
     } 

     return result; 
    } 
}