2010-06-23 2 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 не является свойством зависимостей, я не могу связать с ним.

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

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

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

Спасибо!

ответ

2

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

+1

Это хорошее предложение. Я должен посмотреть на это. Я как бы надеялся, что будет решение, которое не связано с пользовательскими элементами управления, но я, безусловно, открыт для него. Я вернусь после небольшого исследования. –

+0

Я пытаюсь сделать то же самое, но я не уверен, как настроить прикрепленные свойства, чтобы справиться с этим. Я разместил новый вопрос: http://stackoverflow.com/q/24119097/65461 –

1

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

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

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

+0

Привязка к экземпляру MyTextBoxClass - это то, что я пробовал, но метод ConvertBack в ValueConverter будет проблемой, поскольку существует много свойств что у меня нет места для объекта TextBox. Таким образом, я получаю неполный объект, возвращающийся из TextBox. Я рассмотрю преобразователь с несколькими значениями. Однако FormatString не является связующим, поскольку он является свойством зависимости, поэтому я не уверен, что это сработает. –

+0

Как это должно работать? Когда TextBox обновляется с использованием привязки данных, текст форматируется с помощью FormatString. Когда пользователь обновляет текстовое поле, он может вводить любой текст, который может быть несовместим с форматированием FormatString. Это нормально? Вы уверены, что не хотите использовать маскированное текстовое поле? Кроме того, FormatString так же связывается, как и любое другое публичное свойство. –

+0

«FormatString так же связывается, как и любое другое публичное свойство», объясните, почему тогда вы получите сообщение об ошибке, которое говорит о том, что «привязка» не может быть установлена ​​в свойстве «StringFormat» типа «Binding». установленный в DependencyProperty объекта DependencyObject. " – jpierson

1

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

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

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

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

  • Написать пользовательский элемент управления, который расширяет TextBox что желаемое поведение форматирования, как Jakob Christensen предложили.
  • Напишите собственный преобразователь значений, который происходит из DependencyObject или FrameworkElement и имеет свойство FormatString DependencyProperty. Если вы хотите перейти на путь DependencyObject, я считаю, что вы можете нажать значение в свойство FormatString, используя привязку OneWayToSource с помощью технологии «виртуальной ветки». Другой простой способ может вместо этого наследовать от FrameworkElement и поместить ваш конвертер значений в визуальное дерево вместе с вашими другими элементами управления, чтобы вы могли просто привязываться к нему, когда это необходимо ElementName.
  • Используйте прикрепленное поведение, аналогичное тому, которое я упомянул в начале этого сообщения, но вместо того, чтобы вместо параметра FormatString иметь два прикрепленных свойства, один для настраиваемого преобразователя значений и один для параметра, который будет передан преобразователю значений , Затем вместо изменения оригинальной привязки для добавления FormatString вы добавляете конвертер и параметр преобразователя в привязку. Лично я думаю, что этот вариант приведет к наиболее читаемому и интуитивно понятному результату, потому что прикрепленные поведения, как правило, более чистые, но все же достаточно гибкие, чтобы использовать их в самых разных ситуациях, кроме как только TextBox.
2

Этот код (вдохновленный от DefaultValueConverter.cs @ referencesource.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 если свойство источника может быть пустым.

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; 
    } 
} 
Смежные вопросы