2010-06-23 10 views
9

Tengo un formulario que se genera en base a varios elementos DataTemplate. Uno de los elementos DataTemplate crea un cuadro de texto de una clase que tiene este aspecto:Encuadernación WPF y asignación dinámica de la propiedad StringFormat

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

Necesito una manera de "atar" el valor de la propiedad FormatString a la propiedad "StringFormat" de la unión. Hasta ahora tengo:

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

Sin embargo, desde StringFormat no es una propiedad de dependencia, no puedo obligar a la misma.

Mi siguiente pensamiento fue crear un convertidor de valor y pasar el valor de la propiedad FormatString en el ConvertidorParámetro, pero me encontré con el mismo problema: ConverterParameter no es una DependencyProperty.

Entonces, ahora me dirijo a ti, ASÍ. ¿Cómo configuro dinámicamente el StringFormat de un enlace? más específicamente, en un TextBox?

Preferiría dejar que XAML hiciera el trabajo por mí, así puedo evitar jugar con el código subyacente. Estoy usando el patrón MVVM y me gustaría mantener los límites entre view-model y view lo menos borrosos posible.

Gracias!

Respuesta

2

Una forma puede ser crear una clase que herede TextBox y en esa clase crear su propia propiedad de dependencia que delegue en StringFormat cuando se establezca. Por lo tanto, en lugar de usar TextBox en su XAML, usará el cuadro de texto heredado y establecerá su propia propiedad de dependencia en el enlace.

+1

Esa es una buena sugerencia. Tendré que investigar esto. Esperaba que hubiera una solución que no implicara controles personalizados, pero ciertamente estoy abierto a eso. Voy a volver después de un poco de investigación. –

+0

Estoy tratando de hacer lo mismo, pero no estoy seguro de cómo configurar las propiedades adjuntas para manejar esto. Publiqué una nueva pregunta: http://stackoverflow.com/q/24119097/65461 –

1

Simplemente enlace el cuadro de texto a la instancia de MyTextBoxClass en lugar de MyTextBoxClass.Value y use un convertidor de valor para crear una cadena a partir del valor y de la cadena de formato.

Otra solución es utilizar un convertidor de valores múltiples que se unen a ambas Valor y FormatString.

La primera solución no admite cambios en las propiedades, es decir, si el valor o la cadena de formato cambia, el convertidor de valor no se invocará como lo haría si está utilizando un convertidor multivalor y vincula directamente a las propiedades.

+0

La vinculación a la instancia MyTextBoxClass es algo que probé, pero el método ConvertBack en el ValueConverter va a ser un problema ya que hay muchas, muchas propiedades que no tengo lugar para un objeto TextBox. Entonces, obtendría un objeto incompleto que volviera del TextBox. Voy a ver el convertidor de valores múltiples. Sin embargo, FormatString no es vinculable, ya que es una propiedad de dependencia, por lo que no estoy seguro de que vaya a funcionar. –

+0

¿Cómo se supone que esto funcione? Cuando el TextBox se actualiza usando databinding, el texto se formatea utilizando FormatString. Cuando un usuario actualiza el cuadro de texto, puede ingresar cualquier texto que pueda ser inconsistente con el formato de FormatString. ¿Eso esta bien? ¿Estás seguro de que no quieres usar un cuadro de texto enmascarado en su lugar? Además, FormatString es tan vinculable como cualquier otra propiedad pública. –

+0

"FormatString es tan vinculable como cualquier otra propiedad pública" explique por qué recibirá un error que dice que "un 'Enlace' no se puede establecer en la propiedad 'StringFormat' del tipo 'Encuadernación'. Un 'Encuadernación' solo puede ser establecer en una DependencyProperty de un DependencyObject ". – jpierson

1

Se podría crear un comportamiento adjunto que podría reemplazar el enlace por uno que tenga especificado FormatString. Si la propiedad de dependencia FormatString, el enlace volvería a actualizarse. Si el enlace se actualiza, FormatString se volverá a aplicar a ese enlace.

Las dos únicas cosas complicadas con las que puedo pensar que tendrías que lidiar. Uno de los problemas es si desea crear dos propiedades adjuntas que se coordinen entre sí para FormatString y TargetProperty en el que exista el enlace que se debe aplicar a FormatString (por ejemplo, TextBox.Text) o tal vez simplemente pueda asumir qué propiedad está tratando. con dependiendo del tipo de control objetivo. El otro problema puede ser que puede ser no trivial copiar un enlace existente y modificarlo ligeramente dados los diversos tipos de enlaces que también pueden incluir enlaces personalizados.

Es importante tener en cuenta que todo esto solo logra formatear en la dirección de sus datos a su control.Por lo que puedo descubrir usando algo así como un MultiBinding junto con un MultiValueConverter personalizado para consumir tanto el valor original como el FormatString y producir el resultado deseado, todavía sufre el mismo problema principalmente porque el método ConvertBack solo tiene la cadena de salida y lo haría se espera que descifre tanto el FormatString como su valor original, que en ese punto casi siempre es imposible.

Los restantes soluciones que deben trabajar para el formato bidireccional y unformatting sería el siguiente:

  • tu dispositivo de control personalizado que se extiende cuadro de texto que tiene el comportamiento formato deseado como Jakob Christensen sugerido.
  • Escriba un convertidor de valor personalizado que se deriva de DependencyObject o FrameworkElement y tiene un FormatString DependencyProperty en él. Si desea ir a la ruta DependencyObject, creo que puede insertar el valor en la propiedad FormatString utilizando el enlace OneWayToSource con una técnica de "rama virtual". La otra manera más fácil puede, en cambio, heredar de FrameworkElement y colocar su convertidor de valor en el árbol visual junto con sus otros controles para que pueda unirse a él cuando lo necesite ElementName.
  • Utilice un comportamiento adjunto similar al que mencioné en la parte superior de esta publicación, pero en lugar de establecer FormatString, tiene dos propiedades adjuntas, una para un convertidor de valor personalizado y otra para el parámetro que se pasará al convertidor de valor . Luego, en lugar de modificar el enlace original para agregar FormatString, estaría agregando el convertidor y el parámetro del convertidor al enlace. Personalmente, creo que esta opción daría como resultado el resultado más legible e intuitivo porque los comportamientos adjuntos tienden a ser más limpios y, al mismo tiempo, lo suficientemente flexibles como para usarlos en una variedad de situaciones que no sean solo un TextBox.
2

Este código (inspirado desde DefaultValueConverter.cs @ referencesource.microsoft.com) trabaja para una unión a un cuadro de texto o control similar de dos vías, mientras el FormatString sale de la versión ToString() de la propiedad de origen en un estado que se puede convertir de nuevo. (es decir, el formato como "#, 0.00" está bien porque "1,234.56" se puede analizar, pero FormatString = "Some Prefix Text #, 0.00" convertirá a "Some Prefix Text 1,234.56" que no se puede analizar de nuevo).

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> 

Nota duplicar TargetNullValue si la propiedad de origen puede ser nulo.

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; 
    } 
} 
Cuestiones relacionadas