本文介绍了将Label.Content绑定到非字符串而不是字符串时,为什么应用隐式TextBlock样式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I was looking at this question, and discovered that binding Label.Content to a non-string value will apply an implicit TextBlock style, however binding to a string does not.

下面是一些重现该问题的示例代码:

Here's some sample code to reproduce the problem:

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

绑定值的代码在哪里

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

最终结果看起来像这样,隐式TextBlock样式的Margin属性被应用于仅绑定到非字符串的Label:

And the end result looks like this, with the Margin property from the implicit TextBlock style getting applied to the Label bound to a non-string only:

两个标签都呈现为

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

当我使用 Snoop 检出VisualTree时,我发现两者的外观完全相同元素,除了第二个TextBlock会从隐式样式应用Margin,而第一个不会.

When I check out the VisualTree with Snoop, I can see that it looks exactly the same for both elements, except the 2nd TextBlock applies the Margin from the implicit style, while the first does not.

我使用Blend提取了默认标签模板的副本,但是在那里看不到任何奇怪的东西,当我将模板应用于两个标签时,也会发生相同的事情.

I've used Blend to pull out a copy of the default Label Template, but don't see anything strange there, and when I apply the template to both my labels, the same thing happens.

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

还应注意,将默认ContentTemplate设置为TextBlock确实会使两个项目都呈现无隐式样式,因此当WPF尝试将非字符串值作为部分呈现时,它必须与之相关.的界面.

It should also be noted that setting a default ContentTemplate to a TextBlock does make both items render without the implicit style, so it must have something to do with when WPF tries to render a non-string value as part of the 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>

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

What is the logic that causes a non-string inserted into the UI to be drawn using an implicit TextBlock style, but a string inserted into the UI does not? And where does this occur at?

推荐答案

(也许将其移到底部?)

(maybe move this to the bottom?)

然后我戳了一下-我认为我已经解决了问题的症结所在(强调我认为")

And I poked a bit more - and I think I got to the crux of the problem (w/ emphasis on 'I think')

将其放入某些Button1_Click或其他内容(同样,我们需要对此进行懒惰"操作-因为我们需要构建可视化树-我们无法像在"Loaded"上那样进行操作)刚刚制作了模板-这需要更好的初始化技术,但这只是一个测试,所以谁在乎?

Put this into some Button1_Click or something (again, we need to go 'lazy' on this - as we need the visual tree constructed - we cannot do it on 'Loaded' as we just made the templates - this required better initialization technique true, but it's just a test so who cares)

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

如此处所述 Application.Resources与Window.Resources中的隐式样式?
FindImplicitStyleResource(在FrameworkElement中)使用类似...

As mentioned here Implicit styles in Application.Resources vs Window.Resources?
The FindImplicitStyleResource (in FrameworkElement) uses something like...

boundaryElement = fe.TemplatedParent;



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



Original Answer: (read this first if you just arrived)

(@ dowhilefor和@Jehof已经涉及到主要问题)
我不确定这是否是一个答案"-仍然是猜测,但我需要更多空间来解释我的想法.

(@dowhilefor and @Jehof already touched on the main things)
I'm not sure this is an 'answer' as such - it's still a guess work - but I needed more space to explain what I think is going on.

您可以在网络上找到"ContentPresenter源"代码-比使用反射器更容易-只需使用"google",出于明显的原因,我就不会在此处发布它了:)

You can find the 'ContentPresenter source' code on the web - it's easier than using reflector - just 'google' for it, I'm not posting it here for the obvious reasons :)

关于为ContentPresenter选择的ContentTemplate(并按此顺序)...

It's about the ContentTemplate that is chosen for the ContentPresenter (and in this order)...

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)

实际上,它与Label没有任何关系,但是与使用ContentPresenter的任何ContentControl或控件模板无关.或者您可以绑定到资源等.

And indeed it doesn't have anything to do with the Label but any ContentControl or control template that uses ContentPresenter. Or you could bind to resource etc.

这是内部情况的再现-我的目标是为字符串"或任何类型的内容重现类似的行为.

在XAML中,只需为标签命名"(这不是拼写错误,而是故意在两者中都放置字符串以平整公平的竞争环境)...

In XAML just 'name' the labels (and it isn't a typo, a deliberately put strings in both to level the playing field sort of)...

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

以及后面的代码(模仿演示者所做工作的最小代码):
注意:我在Loaded上完成了此操作,因为我需要访问隐式创建的演示者

And from code behind (the minimal code that sort of mimics what presenter does):
note: I did it on Loaded as I needed access to the presenter implicitly created

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

第一部分(对于_labelString)执行文本"模板对字符串的处理.

First part (for _labelString) does what 'text' template does for strings.

如果紧接着return-您将得到两个相同的外观框,没有隐式模板.

If you return right after that - you'll get the two same looking boxes, no implicit template.

第二部分(用于_labelDecimal)模仿为十进制"调用的默认模板".

Second part (for _labelDecimal) mimics the 'default template' which is invoked for the 'decimal'.

关于原因-我的猜测是这样的(尽管不确定,有人会以我认为更明智的方式跳进来)...

As to why - my guess is something like this (though far from certain - somebody will jump in with something more sensible I guess)...

根据此链接 FrameworkElementFactory

我猜想它不会为TextBlock调用任何定义的样式.

And I'm guessing it doesn't invoke any defined styles for the TextBlock.

虽然其他模板"(默认模板)-实际上构造了TextBlock,并且沿着这些行的某个位置-它实际上采用了隐式样式.

While the 'other template' (default template) - actually constructs the TextBlock and somewhere along those lines - it actually picks up the implicit style.

坦率地说,这就是我能够得出的结论,但没有涉及整个WPF的内部"问题以及样式的实际应用方式/位置.

Frankly, that's as much as I was able to conclude, short of going through the entire WPF 'internals' and how/where actually styles get applied.


我在FindVisualChild在WPF项目控件中查找控件中使用了此代码.
SetProperty只是一种反映-对于该属性,我们需要具有访问权限才能执行所有这些操作.例如


I used this code Finding control within WPF itemscontrol for FindVisualChild.
And the SetProperty is just the reflection - for that one property we need access to to be able to do all this. e.g.

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

这篇关于将Label.Content绑定到非字符串而不是字符串时,为什么应用隐式TextBlock样式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-06 00:53