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>

VisualTree をSnoopでチェックアウトすると、2 番目の TextBlock が暗黙のスタイルから Margin を適用し、最初の TextBlock は適用しないことを除いて、両方の要素がまったく同じに見えることがわかります。

ここに画像の説明を入力

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

ここに画像の説明を入力

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 vs Window.Resources の Implicit styles?
( FindImplicitStyleResourcein FrameworkElement) は次のようなものを使用します...

boundaryElement = fe.TemplatedParent;  

そして、存在しない場合TemplatedParent(およびTextBlock内で が構築される方法によりDefaultTemplate) 、「境界」セットが存在せず、暗黙的なリソース/スタイルを検索すると、ずっと伝播するようです。



元の回答: (到着したばかりの場合は、最初にこれをお読みください)

(@dowhilefor と @Jehof はすでに主要な事柄に触れています)
これが「答え」であるかどうかはわかりませんが、これはまだ推測の作業です。

Web で「ContentPresenter ソース」コードを見つけることができます。リフレクターを使用するよりも簡単です。「google」するだけです。明らかな理由から、ここには投稿しません :)

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

最初の部分 ( for _labelString) は、'text' テンプレートが文字列に対して行うことを行います。

そのreturn直後に、2 つの同じ外観のボックスが表示されますが、暗黙的なテンプレートはありません。

2 番目の部分 ( for _labelDecimal) は、「10 進数」に対して呼び出される「デフォルト テンプレート」を模倣します。

最終結果は、元の例と同じように動作するはずです。stringandについてはテンプレートを作成しましたdecimalが、コンテンツには何でも入れることができます (もちろん意味がある場合)。

理由について- 私の推測では、このようなものです (確かではありませんが、誰かがもっと賢明なものに飛び込むと思います)...

このリンクに従ってFrameworkElementFactory

このクラスは、ControlTemplate や DataTemplate などの FrameworkTemplate のサブクラスであるテンプレートをプログラムで作成する非推奨の方法です。このクラスを使用してテンプレートを作成する場合、すべてのテンプレート機能を使用できるわけではありません。テンプレートをプログラムで作成するための推奨される方法は、XamlReader クラスの Load メソッドを使用して、文字列またはメモリ ストリームから XAML を読み込むことです。

そして、TextBlock に対して定義されたスタイルを呼び出さないと思います。

「他のテンプレート」(既定のテンプレート) は、実際TextBlockにはこれらの行に沿って and を構築しますが、実際には暗黙的なスタイルを選択します。

率直に言って、WPF の「内部」全体と、スタイルが実際にどのように/どこに適用されるかを説明するまでは、私が結論付けることができたのはこれだけです。


このコードFinding control within WPF itemscontrol forを使用しましたFindVisualChild
そして、これSetPropertyは単なるリフレクションです。その 1 つのプロパティに対して、これらすべてを実行できるようにするためにアクセスする必要があります。例えば

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

私のコメントによると、質問にさらに情報を追加します。直接的な答えではありませんが、説明されている問題の追加情報を提供します。

以下の 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いないことを分析しました。

ここに画像の説明を入力

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 を使用してビジュアルを生成しています。

于 2013-04-17T19:22:15.620 に答える