问题描述
我正在查看这个问题,发现将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样式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!