Связывание WPF и динамическое присвоение свойства StringFormat - PullRequest
10 голосов
/ 23 июня 2010

У меня есть форма, созданная на основе нескольких элементов 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 не является свойством зависимости, я не могу с ним связываться.

Моя следующая мысль заключалась в создании преобразователя значений и передаче FormatStringзначение свойства в ConverterParameter, но я столкнулся с той же проблемой - ConverterParameter не является DependencyProperty.

Итак, теперь я перехожу к вам, ТАК.Как мне динамически установить StringFormat привязки;точнее, на TextBox?

Я бы предпочел, чтобы XAML сделал всю работу за меня, чтобы я мог не играть с code-behind.Я использую шаблон MVVM и хотел бы сохранить границы между моделью представления и видом как можно более размытыми.

Спасибо!

Ответы [ 4 ]

2 голосов
/ 26 мая 2014

Этот код (созданный из DefaultValueConverter.cs @ referenceource.microsoft.com ) работает для двухсторонней привязки к TextBox или аналогичному элементу управления, если 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, если свойство источника может иметь значение null.

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 голосов
/ 23 июня 2010

Одним из способов может быть создание класса, который наследует TextBox, и в этом классе создать собственное свойство зависимостей, которое делегируется StringFormat, когда установлено.Поэтому вместо использования TextBox в вашем XAML вы будете использовать унаследованное текстовое поле и установить свое собственное свойство зависимости в привязке.

1 голос
/ 19 февраля 2013

Можно создать присоединенное поведение, которое может заменить привязку на привязку, для которой указана FormatString. Если свойство зависимости FormatString, то привязка будет снова обновлена. Если привязка обновлена, то FormatString будет повторно применен к этой привязке.

Единственные две хитрости, о которых я могу подумать, что тебе придется иметь дело. Одна из проблем заключается в том, хотите ли вы создать два прикрепленных свойства, которые координируются друг с другом для FormatString и TargetProperty, для которого существует привязка, для которой необходимо применить FormatString (например, TextBox.Text), или, возможно, вы можете просто предположить, какое свойство вы имеете дело в зависимости от целевого типа управления. Другая проблема может заключаться в том, что копирование существующей привязки и ее незначительное изменение может быть нетривиальным с учетом различных типов привязок, которые могут также включать настраиваемые привязки.

Важно учитывать, что все это обеспечивает только форматирование в направлении от ваших данных к вашему контролю. Насколько я могу обнаружить, использование чего-то вроде MultiBinding вместе с настраиваемым MultiValueConverter для использования как исходного значения, так и FormatString и получения желаемого результата все еще страдает от той же проблемы, главным образом потому, что метод ConvertBack получает только строку вывода, и вы бы ожидается, что он расшифрует и FormatString, и исходное значение из него, что в этот момент почти всегда невозможно.

Остальные решения, которые должны работать для двунаправленного форматирования и форматирования, будут следующими:

  • Напишите пользовательский элемент управления, расширяющий TextBox, который имеет желаемое поведение форматирования, как предложил Якоб Кристенсен.
  • Напишите пользовательский преобразователь значений, который является производным от DependencyObject или FrameworkElement и имеет FormatString DependencyProperty. Если вы хотите пойти по пути DependencyObject, я полагаю, что вы можете вставить значение в свойство FormatString, используя привязку OneWayToSource с помощью метода «виртуальной ветви». Другой более простой способ - вместо этого унаследовать от FrameworkElement и поместить конвертер значений в визуальное дерево вместе с другими элементами управления, чтобы вы могли просто связываться с ним при необходимости ElementName.
  • Используйте прикрепленное поведение, подобное тому, которое я упомянул в начале этого поста, но вместо установки FormatString вместо этого имейте два вложенных свойства: одно для пользовательского преобразователя значений и одно для параметра, который будет передан преобразователю значений. , Тогда вместо того, чтобы изменять исходную привязку для добавления FormatString, вы добавляете преобразователь и параметр преобразователя в привязку. Лично я думаю, что эта опция привела бы к наиболее читабельному и интуитивно понятному результату, потому что прикрепленное поведение имеет тенденцию быть более чистым, но все же достаточно гибким для использования в различных ситуациях, отличных от TextBox.
1 голос
/ 23 июня 2010

Просто привяжите текстовое поле к экземпляру MyTextBoxClass вместо MyTextBoxClass.Value и используйте преобразователь значения для создания строки из значения и строки formattring.

Другим решением является использование многозначного преобразователя, который будет связывать как Value, так и FormatString.

Первое решение не поддерживает изменения свойств, то есть, если значение или строка формата изменяет преобразователь значения, он не будет вызываться так, как если бы вы использовали многозначный преобразователь и связывались непосредственно со свойствами.

...