2011-12-27 18 views
5

Sto tentando di utilizzare un controllo personalizzato in un'app WPF e ho qualche problema nell'utilizzo di un'associazione StringFormat.Associazione con StringFormat su un controllo personalizzato

Il problema è facile da riprodurre. Per prima cosa, creiamo un'applicazione WPF e chiamiamola "TemplateBindingTest". Qui, aggiungi un ViewModel personalizzato con una sola proprietà (Testo) e assegnalo al DataContext della Finestra. Imposta la proprietà Text su "Hello World!".

Ora aggiungere un controllo personalizzato alla soluzione. Il controllo personalizzato è così semplice come si può ottenere:

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); 
      } 
     } 
    } 
} 

Quando si aggiunge il controllo personalizzato alla soluzione, Visual Studio creata automaticamente una cartella Temi, con un file generic.xaml. Mettiamo lo stile predefinito per il controllo in là:

<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> 

Ora, basta aggiungere il controllo alla finestra, e impostare un vincolante per la proprietà Text, utilizzando uno StringFormat. Anche aggiungere un semplice TextBlock per essere sicuri che la sintassi di legame è corretta:

<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> 

compilare, eseguire, Aaaaand ... Il testo visualizzato nella finestra è:

Ciao Mondo !

Test2: Hello World!

Nel controllo personalizzato, StringFormat viene completamente ignorato. Nessun errore è visibile sulla finestra di output VS. Cosa sta succedendo?

Modifica: La soluzione alternativa.

Ok, il TemplateBinding era fuorviante. Ho trovato la causa e una soluzione sporca.

In primo luogo, si noti che il problema è lo stesso con la proprietà Content di Button:

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

Quindi, cosa sta succedendo? Usiamo Reflector e ci immergiamo nella proprietà StringFormat della classe BindingBase. La funzione "Analizza" mostra che questa proprietà è utilizzata dal metodo interno DetermineEffectiveStringFormat. Vediamo questo metodo:

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

Il problema è proprio qui. Il campo effectiveStringFormat è quello utilizzato quando si risolve il binding. E questo campo è assegnato solo se DependencyProperty è di tipo String (il mio è, come proprietà Contenuto del pulsante, Object).

Perché oggetto? Perché il mio controllo personalizzato è un po 'più complesso di quello che ho incollato e, come il pulsante, voglio che l'utente del controllo sia in grado di fornire controlli figlio anziché solo testo.

Quindi, che ora? Stiamo imbattendo in un comportamento esistente anche nei controlli di base WPF, quindi posso semplicemente lasciarlo così com'è.Eppure, come il mio controllo personalizzato viene utilizzato solo su un progetto interno, e voglio che sia più facile da usare da XAML, ho deciso di usare questo hack:

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 è la proprietà usato nella mia TemplateBinding. Quando viene fornito un valore a Text, viene applicato StringFormat poiché la proprietà è di tipo String, quindi il valore viene inoltrato alla proprietà Header utilizzando un callback. Funziona, ma è davvero sporco:

  • Il Header e la proprietà Text non sono in sincronia, come Text non viene aggiornato quando aggiorno Header. Ho scelto di non fornire getter alla proprietà Text per evitare alcuni errori, ma può ancora accadere se qualcuno legge direttamente il valore da DependencyProperty (GetValue(TextProperty)).
  • È possibile che si verifichi un comportamento imprevedibile se qualcuno fornisce un valore per entrambe le proprietà Header e Text in quanto uno dei valori andrà perso.

Quindi, nel complesso, non consiglierei di usare questo trucco. Fallo solo se hai il veramente in controllo del tuo progetto. Se il controllo ha anche la minima possibilità di essere utilizzato su un altro progetto, basta rinunciare a StringFormat.

risposta

2

StringFormat viene utilizzato durante l'associazione a una proprietà string, mentre la proprietà Text nel controllo è di tipo object, quindi StringFormat viene ignorato.

+0

errato. Il runtime richiama 'object.ToString()' per tutti gli oggetti prima di formattare la stringa. –

+2

Sì, ma controlla il tipo sottostante della proprietà di dipendenza prima di applicare StringFormat. Funziona se cambio il tipo di mio dp in String invece di Object. –

Problemi correlati