Подтвердить что ты не робот

Почему применяется неявный стиль TextBlock при привязке Label.Content к нестроковой, но не к строке?

Я смотрел этот вопрос и обнаружил, что привязка 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 применяется к метке, привязанной только к нестроковой:

enter image description here

Обе метки получаются как

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

Когда я просматриваю VisualTree с Snoop, я вижу, что он выглядит точно так же для обоих элементов, кроме 2-го TextBlock применяет маржу из неявного стиля, а первый - нет.

enter image description here

Я использовал Blend, чтобы вытащить копию шаблона Label по умолчанию, но не вижу там ничего странного, и когда я применяю шаблон к обоим ярлыкам, происходит то же самое.

<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 пытается отобразить нестроковое значение как часть пользовательского интерфейса.

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

enter image description here

Какова логика, которая заставляет нестроку, вставленную в пользовательский интерфейс, рисоваться с использованием неявного стиля TextBlock, но строка, вставленная в пользовательский интерфейс, не работает? И где это происходит в?

4b9b3361

Ответ 1

EDIT: (возможно, переместите это на дно?)

И я ткнул немного больше - и Я думаю, что попал в суть проблемы (с акцентом на "я думаю" )

Поместите это в некоторый Button1_Click или что-то (опять же, нам нужно идти "лениво" на это - так как нам нужно построение визуального дерева - мы не можем сделать это на "Loaded", поскольку мы просто сделали шаблоны - это требовало лучшей техники инициализации, правда, но это просто тест, которому кто-то заботится)

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?
FindImplicitStyleResourceFrameworkElement) использует что-то вроде...

boundaryElement = fe.TemplatedParent;  

И кажется, что , если нет TemplatedParent (и из-за способов TextBlock построен внутри DefaultTemplate) - там не установлены "границы" - и поиск неявных ресурсов/стилей - распространяется полностью.

<ч/" >


Исходный ответ: (сначала прочитайте это, если вы только что прибыли)

(@dowhilefor и @Jehof уже коснулись основных вещей)
Я не уверен, что это "ответ" как таковой - это все еще предположение, но мне нужно больше места, чтобы объяснить, что я думаю.

Вы можете найти код "ContentPresenter source" в Интернете - это проще, чем использовать рефлектор - просто "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"/>

И из кода позади (минимальный код, который подражает тому, что делает ведущий):
note: Я сделал это на 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) делает шаблон "text" для строк.

Если вы return сразу после этого - вы получите два одинаковых окна, без имплицированного шаблона.

Вторая часть (для _labelDecimal) имитирует "шаблон по умолчанию", который вызывается для "десятичного".

Конечный результат должен вести себя так же, как в исходном примере. Мы построил шаблоны как для string, так и decimal, но мы может помещать что-либо в контент (если это имеет смысл, конечно).

Что касается того, почему - моя догадка - это что-то вроде этого (хотя далека от уверенности - кто-то будет прыгать с чем-то более разумным, я думаю)...

В соответствии с этой ссылкой FrameworkElementFactory

Этот класс является устаревшим способом программного создания шаблонов, которые являются подклассами FrameworkTemplate, такими как ControlTemplate или DataTemplate; не все функциональные возможности шаблона доступны, когда вы создаете шаблон, используя этот класс. Рекомендуемый способ программным способом создать шаблон - загрузить XAML из строки или поток памяти, используя метод Load класса XamlReader.

И я предполагаю, что он не вызывает никаких определенных стилей для TextBlock.

В то время как "другой шаблон" (шаблон по умолчанию) - фактически создает TextBlock и где-то вдоль этих строк - он фактически подбирает неявный стиль.

Откровенно говоря, это было настолько, насколько я был в состоянии заключить, не пройдя через всю внутреннюю структуру WPF и как/где на самом деле стили применяются.

<ч/" > Я использовал этот код Поиск элемента управления в элементах управления WPF для 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);
}

Ответ 2

Пройдя этот вопрос и ценные комментарии от всех, я провел некоторое исследование по TextBlock Styling.

Насколько я понимаю, проблема здесь заключается не в Label или TextBlock, а в составе contentpresenter и элемента управления, которые используют contentpresenter, такие как Label, button и ComboBoxItem.

Одно из свойств ведущего контента из MSDN: http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx

"Если есть TypeConverter, который преобразует тип Контента в строку, ContentPresenter использует этот TypeConverter и создает TextBlock, чтобы содержать эту строку. TextBlock отображается"

В приведенном выше примере для ContentString Content презентер преобразует его в текстовый блок и применяет поле TextBlock (10) вместе с маркером метки (10), что делает его 20.

Чтобы избежать этого сценария, вам необходимо переопределить стиль TextBlock в contentpresenter, как показано ниже.

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

Это объяснение основано только на моем понимании. Дайте мне знать ваши комментарии.

Спасибо

Ответ 3

В соответствии с моим комментарием я добавляю дополнительную информацию к вопросу. Это не прямой ответ, но предоставляет дополнительную информацию описанной проблеме.

В приведенном ниже примере XAML будет отображаться описанное поведение непосредственно в конструкторе Visual Studio, и я сузил его до ContentPresenter, который, кажется, является источником проблемы. Стиль применяется к первым и ContentPresenter (intPresenter и boolPresenter), но не к последнему, который использует строку как 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 для создания визуальных образов.