19

我在看这个问题,发现绑定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>

绑定值的代码在哪里

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

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

在此处输入图像描述

两个标签都呈现为

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

当我用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> 

还应该注意的是,将默认值设置ContentTemplate为 aTextBlock确实会使两个项目在没有隐式样式的情况下呈现,因此它必须与 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>

在此处输入图像描述

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

4

3 回答 3

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

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

boundaryElement = fe.TemplatedParent;  

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



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

(@dowhilefor 和@Jehof 已经谈到了主要的事情)
我不确定这是一个“答案”——它仍然是一个猜测工作——但我需要更多的空间来解释我认为正在发生的事情。

您可以在网络上找到“ContentPresenter 源”代码 - 它比使用反射器更容易 - 只需 '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)

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

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

在 XAML 中,只需“命名”标签(这不是错字,故意在两者中放入字符串以平衡游戏环境)......

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

从后面的代码(模仿演示者所做的最小代码):
注意:我这样做是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();

第一部分 (for _labelString) 的作用与 'text' 模板对字符串的作用相同。

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

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

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

至于为什么-我的猜测是这样的(尽管还不确定-我猜有人会加入一些更明智的事情)...

根据此链接FrameworkElementFactory

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

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

而“其他模板”(默认模板)——实际上是TextBlock沿着这些线构造了 and ——它实际上采用了隐式样式。

坦率地说,这就是我所能得出的结论,没有了解整个 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);
}
于 2013-04-17T11:54:01.383 回答
0

在经历了这个问题和大家的宝贵意见之后,我对 TextBlock Styling 做了一些研究。

据我了解,这里的问题不在于标签或文本块,而在于内容呈现器和使用内容呈现器的控件,如标签、按钮和 ComboBoxItem。

来自 MSDN 的内容演示者的属性之一:http: //msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx

" 如果有一个 TypeConverter 将 Content 的类型转换为字符串,ContentPresenter 使用该 TypeConverter 并创建一个 TextBlock 来包含该字符串。显示 TextBlock"

在上面的示例中,对于 SomeString 内容演示者将其转换为文本块并应用 TextBlock 边距 (10) 以及标签边距 (10) 使其变为 20。

为了避免这种情况,您需要覆盖 contentpresenter 中的 TextBlock 样式,如下所示

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

以下是对您的代码的更改。

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

这个解释只是基于我的理解。让我知道你的意见。

谢谢

于 2013-04-17T15:45:06.550 回答
0

根据我的评论,我在问题中添加了更多信息。它不是一个直接的答案,但为所描述的问题提供了额外的信息。

下面的 XAML 将直接在 Visual Studio 的设计器中显示所描述的行为,我已将其缩小到 ContentPresenter,这似乎是问题的根源。该样式适用于第一个 ContentPresenter (intPresenterboolPresenter),但不是最后一个使用字符串作为 Content ( 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>

在调试器中,我分析了stringPresenter使用 DefaultStringTemplate 而intPresenter不是。

在此处输入图像描述

也很有趣的是Languageof theintPresenter被设置,而由stringPresenter它不是。

该方法的实现看起来像这样(取自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 和 AccessTextTemplate 使用 FrameworkElementFactory 来生成视觉效果。

于 2013-04-17T19:22:15.620 回答