为什么在将Label.Content绑定到非字符串而不是字符串时会应用隐式TextBlock样式?

Rac*_*hel 18 wpf label styles implicit-style

我正在查看这个问题,并发现绑定Label.Content到非字符串值将应用隐式TextBlock样式,但是绑定到字符串则不会.

以下是重现问题的示例代码:

<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>
Run Code Online (Sandbox Code Playgroud)

绑定值的代码是

SomeDecimal = 50;
SomeString = SomeDecimal.ToString();
Run Code Online (Sandbox Code Playgroud)

最终结果如下所示,Margin隐式TextBlock样式的属性应用于仅绑定到非字符串的Label:

在此输入图像描述

两个标签都呈现为

<Label>
    <Border>
        <ContentPresenter>
            <TextBlock />
        </ContentPresenter>
    </Border>
</Label>
Run Code Online (Sandbox Code Playgroud)

当我使用Snoop检查VisualTree时,我可以看到它对于两个元素看起来完全相同,除了第二个TextBlock从隐式样式应用Margin,而第一个没有.

在此输入图像描述

我已经使用Blend来提取默认标签模板的副本,但是没有看到任何奇怪的东西,当我将模板应用于我的两个标签时,同样的事情发生了.

<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> 
Run Code Online (Sandbox Code Playgroud)

还应该注意的是,将默认值设置ContentTemplate为a TextBlock会使两个项目都呈现而没有隐式样式,因此当WPF尝试将非字符串值作为UI的一部分呈现时,它必须与之相关.

<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>
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

使用隐式TextBlock样式导致插入UI的非字符串的逻辑是什么,但插入UI的字符串不是?这发生在哪里?

NSG*_*aga 2

编辑:(也许将其移到底部?)

我又戳了一点——我想我已经找到了问题的症结(重点是“我认为”)

将其放入某些Button1_Click或某些东西中(同样,我们需要对此进行“懒惰” - 因为我们需要构建可视化树 - 我们不能在“加载”上执行此操作,因为我们刚刚制作了模板 - 这需要更好的初始化技术,但是这只是一个测试所以谁在乎)

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 !!
Run Code Online (Sandbox Code Playgroud)

正如这里提到的Application.Resources vs Window.Resources 中的隐式样式?
( FindImplicitStyleResourcein FrameworkElement) 使用类似...

boundaryElement = fe.TemplatedParent;  
Run Code Online (Sandbox Code Playgroud)

似乎如果没有TemplatedParent(并且由于TextBlock内部构造的方式DefaultTemplate) -没有设置“边界” - 并且搜索隐式资源/样式 - 会一路传播



原始答案:(如果您刚来,请先阅读此内容)

(@dowhilefor 和 @Jehof 已经触及了主要内容)
我不确定这是否是一个“答案” - 这仍然是一个猜测 - 但我需要更多的空间来解释我认为正在发生的事情。

您可以在网络上找到“ContentPresenter 源”代码 - 它比使用 Reflector 更容易 - 只需“google”即可,出于显而易见的原因,我不会将其发布在这里:)

这是关于ContentTemplateContentPresenter(并且按此顺序)选择的...

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)
Run Code Online (Sandbox Code Playgroud)

Label事实上,它与任何使用 的 ContentControl 或控件模板都没有任何关系ContentPresenter。或者你可以绑定到资源等。

这是内部发生的事情的重现-我的目标是重现“字符串”或任何类型的内容的类似行为。

在 XAML 中,只需“命名”标签(这不是拼写错误,而是故意在两者中放入字符串以公平竞争)...

<Label Name="_labelString" Content="{Binding SomeString}" Background="Red"/>
<Label Name="_labelDecimal" Content="{Binding SomeString}" Background="Green"/>
Run Code Online (Sandbox Code Playgroud)

从代码后面(模仿演示者所做的最小代码):
注意:我这样做是Loaded因为我需要访问隐式创建的演示者

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();
Run Code Online (Sandbox Code Playgroud)

第一部分(对于_labelString)执行“文本”模板对字符串执行的操作。

如果您return在那之后 - 您将得到两个外观相同的框,没有隐式模板。

第二部分(for _labelDecimal)模仿为“小数”调用的“默认模板”。

最终结果应与原始示例相同。string我们构建了和的模板decimal- 但我们可以在内容中放置任何内容(当然,如果它有意义的话)。

至于为什么- 我的猜测是这样的(尽管远非确定 - 有人会跳出一些更明智的东西我猜)......

根据此链接FrameworkElementFactory

此类是一种已弃用的以编程方式创建模板的方法,模板是 FrameworkTemplate 的子类,例如 ControlTemplate 或 DataTemplate;当您使用此类创建模板时,并非所有模板功能都可用。以编程方式创建模板的推荐方法是使用 XamlReader 类的 Load 方法从字符串或内存流加载 XAML。

我猜它不会调用 TextBlock 的任何定义的样式。

虽然“其他模板”(默认模板) - 实际上TextBlock在这些地方构造了 和 - 它实际上采用了隐式样式。

坦率地说,这就是我所能得出的结论,但没有详细了解整个 WPF“内部结构”以及样式的实际应用方式/位置。


我使用此代码在 WPF itemscontrol中查找控件FindVisualChild
SetProperty只是反射 - 对于我们需要访问的一个属性才能完成这一切。例如

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);
}
Run Code Online (Sandbox Code Playgroud)