check - tooltip wpf
¿Por qué se aplica un estilo de TextBlock implícito cuando se vincula Label.Content a una no cadena, pero no a una cadena? (3)
Estaba analizando esta pregunta y descubrí que vincular Label.Content
a un valor no de cadena aplicará un estilo de TextBlock
implícito, sin embargo, la vinculación a una cadena no.
Aquí hay algunos ejemplos de código para reproducir el 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>
Donde está el código para los valores ligados
SomeDecimal = 50;
SomeString = SomeDecimal.ToString();
Y el resultado final se ve así, con la propiedad Margin
del estilo TextBlock implícito que se aplica a la etiqueta vinculada a una única no cadena:
Ambas etiquetas se renderizan como
<Label>
<Border>
<ContentPresenter>
<TextBlock />
</ContentPresenter>
</Border>
</Label>
Cuando reviso el VisualTree con Snoop , puedo ver que se ve exactamente igual para ambos elementos, excepto que el 2nd TextBlock aplica el Margen del estilo implícito, mientras que el primero no lo hace.
He utilizado Blend para extraer una copia de la plantilla de etiqueta predeterminada, pero no veo nada extraño allí, y cuando aplico la plantilla a mis dos etiquetas, sucede lo mismo.
<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>
También se debe tener en cuenta que establecer una ContentTemplate
predeterminada en un TextBlock
hace que ambos elementos se representen sin el estilo implícito, por lo que debe tener algo que ver con cuando WPF intenta representar un valor no de cadena como parte de la IU.
<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>
¿Cuál es la lógica que hace que una cadena no insertada en la interfaz de usuario se dibuje utilizando un estilo de TextBlock implícito, pero una cadena insertada en la interfaz de usuario no? ¿Y dónde ocurre esto?
Después de pasar por esta pregunta y los valiosos comentarios de todos, he investigado el estilo de TextBlock.
A mi entender, el problema aquí no es con Label o TextBlock, sino con el contentpresenter y los controles que usan contentpresenter como Label, button y ComboBoxItem.
Una de las propiedades del presentador de contenido de MSDN: http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx
"Si hay un TypeConverter que convierte el tipo de Contenido en una cadena, el ContentPresenter usa ese TypeConverter y crea un TextBlock para contener esa cadena. El TextBlock se muestra"
En el ejemplo anterior, For SomeString Content presenter lo está convirtiendo en textblock y aplicando el margen TextBlock (10) junto con el margen Label (10), lo que lo convierte en 20.
Para evitar este escenario, debe anular el estilo de TextBlock en contentpresenter como se muestra a continuación
<ContentPresenter >
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
A continuación se muestran los cambios a su código.
<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>
Esta explicación se basa simplemente en mi comprensión. Hazme saber tus comentarios.
Gracias
Según mi comentario agrego más información a la pregunta. No es una respuesta directa, pero proporciona información adicional al problema descrito.
El XAML a continuación mostrará el comportamiento descrito directamente en el Diseñador de Visual Studio y lo he reducido al ContentPresenter, que parece ser la fuente del problema. El estilo se aplica al primer ContentPresenter ( intPresenter
y boolPresenter
), pero no al último que usa una cadena como Contenido ( 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>
En el depurador, he analizado que stringPresenter
utiliza stringPresenter
mientras que intPresenter
no.
También es interesante que el Language
del intPresenter
está establecido, mientras que el stringPresenter
no está configurado.
Y la implementación del método se ve algo así (tomado de 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;
}
}
StringContentTemplate y AccessTextTemplate utilizan un FrameworkElementFactory para generar los elementos visuales.
EDITAR: (tal vez mover esto al fondo?)
Y asomé un poco más, y creo que llegué al punto crucial del problema (con énfasis en ''Creo'')
Ponga esto en algún Button1_Click
o algo (de nuevo, debemos ir ''flojos'' en esto, ya que necesitamos que se construya el árbol visual; no podemos hacerlo en ''Cargado'', ya que simplemente hicimos las plantillas - esto requirió una mejor técnica de inicialización. pero es solo una prueba asi que a quien le 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 !!
Como se mencionó aquí Estilos implícitos en Application.Resources vs Window.Resources?
FindImplicitStyleResource
(en FrameworkElement
) usa algo como ...
boundaryElement = fe.TemplatedParent;
Respuesta original: (lea esto primero si acaba de llegar)Y parece que si no hay un
TemplatedParent
(y debido a las formas en que se construye elTextBlock
dentro deDefaultTemplate
) - no hay un conjunto de ''límites'' - y la búsqueda de recursos / estilos implícitos - se propaga hasta el final .
(@dowhilefor y @Jehof ya tocaron las cosas principales)
No estoy seguro de que esta sea una "respuesta" como tal, sigue siendo un trabajo de adivinación, pero necesitaba más espacio para explicar lo que creo que está pasando.
Puede encontrar el código ''ContentPresenter source'' en la web. Es más fácil que usar el reflector. Simplemente ''google'', no lo estoy publicando aquí por las razones obvias :)
Se trata del ContentTemplate
que se elige para el ContentPresenter
(y en este orden) ...
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)
Y, de hecho, no tiene nada que ver con la Label
sino con cualquier ContentControl o plantilla de control que use ContentPresenter
. O podrías enlazar al recurso etc.
Aquí hay una repro de lo que está sucediendo en mi interior : mi objetivo era reproducir un comportamiento similar para "cadenas" o cualquier tipo de contenido.
En XAML, simplemente ''nombre'' las etiquetas (y no es un error tipográfico, sino que se ponen cadenas a ambos para nivelar el campo de juego) ...
<Label Name="_labelString" Content="{Binding SomeString}" Background="Red"/>
<Label Name="_labelDecimal" Content="{Binding SomeString}" Background="Green"/>
Y del código que hay detrás (el código mínimo que imita lo que hace el presentador):
Nota: Lo hice en Loaded
cuando necesitaba acceso al presentador creado implícitamente.
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();
La primera parte (para _labelString
) hace lo que hace la plantilla de ''texto'' para las cadenas.
Si return
justo después de eso, obtendrá los dos cuadros que buscan lo mismo, sin plantilla implícita.
La segunda parte (para _labelDecimal
) imita la ''plantilla predeterminada'' que se invoca para el ''decimal''.
El resultado final debe comportarse igual que el ejemplo original. Construimos las plantillas según la
string
y eldecimal
, pero podemos poner cualquier cosa en el contenido (si tiene sentido, por supuesto).
En cuanto a por qué , mi conjetura es algo como esto (aunque lejos de ser seguro, alguien saltará con algo más sensato, supongo) ...
Según este enlace FrameworkElementFactory
Esta clase es una forma obsoleta de crear plantillas mediante programación, que son subclases de FrameworkTemplate como ControlTemplate o DataTemplate; no toda la funcionalidad de la plantilla está disponible cuando crea una plantilla con esta clase. La forma recomendada de crear una plantilla mediante programación es cargar XAML desde una cadena o un flujo de memoria utilizando el método Load de la clase XamlReader.
Y supongo que no invoca ningún estilo definido para el TextBlock.
Mientras que la "otra plantilla" (plantilla predeterminada), en realidad construye el TextBlock
y en algún lugar en esa línea, en realidad recoge el estilo implícito.
Francamente, eso es todo lo que pude concluir, sin pasar por todas las "partes internas" de WPF y cómo / dónde se aplican los estilos.
Utilicé este código para encontrar el control dentro del control de elementos de WPF paraFindVisualChild
. Y
SetProperty
es solo el reflejo: para esa propiedad a la que necesitamos acceso para poder hacer todo esto. p.ej 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);
}