Pytanie Dlaczego domyślny styl TextBlock zostaje zastosowany podczas wiązania Label.Content z łańcuchem innym niż ciąg, ale bez łańcucha?


Patrzyłem to pytanie, i odkrył, że wiążące Label.Content do wartości innej niż łańcuchowa zostanie zastosowana niejawna TextBlock styl, jakkolwiek wiązanie z ciągiem nie jest.

Oto przykładowy kod do odtworzenia problemu:

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

Gdzie kod dla wartości związanych są

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

A wynik końcowy wygląda tak, z Margin właściwość z niejawnego stylu TextBlock, który zostanie zastosowany do etykiety powiązanej tylko z tekstem innym niż łańcuch:

enter image description here

Obie etykiety są renderowane jako

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

Kiedy sprawdzam VisualTree z Podejrzeć, Widzę, że wygląda dokładnie tak samo dla obu elementów, z wyjątkiem 2. TextBlock stosuje Margines z domyślnego stylu, podczas gdy pierwszy nie.

enter image description here

Użyłem Blend, aby wyciągnąć kopię domyślnego szablonu Label, ale nie widzę w tym nic dziwnego, a kiedy zastosuję szablon do obu etykiet, to samo się stanie.

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

Należy również zauważyć, że ustawienie domyślne ContentTemplate do a TextBlock powoduje, że oba elementy są renderowane bez domyślnego stylu, więc musi mieć coś wspólnego z tym, kiedy WPF próbuje renderować wartość inną niż łańcuchowa jako część interfejsu użytkownika.

<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

Jaka jest logika, która powoduje, że ciąg niewysłany w interfejsie może być narysowany przy użyciu niejawnego stylu TextBlock, ale ciąg wstawiony do interfejsu użytkownika nie jest? I gdzie to się dzieje?


18
2018-04-11 14:30


pochodzenie


To pytanie mnie zaintrygowało, więc musiałem uruchomić stary reflektor. Cóż, jest tylko jedna rzecz, którą mogłem znaleźć do tej pory. Wygląda na to, że TextBlock rozróżnia proste i "złożone" obiekty, używa dwóch różnych podklas dla obu. Niestety, tam się kończy. Nie mogę znaleźć żadnego śladu specjalistycznego stylu ani niczego. Jedną z rzeczy, którą należy wypróbować, jest dostarczenie ContentTemplate z TextBlock i sprawdzenie, czy to coś zmieni. W takim przypadku może mieć coś wspólnego z tym mistycznym szablonem domyślnym. - dowhilefor
@dowhilefor Próbowałem wyciągnąć domyślny szablon z Blend i zastosować go do obu etykiet, i to samo się dzieje (pytanie zaktualizowane o te informacje). Przypuszczam, że tylko WPF wie, aby renderować System.String jak TextBlock bez domyślnej stylizacji, ale gdy chodzi o renderowanie wartości nie-łańcuchowej, rysuje ją jako TextBlock związany z .ToString() obiektu i stosuje dowolne ukryte stylizacje. - Rachel
@dowhilefor Masz rację, ale to ustawienie domyślne ContentTemplate do a <TextBlock Text="{Binding }" /> powoduje, że oba elementy są renderowane bez domyślnego stylu, więc wygląda na to, że ma coś wspólnego z domyślnym ContentTemplate dla obiektów nie będących ciągami - Rachel
Ok, mam więcej informacji. Myślę, że powodem tego jest sam ContentPresenter, jego domyślny szablon wydaje się być skonstruowany w kodzie. Klasa wewnętrzna jest w rzeczywistości nazywana DefaultTemplate, a wewnątrz znajduje się tworzenie obiektu TextBlock. Wciąż nie mam pojęcia, skąd bierze się zgłoszenie stylu, ale uznałbym, że jest to możliwe z powodu kolejności tworzenia i obsługi różnych treści. - dowhilefor
To nie jest tylko problem z etykietą. Można to również odtworzyć za pomocą ContentControl (typ podstawowy etykiety). I nie jest to problem wiążący, ponieważ ten sam efekt może zostać odtworzony podczas definiowania i Int32 oraz String jako zasób i użycie go jako StaticResource / DynamicResource - Jehof


Odpowiedzi:


EDYTOWAĆ: (może przenieść to na sam dół?)

I szturchnąłem trochę więcej - i Myślę, że dotarłem do sedna problemu (w / nacisk na "myślę")

Umieść to w niektórych Button1_Click lub coś (znowu musimy "leniwy" na tym - ponieważ potrzebujemy zbudowanego wizualnego drzewa - nie możemy tego zrobić na "Załadowanym", ponieważ właśnie stworzyliśmy szablony - wymagało to lepszej techniki inicjalizacji, ale to tylko test, więc kto dba)

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

Jak wspomniano tutaj Niejawne style w Application.Resources vs Window.Resources?
The FindImplicitStyleResource (w FrameworkElement) używa czegoś takiego jak ...

boundaryElement = fe.TemplatedParent;  

I wydaje się, że Jeżeli nie ma TemplatedParent (i ze względu na sposoby    TextBlock jest zbudowany w DefaultTemplate) - tam   nie są zbiorem "granic" - i wyszukaj ukryte zasoby / style -   propaguje do końca.



Oryginalna odpowiedź: (przeczytaj to pierwsze, jeśli właśnie przybyłeś)

(@dowhilefor i @Jehof już poruszyli najważniejsze rzeczy)
Nie jestem pewien, czy jest to "odpowiedź" jako taka - wciąż jest to domysły - ale potrzebowałem więcej miejsca, żeby wyjaśnić, co myślę dzieje.

Możesz znaleźć kod źródłowy "ContentPresenter" w Internecie - jest to łatwiejsze niż użycie reflektora - po prostu "google", nie zamieszczam go tutaj z oczywistych powodów :)

Chodzi o to ContentTemplate to jest wybrane dla ContentPresenter (i w tej kolejności) ...

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)

I rzeczywiście nie ma to nic wspólnego z Label ale dowolny ContentControl lub szablon kontrolny, który używa ContentPresenter. Lub możesz powiązać zasoby itp.

Tutaj jest Repro z tego, co się dzieje w środku - moim celem było odtworzenie podobnego zachowania dla "struny" lub dowolny rodzaj treści.

W XAML wystarczy "nazwać" etykiety (i to nie jest literówka, celowo umieścić struny w obu, aby wyrównać pole gry) ...

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

Oraz z kodu (minimalny kod, który naśladuje to, co robi prezenter):
uwaga: Zrobiłem to Loaded ponieważ potrzebowałem dostępu do prezentera, który został domyślnie stworzony

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

Pierwsza część (dla _labelString) robi to, co szablon "tekstowy" dla łańcuchów.

Jeśli ty return Zaraz po tym - otrzymasz dwa identyczne pola, bez domyślnego szablonu.

Druga część (dla _labelDecimal) naśladuje "szablon domyślny", który jest wywoływany dla "liczby dziesiętnej".

Wynik końcowy powinien zachowywać się tak samo jak oryginalny przykład. My   skonstruował szablony jak dla string i decimal - ale my   może umieścić cokolwiek w treści (jeśli oczywiście ma to sens).

Dlaczego? - Domyślam się, że jest coś takiego (choć dalekie od pewności - ktoś podejdzie z czymś bardziej rozsądnym, jak sądzę) ...

Zgodnie z tym linkiem FrameworkElementFactory 

Ta klasa jest przestarzałym sposobem programowego tworzenia szablonów,   które są podklasami FrameworkTemplate, takimi jak ControlTemplate lub   DataTemplate; nie wszystkie funkcje szablonu są dostępne, gdy   tworzysz szablon przy użyciu tej klasy. Zalecany sposób   programowo utworzyć szablon jest ładowanie XAML z ciągu lub   strumień pamięci za pomocą metody Load klasy XamlReader.

I zgaduję, że nie wywołuje on żadnych zdefiniowanych stylów dla TextBlock.

Podczas gdy "inny szablon" (szablon domyślny) - faktycznie tworzy TextBlock i gdzieś wzdłuż tych linii - w rzeczywistości wychwytuje ukryty styl.

Szczerze mówiąc, to tyle, na ile mogłem dojść do konkluzji, bez przechodzenia przez wszystkie wewnętrzne elementy WPF i jak / gdzie stosuje się style.


Użyłem tego kodu Znalezienie kontroli w ramach WPF itemscontrol dla FindVisualChild.
I SetProperty jest tylko odbiciem - dla tej jednej nieruchomości potrzebujemy dostępu, aby móc to wszystko zrobić. na przykład

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
2018-04-17 11:54





Po przejściu tego pytania i cennych uwag ze wszystkich, zrobiłem kilka badań na temat TextBlock Styling.

W moim rozumieniu problem tutaj nie jest związany z Label lub TextBlock, jest on z contentnresenter i formanty, które używają contentnresenter jak Label, button i ComboBoxItem.

Jedna z właściwości prezentera treści z MSDN: http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx

"Jeśli istnieje TypeConverter, który konwertuje typ Treści na ciąg, ContentPresenter używa tego TypeConvertera i tworzy TextBlock, aby zawierał ten ciąg." Wyświetlana jest blokada tekstu "

W powyższym przykładzie program prezentujący treść SomeString Content konwertuje go na blok tekstu i nakłada margines TextBlock (10) wraz z marginesem etykiety (10), co daje 20.

Aby uniknąć tego scenariusza, należy zastąpić styl TextBlock w edytorze treści, jak pokazano poniżej

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

Poniżej przedstawiono zmiany w kodzie.

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

To wyjaśnienie opiera się tylko na moim zrozumieniu. Daj mi znać swoje komentarze.

Dzięki


0
2018-04-17 15:45



Właściwie dodałem trochę do mojego pytania, które wyjaśnia, że ​​jeśli nadpiszesz ContentTemplate etykiety przestaje stosować niejawny styl TextBlock. Moje pytanie dotyczy raczej tego, dlaczego umieszczanie łańcucha w interfejsie robi zastosuj niejawny styl, a umieszczenie nie-ciągu (który jest następnie konwertowany na ciąg znaków) w interfejsie użytkownika nie. Myślę, że możesz być blisko z wyceną MSDN. Podejrzewam, że proces, który używa TypeConverter stworzyć TextBlock stosuje dowolne niejawne style, niezależnie od granic szablonów. - Rachel
@Rachel - TypeConverter właśnie tam zrobiłem textBlock.Text = presenter.Content.ToString(); (Mogę opublikować kod - i pełny link, jeśli chcesz - ale byłem niechętny, aby się tym dzielić) - wypełnia tekst na samym "końcu". Nie ma to nic wspólnego z tworzeniem TextBlock (szablony). Zobacz moje EDIT - problem jest z boundaries tak właściwie. - NSGaga


Według mojego komentarza dodaję więcej informacji do pytania. Nie jest to bezpośrednia odpowiedź, ale dostarcza dodatkowych informacji do opisanego problemu.

Poniższy XAML wyświetli opisane zachowanie bezpośrednio w Designer of Visual Studio i zawęziłem go do ContentPresenter, który wydaje się być źródłem problemu. Styl zostanie zastosowany do pierwszego elementu ContentPresenter (intPresenter i boolPresenter), ale nie ostatni, który używa ciągu jako Treści (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>

W debugerze przeanalizowałem, że stringPresenter używa DefaultStringTemplate podczas gdy intPresenter nie.

enter image description here

Jest to również interesujące, że Language z intPresenter jest ustawiony, a przez stringPresenter to nie jest.

A implementacja metody wygląda podobnie (wzięta z 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;
      }
    }

The StringContentTemplate i AccessTextTemplate używają FrameworkElementFactory do generowania Visualów.


0
2018-04-17 19:22