我有一个基于多个DataTemplate元素生成的表单。其中一个DataTemplate元素从类中创建一个TextBox:
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;更具体地说,在TextBox上?
我更愿意让XAML为我工作,这样我就可以避免使用代码隐藏。我正在使用MVVM模式,并且希望尽可能不模糊视图模型和视图之间的界限。
谢谢!
答案 0 :(得分:2)
一种方法可能是创建一个继承TextBox
的类,并在该类中创建自己的依赖项属性,该属性在设置时委托给StringFormat
。因此,不要在XAML中使用TextBox
,而是使用继承的文本框并在绑定中设置自己的依赖项属性。
答案 1 :(得分:2)
此代码(灵感来自DefaultValueConverter.cs @ referencesource.microsoft.com)适用于对TextBox或类似控件的双向绑定,只要FormatString将Source属性的ToString()版本保留在可以转换回的状态中。 (例如“#,0.00”之类的格式是可以的,因为“1,234.56”可以被解析回来,但FormatString =“Some Prefix Text#,0.00”将转换为“Some Prefix Text 1,234.56”,无法解析。)< / p>
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>
如果source属性可以为null,请注意重复的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;
}
}
答案 2 :(得分:1)
只需将文本框绑定到MyTextBoxClass的实例而不是MyTextBoxClass.Value,并使用valueconverter从value和formatstring创建一个字符串。
另一个解决方案是使用一个多值转换器,它将绑定到Value和FormatString。
第一个解决方案不支持对属性的更改,即如果value或formatstring更改,则不会调用值转换器,就像使用多值转换器并直接绑定到属性一样。
答案 3 :(得分:1)
可以创建一个附加行为,可以用指定了FormatString的绑定替换绑定。如果FormatString依赖属性然后再次更新绑定。如果绑定已更新,则FormatString将重新应用于该绑定。
我认为你必须处理的两件棘手的事情。一个问题是你是否要为FormatString和TargetProperty创建两个相互协调的附加属性,其中存在应该应用FormatString的绑定(例如TextBox.Text)或者你可以只假设你的交易属性取决于目标控件类型。另一个问题可能是复制现有绑定并稍微修改它可能是非常重要的,因为那里可能还包括自定义绑定的各种类型的绑定。
重要的是要考虑所有这些只能实现从数据到控件的方向格式化。至于我发现使用类似MultiBinding和自定义MultiValueConverter之类的东西来消耗原始值和FormatString并产生所需的输出仍然会遇到同样的问题,主要是因为ConvertBack方法只给出输出字符串而你会期望从它解密FormatString和原始值,在那时几乎总是不可能。
应该用于双向格式化和取消格式化的其余解决方案如下:
答案 4 :(得分:0)
这是来自Andrew Olson的解决方案,它使用附加属性,因此可以在各种情况下使用。
像这样使用:
<TextBlock
local:StringFormatHelper.Format="{Binding FormatString}"
local:StringFormatHelper.Value="{Binding Value}"
Text="{Binding (local:StringFormatHelper.FormattedValue)}"
/>
所需的帮助者:(source Gist)
public static class StringFormatHelper
{
#region Value
public static DependencyProperty ValueProperty = DependencyProperty.RegisterAttached(
"Value", typeof(object), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null, OnValueChanged));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
RefreshFormattedValue(obj);
}
public static object GetValue(DependencyObject obj)
{
return obj.GetValue(ValueProperty);
}
public static void SetValue(DependencyObject obj, object newValue)
{
obj.SetValue(ValueProperty, newValue);
}
#endregion
#region Format
public static DependencyProperty FormatProperty = DependencyProperty.RegisterAttached(
"Format", typeof(string), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null, OnFormatChanged));
private static void OnFormatChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
RefreshFormattedValue(obj);
}
public static string GetFormat(DependencyObject obj)
{
return (string)obj.GetValue(FormatProperty);
}
public static void SetFormat(DependencyObject obj, string newFormat)
{
obj.SetValue(FormatProperty, newFormat);
}
#endregion
#region FormattedValue
public static DependencyProperty FormattedValueProperty = DependencyProperty.RegisterAttached(
"FormattedValue", typeof(string), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null));
public static string GetFormattedValue(DependencyObject obj)
{
return (string)obj.GetValue(FormattedValueProperty);
}
public static void SetFormattedValue(DependencyObject obj, string newFormattedValue)
{
obj.SetValue(FormattedValueProperty, newFormattedValue);
}
#endregion
private static void RefreshFormattedValue(DependencyObject obj)
{
var value = GetValue(obj);
var format = GetFormat(obj);
if (format != null)
{
if (!format.StartsWith("{0:"))
{
format = String.Format("{{0:{0}}}", format);
}
SetFormattedValue(obj, String.Format(format, value));
}
else
{
SetFormattedValue(obj, value == null ? String.Empty : value.ToString());
}
}
}