2010-06-23 57 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; 
    } 
}