2013-04-11 10 views
18

Stavo osservando this question e ho scoperto che il binding da Label.Content a un valore non stringa applicherà uno stile implicito TextBlock, tuttavia il binding a una stringa non lo è.Perché viene applicato uno stile TextBlock implicito quando si associa Label.Content a una non stringa, ma non a una stringa?

Ecco alcuni esempi di codice per riprodurre il problema:

<Window.Resources> 
    <Style TargetType="Label"> 
     <Setter Property="FontSize" Value="26"/> 
     <Setter Property="Margin" Value="10"/> 
     <Setter Property="VerticalAlignment" Value="Center"/> 
    </Style> 
    <Style TargetType="{x:Type TextBlock}"> 
     <Setter Property="FontSize" Value="26"/> 
     <Setter Property="Margin" Value="10"/> 
    </Style> 
</Window.Resources> 

<Grid> 
    <StackPanel Orientation="Horizontal"> 
     <Label Content="{Binding SomeString}" Background="Red"/> 
     <Label Content="{Binding SomeDecimal}" Background="Green"/> 
    </StackPanel> 
</Grid> 

dove il codice per i valori legati vengono

SomeDecimal = 50; 
SomeString = SomeDecimal.ToString(); 

E il risultato finale è come questo, con la proprietà Margin dalla implicita Stile TextBlock applicato all'etichetta vincolato solo a una stringa:

enter image description here

Entrambe le etichette vengono resi come

<Label> 
    <Border> 
     <ContentPresenter> 
      <TextBlock /> 
     </ContentPresenter> 
    </Border> 
</Label> 

Quando controllo il VisualTree con Snoop, posso vedere che sembra esattamente lo stesso per entrambi gli elementi, ad eccezione del 2 ° TextBlock si applica il margine dallo stile implicita, mentre il primo no.

enter image description here

ho usato Miscela di tirare fuori una copia del modello di etichetta di default, ma non vedo niente di strano, e quando applico il modello per entrambi i miei etichette, la stessa cosa accade.

<Label.Template> 
    <ControlTemplate TargetType="{x:Type Label}"> 
     <Border BorderBrush="{TemplateBinding BorderBrush}" 
       BorderThickness="{TemplateBinding BorderThickness}" 
       Background="{TemplateBinding Background}" 
       Padding="{TemplateBinding Padding}" 
       SnapsToDevicePixels="True"> 
      <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" 
           Content="{TemplateBinding Content}" 
           ContentStringFormat="{TemplateBinding ContentStringFormat}" 
           HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
           RecognizesAccessKey="True" 
           SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 
     </Border> 
     <ControlTemplate.Triggers> 
      <Trigger Property="IsEnabled" Value="False"> 
       <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> 
      </Trigger> 
     </ControlTemplate.Triggers> 
    </ControlTemplate> 
</Label.Template> 

Va inoltre notato che l'impostazione di un valore predefinito ContentTemplate ad un TextBlock non fare entrambe le voci rendono senza lo stile implicita, quindi deve avere qualcosa a che fare con quando WPF cerca di rendere un valore non stringa come parte dell'interfaccia utente.

<Window.Resources> 
    <Style TargetType="Label"> 
     <Setter Property="FontSize" Value="26"/> 
     <Setter Property="Margin" Value="10"/> 
     <Setter Property="VerticalAlignment" Value="Center"/> 
    </Style> 
    <Style x:Key="TemplatedStyle" TargetType="Label" BasedOn="{StaticResource {x:Type Label}}"> 
     <Setter Property="ContentTemplate"> 
      <Setter.Value> 
       <DataTemplate> 
        <TextBlock Text="{Binding }"/> 
       </DataTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 
    <Style TargetType="{x:Type TextBlock}"> 
     <Setter Property="FontSize" Value="26"/> 
     <Setter Property="Margin" Value="10"/> 
    </Style> 
</Window.Resources> 

<Grid> 
    <StackPanel Orientation="Horizontal"> 
     <Label Content="{Binding SomeString}" Background="Red"/> 
     <Label Content="{Binding SomeDecimal}" Background="Green"/> 
     <Label Content="{Binding SomeString}" Background="Red" 
       Style="{StaticResource TemplatedStyle}"/> 
     <Label Content="{Binding SomeDecimal}" Background="Green" 
       Style="{StaticResource TemplatedStyle}"/> 
    </StackPanel> 
</Grid> 

enter image description here

Qual è la logica che causa una non stringa inserita nella UI da trarre utilizzando uno stile TextBlock implicita, ma una stringa inserita l'interfaccia utente non lo fa? E dove avviene questo?

+1

Questa domanda mi ha incuriosito, quindi ho dovuto iniziare il vecchio riflettore. Beh, c'è solo una cosa che potrei trovare finora. Sembra che TextBlock faccia una distinzione tra testo semplice e oggetti "complessi", utilizza due sottoclassi differenti per entrambi. Purtroppo questo è dove finisce. Non riesco a trovare alcuna traccia di uno stile specializzato o altro. Una cosa da provare è fornire un ContentTemplate con un TextBlock e controllare se questo cambia qualcosa. In tal caso potrebbe avere qualcosa a che fare con quel modello mistico predefinito. – dowhilefor

+0

@dowhilefor Ho provato a estrarre il modello predefinito da Blend e applicarlo a entrambe le etichette, e succede la stessa cosa (domanda aggiornata con quelle informazioni). La mia unica ipotesi sarebbe che WPF sappia rendere un 'System.String' come un' TextBlock' senza uno stile implicito, ma quando va a renderizzare un valore non-string, lo disegna come un 'TextBlock' legato a' .ToString() 'dell'oggetto e applica qualsiasi stile implicito. – Rachel

+0

@dowhilefor Hai ragione però che l'impostazione predefinita di 'ContentTemplate' su a' 'rende entrambi gli oggetti renderizzati senza lo stile implicito, quindi sembra che abbia qualcosa a che fare con il default 'ContentTemplate' per oggetti non stringa – Rachel

risposta

2

EDIT: (? Forse spostare questo verso il basso)

E mi infilò un po 'di più - e Penso che ho avuto al nocciolo del problema (w/enfasi su 'penso')

Mettere questo in qualche Button1_Click o qualcosa (ancora una volta, abbiamo bisogno di andare 'pigro' su questo - come abbiamo bisogno l'albero visuale costruito - non possiamo farlo su 'Loaded', come abbiamo appena fatto i modelli - questo ha richiesto una migliore tecnica di inizializzazione vera, ma è solo un test quindi a chi importa)

void Button_Click(object sender, EventArgs e) 
{ 
var insideTextBlock = FindVisualChild<TextBlock>(_labelString); 
var value = insideTextBlock.GetProperty<bool>("HasImplicitStyleFromResources"); // false 
value = insideTextBlock.GetProperty<bool>("ShouldLookupImplicitStyles"); // true 

var boundaryElement = insideTextBlock.TemplatedParent; // ContentPresenter and != null 

insideTextBlock = FindVisualChild<TextBlock>(_labelDecimal); 
value = insideTextBlock.GetProperty<bool>("HasImplicitStyleFromResources"); // true 
value = insideTextBlock.GetProperty<bool>("ShouldLookupImplicitStyles"); // true 

boundaryElement = insideTextBlock.TemplatedParent; // == null !! 

Come accennato qui Implicit styles in Application.Resources vs Window.Resources?
Il FindImplicitStyleResource (in FrameworkElement) utilizza qualcosa di simile ...

boundaryElement = fe.TemplatedParent; 

e sembra che se non c'è TemplatedParent (e per le vie il TextBlock è costruito all'interno della DefaultTemplate) - ci sono impostati no 'confini' - e la ricerca di risorse implicite/styles - si diffonde fino a.



risposta originale: (leggere questo in primo luogo se appena arrivati)

(@dowhilefor e @Jehof già toccato le cose principali)
io non sono sicuro che questo è una "risposta" in quanto tale - è ancora un lavoro da indovinare - ma avevo bisogno di più spazio per spiegare cosa penso stia succedendo.

È possibile trovare il codice 'ContentPresenter fonte' sul web - è più facile che usare riflettore - solo 'Google' per questo, io non sto postando qui per le ovvie ragioni :)

Si tratta della ContentTemplate che viene scelto per la ContentPresenter (e in questo ordine) ...

ContentTemplate // if defined 
ContentTemplateSelector // if defined 
FindResource // for typeof(Content) - eg if defined for sys:Decimal takes that one 
DefaultTemplate used internally by the presenter 
...specific templates are chosen based on typeof(Content) 

e in effetti non ha nulla a che fare con la ma qualsiasi modello ContentControl o il controllo Label che utilizza ContentPresenter. Oppure si potrebbe legare a risorsa ecc

Ecco un Repro di quello che sta succedendo all'interno - il mio obiettivo era quello di riprodurre il comportamento simile per 'strings' o qualsiasi tipo di contenuto.

in XAML proprio 'nome' le etichette (e non è un errore di battitura, volutamente messo stringhe sia per livellare il campo di gioco o quasi) ...

<Label Name="_labelString" Content="{Binding SomeString}" Background="Red"/> 
<Label Name="_labelDecimal" Content="{Binding SomeString}" Background="Green"/> 

E dal codice dietro (il codice minimo questo tipo di imita quello che presentatore fa):
nota: l'ho fatto Loaded come ho bisogno di accesso al presentatore creato implicitamente

void Window1_Loaded(object sender, RoutedEventArgs e) 
{ 
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(TextBlock)); 
factory.SetValue(TextBlock.TextProperty, new TemplateBindingExtension(ContentProperty)); 
var presenterString = FindVisualChild<ContentPresenter>(_labelString); 
presenterString.ContentTemplate = new DataTemplate() { VisualTree = factory }; 

// return; 

var presenterDecimal = FindVisualChild<ContentPresenter>(_labelDecimal); 
presenterDecimal.ContentTemplate = new DataTemplate(); 
// just to avoid the 'default' template kicking in 

// this is what 'default template' does actually, the gist of it 
TextBlock textBlock = new TextBlock(); 
presenterDecimal.SetProperty(typeof(FrameworkElement), "TemplateChild", textBlock); 
textBlock.Text = presenterDecimal.Content.ToString(); 

Prima parte (per _labelString) fa quello che 'testo' modello faper archi.

Se si dispone di return subito dopo, si otterranno le due caselle identiche, nessun modello implicito.

La seconda parte (per _labelDecimal) simula il "modello predefinito" che viene richiamato per il "decimale".

Il risultato finale dovrebbe essere lo stesso dell'esempio originale. Noi abbiamo costruito i modelli come per lo string e decimal - ma noi possiamo inserire qualsiasi cosa nel contenuto (se ha senso, ovviamente).

Quanto al motivo per - la mia ipotesi è qualcosa di simile (anche se tutt'altro che certo - qualcuno salterà in con qualcosa di più sensato credo) ...

Secondo questo link FrameworkElementFactory

Questa classe è un metodo deprecato per creare modelli a livello di programmazione, , che sono sottoclassi di FrameworkTemplate come ControlTemplate o DataTemplate; non tutte le funzionalità del modello sono disponibili quando si crea un modello utilizzando questa classe. Il metodo consigliato per creare un modello con in modo programmatico consiste nel caricare XAML da una stringa o da un flusso di memoria utilizzando il metodo Load della classe XamlReader.

E suppongo che non invochi stili definiti per TextBlock.

Mentre l''altro modello' (modello predefinito) - in realtà costruisce il TextBlock e da qualche parte lungo quelle linee - in realtà prende lo stile implicito.

Francamente, questo è quanto è stato in grado di concludere, a meno di passare attraverso l'intero 'WPF' internals 'e come/dove effettivamente vengono applicati gli stili.


Ho utilizzato questo codice Finding control within WPF itemscontrol per FindVisualChild.
E lo SetProperty è solo il riflesso - per quella proprietà a cui dobbiamo accedere per poter fare tutto questo. per esempio.

public static void SetProperty<T>(this object obj, string name, T value) { SetProperty(obj, obj.GetType(), name, value); } 
public static void SetProperty<T>(this object obj, Type typeOf, string name, T value) 
{ 
    var property = typeOf.GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 
    property.SetValue(obj, value, null); 
} 
0

Dopo aver esaminato questa domanda e i preziosi commenti da tutti, ho fatto qualche ricerca su TextBlock Styling.

Per quanto mi riguarda il problema qui non è con l'etichetta o TextBlock, è con il contentpresenter e i controlli che utilizzano contentpresenter come Label, button e ComboBoxItem.

Una delle proprietà di presentatore contenuti da MSDN: http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx

"Se c'è un TypeConverter che converte il tipo di contenuto in una stringa, il ContentPresenter usi che TypeConverter e crea un TextBlock per contenere quella stringa. viene visualizzata TextBlock "

nell'esempio precedente, per SomeString contenuto presentatore è la conversione in blocco di testo e applicando il margine TextBlock (10) insieme a margine Etichetta (10) rendendo 20.

Al fine di evitare questo scenario è necessario sostituire lo stile TextBlock in ContentPresenter come illustrato di seguito

      <ContentPresenter > 
           <ContentPresenter.Resources> 
            <Style TargetType="{x:Type TextBlock}"> 
             <Setter Property="Margin" Value="5" /> 
            </Style> 
           </ContentPresenter.Resources> 
          </ContentPresenter> 

seguito è le modifiche al codice.

<Window.Resources> 
     <Style TargetType="Label"> 
      <Setter Property="FontSize" Value="26"/> 
      <Setter Property="Margin" Value="10"/> 

      <Setter Property="VerticalAlignment" Value="Center"/> 
      <Setter Property="Template"> 
       <Setter.Value> 

        <ControlTemplate TargetType="Label"> 
         <Grid> 
          <Rectangle Fill="{TemplateBinding Background}" /> 
          <ContentPresenter > 
           <ContentPresenter.Resources> 
            <Style TargetType="{x:Type TextBlock}"> 
             <Setter Property="Margin" Value="5" /> 
            </Style> 
           </ContentPresenter.Resources> 
          </ContentPresenter> 
         </Grid> 

        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
     <Style TargetType="{x:Type TextBlock}"> 
      <Setter Property="FontSize" Value="26"/> 
      <Setter Property="Margin" Value="10"/> 
      <Setter Property="Foreground" Value="Pink" /> 
     </Style> 
    </Window.Resources> 

    <Grid> 
     <StackPanel Orientation="Horizontal"> 
      <Label Content="{Binding SomeString}" Background="Red" /> 
      <Label Content="{Binding SomeDecimal}" Background="Green"/> 
     </StackPanel> 
    </Grid> 
</Window> 

Questa spiegazione è basata solo sulla mia comprensione. Fammi sapere i tuoi commenti.

Grazie

+0

In realtà ho aggiunto un po 'alla mia domanda che spiega che se si sovrascrive il 'ContentTemplate' dell'etichetta, si interrompe l'applicazione dello stile implicito TextBlock.La mia domanda è più sul perché posizionare una stringa nell'interfaccia utente * * applica lo stile implicito, mentre posizionare una non stringa (che viene poi convertita in una stringa) nell'interfaccia utente non lo fa. Penso che potresti essere vicino con la citazione MSDN però. Sospetto che il processo che utilizza un 'TypeConverter' per creare un' TextBlock' sta applicando qualsiasi stile implicito, indipendentemente dai limiti del modello. – Rachel

+0

@Rachel - 'TypeConverter' è dove ho fatto' textBlock.Text = presenter.Content.ToString(); '(Posso postare il codice - e il link completo se lo desideri - ma ero solo riluttante a condividerlo) - riempie il testo proprio alla fine. Non ha nulla a che fare con la creazione di TextBlock (i template lo fanno). Vedi il mio 'EDIT' - il problema è in realtà con i' boundaries'. – NSGaga

0

Secondo il mio commento aggiungo ulteriori informazioni alla domanda. Non è una risposta diretta, ma fornisce informazioni aggiuntive al problema descritto.

L'XAML di seguito mostrerà il comportamento descritto direttamente nel Designer di Visual Studio e l'ho ristretto a ContentPresenter, che sembra essere la fonte del problema. Lo stile viene applicato al primo ContentPresenter (intPresenter e boolPresenter), ma non l'ultimo che utilizza una stringa come Contenuto (stringPresenter).

<Window.Resources> 
    <system:Int32 x:Key="intValue">5</system:Int32> 
    <system:Boolean x:Key="boolValue">false</system:Boolean> 
    <system:String x:Key="stringValue">false</system:String> 
    <Style TargetType="{x:Type TextBlock}"> 
    <Setter Property="FontSize" Value="26" /> 
    <Setter Property="Margin" Value="10" /> 
    </Style> 
</Window.Resources> 

<Grid> 
    <StackPanel Orientation="Horizontal"> 
    <ContentPresenter x:Name="intPresenter" 
         VerticalAlignment="Center" 
         Content="{StaticResource intValue}" /> 
    <ContentPresenter x:Name="boolPresenter" 
         VerticalAlignment="Center" 
         Content="{StaticResource boolValue}" /> 
    <ContentPresenter x:Name="stringPresenter" 
         VerticalAlignment="Center" 
         Content="{StaticResource stringValue}" /> 
    </StackPanel> 
    </Grid> 

Nel debugger ho analizzato che il stringPresenter utilizza il DefaultStringTemplate mentre il intPresenter non lo fa.

enter image description here

Sua anche interessante il fatto che il Language del intPresenter è impostato, mentre dal stringPresenter la sua non.

E l'attuazione del metodo sembra qualcosa di simile (tratto da dotPeek)

private bool IsUsingDefaultStringTemplate 
    { 
     get 
     { 
     if (this.Template == ContentPresenter.StringContentTemplate || this.Template == ContentPresenter.AccessTextContentTemplate) 
      return true; 
     DataTemplate dataTemplate1 = ContentPresenter.StringFormattingTemplateField.GetValue((DependencyObject) this); 
     if (dataTemplate1 != null && dataTemplate1 == this.Template) 
      return true; 
     DataTemplate dataTemplate2 = ContentPresenter.AccessTextFormattingTemplateField.GetValue((DependencyObject) this); 
     return dataTemplate2 != null && dataTemplate2 == this.Template; 
     } 
    } 

Lo StringContentTemplate e AccessTextTemplate stanno utilizzando un FrameworkElementFactory per generare le immagini.

Problemi correlati