2011-12-27 20 views
5

Estoy tratando de usar un control personalizado en una aplicación WPF, y tengo algún problema al utilizar un enlace StringFormat.Enlace con StringFormat en un control personalizado

El problema es fácil de reproducir. Primero, creemos una aplicación WPF y llámenla "TemplateBindingTest". Allí, agregue un ViewModel personalizado con una sola propiedad (Texto) y asígnelo al DataContext de la ventana. Establezca la propiedad Text en "Hello World!".

Ahora, agregue un control personalizado a la solución. El control personalizado es tan simple como se puede conseguir:

using System.Windows; 
using System.Windows.Controls; 

namespace TemplateBindingTest 
{ 
    public class CustomControl : Control 
    { 
     static CustomControl() 
     { 
      TextProperty = DependencyProperty.Register(
       "Text", 
       typeof(object), 
       typeof(CustomControl), 
       new FrameworkPropertyMetadata(null)); 

      DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl))); 
     } 

     public static DependencyProperty TextProperty; 

     public object Text 
     { 
      get 
      { 
       return this.GetValue(TextProperty); 
      } 

      set 
      { 
       SetValue(TextProperty, value); 
      } 
     } 
    } 
} 

Al agregar el control personalizado a la solución, Visual Studio crea automáticamente una carpeta Temas, con un archivo generic.xaml. Vamos a poner el estilo predeterminado para el control de allí:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:TemplateBindingTest"> 

    <Style TargetType="{x:Type local:CustomControl}"> 
     <Setter Property="Template"> 
      <Setter.Value> 
       <ControlTemplate TargetType="{x:Type local:CustomControl}"> 
        <TextBlock Text="{TemplateBinding Text}" /> 
       </ControlTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 
</ResourceDictionary> 

Ahora, sólo tiene que añadir el control a la ventana, y establecer un enlace en la propiedad de texto, utilizando un StringFormat. También añadir un TextBlock sencilla para asegurarse de que la sintaxis de unión es correcta:

<Window x:Class="TemplateBindingTest.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:TemplateBindingTest="clr-namespace:TemplateBindingTest" Title="MainWindow" Height="350" Width="525"> 
<StackPanel> 
    <TemplateBindingTest:CustomControl Text="{Binding Path=Text, StringFormat=Test1: {0}}"/> 
    <TextBlock Text="{Binding Path=Text, StringFormat=Test2: {0}}" /> 
</StackPanel> 

compilar, ejecutar, aaaaand ... El texto que aparece en la ventana es:

Hello World !

Test2: ¡Hola, mundo!

En el control personalizado, StringFormat se ignora por completo. Ningún error es visible en la ventana de salida VS. ¿Que esta pasando?

Editar: La solución.

Ok, el TemplateBinding fue engañoso. Encontré la causa y una solución sucia.

En primer lugar, se dio cuenta de que el problema es el mismo con la propiedad del contenido Botón:

<Button Content="{Binding Path=Text, StringFormat=Test3: {0}}" /> 

Por lo tanto, lo que está pasando? Usemos Reflector y buceamos a la propiedad StringFormat de la clase BindingBase. La función 'Analizar' muestra que esta propiedad es utilizada por el método interno DetermineEffectiveStringFormat. Veamos este método:

internal void DetermineEffectiveStringFormat() 
{ 
    Type propertyType = this.TargetProperty.PropertyType; 
    if (propertyType == typeof(string)) 
    { 
     // Do some checks then assign the _effectiveStringFormat field 
    } 
} 

El problema está aquí. El campo effectiveStringFormat es el que se usa al resolver la vinculación. Y este campo se asigna solo si DependencyProperty es del tipo String (el mío es, como propiedad del contenido del botón, Object).

¿Por qué hacer un objeto? Debido a que mi control personalizado es un poco más complejo que el que pegué, y al igual que el botón, quiero que el usuario del control pueda proporcionar controles secundarios en lugar de solo texto.

Entonces, ¿ahora qué? Nos topamos con un comportamiento existente incluso en los controles básicos de WPF, por lo que puedo dejarlo "tal como está".Sin embargo, como mi control personalizado se utiliza sólo en un proyecto interno, y yo quiero que sea más fácil de usar desde XAML, decidí utilizar este truco:

using System.Windows; 
using System.Windows.Controls; 

namespace TemplateBindingTest 
{ 
    public class CustomControl : Control 
    { 
     static CustomControl() 
     { 
      TextProperty = DependencyProperty.Register(
       "Text", 
       typeof(string), 
       typeof(CustomControl), 
       new FrameworkPropertyMetadata(null, Callback)); 

      HeaderProperty = DependencyProperty.Register(
       "Header", 
       typeof(object), 
       typeof(CustomControl), 
       new FrameworkPropertyMetadata(null)); 

      DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl))); 
     } 

     static void Callback(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
     { 
      obj.SetValue(HeaderProperty, e.NewValue); 
     } 

     public static DependencyProperty TextProperty; 
     public static DependencyProperty HeaderProperty; 

     public object Header 
     { 
      get 
      { 
       return this.GetValue(HeaderProperty); 
      } 

      set 
      { 
       SetValue(HeaderProperty, value); 
      } 
     } 

     public string Text 
     { 
      set 
      { 
       SetValue(TextProperty, value); 
      } 
     } 
    } 
} 

Header es la propiedad utilizada en mi TemplateBinding. Cuando se proporciona un valor a Text, se aplica StringFormat ya que la propiedad es de tipo String, luego el valor se reenvía a la propiedad Header mediante una devolución de llamada. Funciona, pero es muy sucio:

  • El Header y la propiedad Text no están en sincronía, como Text no se actualiza cuando actualizo Header. Elijo no proporcionar getter a la propiedad Text para evitar algunos errores, pero aún puede suceder si alguien lee directamente el valor de DependencyProperty (GetValue(TextProperty)).
  • Puede producirse un comportamiento impredecible si alguien proporciona un valor a la propiedad Header y Text ya que se perderá uno de los valores.

Así que en general, no recomiendo usar este truco. Hazlo solo si eres realmente en control de tu proyecto. Si el control tiene la más mínima posibilidad de ser utilizado en otro proyecto, simplemente renuncie a StringFormat.

Respuesta

2

StringFormat se utiliza cuando la unión a una propiedad string, mientras que la propiedad Text en su control es de tipo object, por lo tanto StringFormat se ignora.

+0

Incorrecto. El tiempo de ejecución invoca 'object.ToString()' para todos los objetos antes de formatear la cadena. –

+2

Sí, pero comprueba el tipo subyacente de la propiedad de dependencia antes de aplicar StringFormat. Funciona si cambio el tipo de mi dp a String en lugar de Object. –

Cuestiones relacionadas