为什么绑定时Label.Content到非字符串,而不是一个字符串隐式TextBlock的风格得到应

2019-08-21 21:58发布

我一直在寻找这个问题 ,并发现结合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>
    <Border>
        <ContentPresenter>
            <TextBlock />
        </ContentPresenter>
    </Border>
</Label>

当我检查了与的VisualTree 史努比 ,我可以看到,它看起来完全两个元素是相同的,不同的是二次TextBlock的应用从含蓄风格的保证金,而先不。

我用混合拉出默认标签模板的一个副本,但看不出有什么奇怪那里,当我申请的模板,我的两个标签,同样的事情发生。

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

还应当指出的是,默认设置ContentTemplateTextBlock确实让这两个项目没有渲染的隐式样式,所以它必须有一些做的时候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字符串不? 哪里会发生这种情况的?

Answer 1:

编辑:(也许这个移动的底部?)

我戳多一点- 我想我抓住了问题的症结所在 (W /强调“我觉得”)

将此放入一些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隐风格VS Window.Resources?
FindImplicitStyleResource (在FrameworkElement )使用类似...

boundaryElement = fe.TemplatedParent;  

而且看来, 如果没有TemplatedParent (并且由于方式TextBlock是内构建DefaultTemplate ) - 有没有“边界”集-和搜索隐性资源/风格-传播的所有道路



原来的答案:(第一次读这个,如果你刚到)

(@dowhilefor和@Jehof已经触及了主要的东西)
我不知道这是一个“答案”这样 - 它仍然是一个猜测的工作 - 但我需要更多的空间来解释,我想是怎么回事。

你可以在网上找到了“ContentPresenter源”代码 - 这比使用反射更容易 - 只是“谷歌”吧,我不是在这里张贴了明显的原因:)

这是对ContentTemplate被选择的ContentPresenter (和顺序)...

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

(对于第一部分_labelString )做什么“文”模板确实为字符串。

如果你return后正确的-你会得到两个相同的看着箱子,没有隐含的模板。

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

最终的结果应该表现一样的原来的例子。 我们构建的模板作为stringdecimal -但我们可以在内容把任何东西(如果它使课程的意义上)。

至于为什么 -我的猜测是这样的(虽然远一些-有人会跳的东西更明智的我猜的)...

按照该链接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);
}


Answer 2:

通过这个问题,并提出宝贵的意见,从一切会后,我已经做TextBlock的样式一些研究。

据我了解这里的问题是不是与标签或TextBlock的,它与contentpresenter和使用contentpresenter像标签,按钮和ComboBoxItem控制。

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

“如果有内容的类型转换为字符串的TypeConverter,中,ContentPresenter使用该类型转换器,并创建一个TextBlock以包含字符串,显示TextBlock的”

在上面的例子,对于SomeString内容演示者转换成文本块,并用标签余量(10),使得20一起施加的TextBlock余量(10)。

为了避免这种情况,你需要重写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>

这种解释只是根据我的理解。 让我知道您的意见。

谢谢



Answer 3:

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

在下面的XAML将在Visual Studio的设计师直接显示所描述的行为,我已经把范围缩小到ContentPresenter,这似乎是问题的根源。 样式被应用到第一ContentPresenter(包括intPresenterboolPresenter ),而不是使用一个字符串作为内容(最后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没有。

它也很有趣的Language中的intPresenter设置,同时由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产生的视觉效果。



文章来源: Why does an implicit TextBlock style get applied when binding Label.Content to a non-string, but not a string?