0
votes

Warning: this is not a question, but a recap of WPF Style working. The question is if this summary is right.

I read that in a style definition you can get rid of the TargetType if you include the control's class name in the Property name. That is this:

<Style x:Key="SomeKey" TargetType="{x:Type Button}">
        <Setter Property="Foreground" Value="Red"/>
</Style>

becomes this:

<Style x:Key="SomeKey">
        <Setter Property="Button.Foreground" Value="Red"/>
</Style>

Oh great, this means that, given three controls:

<StackPanel Height="40" Orientation="Horizontal">
    <Button Style="{StaticResource MyStyle}" Content="First"/>
    <TextBox Style="{StaticResource MyStyle}" Text="Second"/>
    <Label Style="{StaticResource MyStyle}" Content="Third"/>
</StackPanel>

I can do something like this:

<Window.Resources>
    <Style x:Key="MyStyle">

        <Setter Property="Button.Foreground" Value="Red"/>
        <Setter Property="TextBox.BorderBrush" Value="DarkBlue"/>
        <Setter Property="Label.Background" Value="LightPink"/>

        <Setter Property="Control.Margin" Value="4,0,0,0"/> 

    </Style>
</Window.Resources>

That is: first button should be standard but with red text; second should be standard but with DarkBlue Border. Third should be with LightPink foreground.
This is what I get instead:

enter image description here

That is: except for the third that is a label and its BorderThickness defaults to 0, every style goes to every control.

After a bit of digging it seems to me that all the mechanics of applying a style boils down to what follows. Since it seems to be quite rudimentary, I'm wondering if this description is right or if I'm missing something important.

Phase 1: defining a style

1.a A style gets a key explicitly if x:Key is set (<Style x:Key="MyStyle">)
1.b If x:Key is not set, TargetType has to be set. (<Style TargetType="{x:Type Button}">). Internally, it assigns the style a key that is the name of the type specified in TargetType
1.c If both TargetType is set and one or more Setter are defined with the syntax <Setter Property="Class.Property" Value=.../>, there is no check of consistence between the TargetType and the Classes values in the Setters. That is, this is legal:

<Style x:Key="MyStyle" TargetType="{x:Type Button}">
    <Setter Property="Control.Margin" Value="4,0,0,0"/>
</Style> 

even if it has little sense, while the following may be useful in (at least) one case.

<Style x:Key="MyStyle" TargetType="{x:Type Control}">
    <Setter Property="TextBox.Text" Value="just an example"/>
</Style> 

That is almost everything regarding style definition.

Pahse 2: associating the style with controls

2.a Does the control have a style defined (<Button Style="{StaticResource MyStyle}">)? Lookup in the resources if there is such a style. If there is, check if it has also a TargetType; if the control is not that class of a subclass, raise the exception:

XamlParseException. Exception: Cannot find resource named 'MyStyle'. Resource names are case sensitive

So, in this case TargetType is not meant for filtering. It just has to match.

2.b If the control doesn't have a style defined, lookup the resources for a style with the key equals to the control's class name. This comes from a style defined with only TargetType. If found, go on to apply phase.
Notice that styles that are defined with a TargetType of a superclass of the control wont be applied. They have to match exactly. This is another 'limitation' of a style system that is far from the complexity of CSS; WPF styles seem to support little more than a dictionary lockups.

Phase 3: Applying the style

At this point, a control has a style to apply. If the style defined a TargetType, it matches with the control (the control is of that type or a subtype of the type defined in the style applied). The style is a set of Setters that may or may not have their own specification of target control(with: <Setter Property="Control.Foreground" Value="Red"/>; without: <Setter Property="Foreground" Value="Red"/>).
It seems (and my previous example seems to proof it) that by design the class specification at the setter level is not meant for further refining or filtering. What is meant for? Good questions, I'll go back to it later on.
How does it work then? It simply ignores the class information (if present) and it goes on trying to apply every setter to the control. That is why even if I defined a Setter like this:

<Setter Property="Label.Background" Value="LightPink"/>

all of the three controls get the LightPink background, not only the Label.

And that's the reason of this post. I couldn't find a complete explanation of how styles really work. All the information that I could find was limited to showcasing some basic usage; that is they don't show you how to approach a complex solution that requires a much more detailed knowledge.

Lastly, why should I ever specify classes names in Setters if the famework doesn't use them for filtering; it's just a useless repetition. Why that feature at all?

    <Style x:Key="MyStyle">
        <Setter Property="Control.Foreground" Value="Red"/>         
        <Setter Property="Control.BorderBrush" Value="DarkBlue"/>
        <Setter Property="Control.Background" Value="LightPink"/>
        <Setter Property="Control.Margin" Value="4,0,0,0"/>
    </Style>

The only case that I could think of is this; i cant set a "Text" property without specifying TextBox, since Control doesn't have a Text property. Again, it doesn't mean filtering and I suppose that if another control, not a subclass of Text had a different Text property, it would get set also.

    <Style x:Key="MyStyle" TargetType="{x:Type Control}">
        <Setter Property="Foreground" Value="Red"/>         
        <Setter Property="BorderBrush" Value="DarkBlue"/>
        <Setter Property="Background" Value="LightPink"/>
        <Setter Property="Margin" Value="4,0,0,0"/>
        <Setter Property="TextBox.Text" Value="just a test"/>
    </Style>
2

2 Answers

1
votes

I read that in a style definition you can get rid of the TargetType if you include the control's class name in the Property name.

Yes that is true according the reference of Style.

[...] except for the third that is a label and its BorderThickness defaults to 0, every style goes to every control.

A style has a specific target type, which it can be applied to. If you define a style without a target type, it will default to IFrameworkInputElement. Both FrameworkElement and FrameworkContentElement implement this interface, meaning it applies to almost any element, including Button, TextBox and Label.

Let us have a look at the properties that you have defined in this style.

  • Foreground is defined on TextElement, but button exposes it by adding Control as its owner.
  • BorderBrush is defined on Border, but TextBox exposes it by adding Control as its owner.
  • Background is defined on Panel, but Control exposes it by adding Control as its owner and Label is a derivative of Control, so it inherits it.
  • Margin is defined on FrameworkElement and Control inherits it.

What is mean by adding a control as owner is that the corresponding controls do not define the dependency properties themselves, but "borrow" them from others using the AddOwner method.

This leads to what you see in your example, the properties are effectively defined for all Controls because the default TargetType does not limit the types apart from framework and framework content elements and due to the way that the affected dependency properties are implemented.

Phase 1: Defining a style

1.a A style gets a key explicitly if x:Key is set (<Style x:Key="MyStyle">)

Yes. That is called an explicit style, because you have to apply it to each target control yourself.

1.b If x:Key is not set, TargetType has to be set. (<Style TargetType="{x:Type Button}">). Internally, it assigns the style a key that is the name of the type specified in TargetType

Not in general. If you define a style in a resource dictionary, yes. The key will be of type object, so not the name of the type, but the Type itself will be its key. If you define a style directly within the Style property of a control, you neither need a key, nor a target type.

1.c If both TargetType is set and one or more Setter are defined with the syntax , there is no check of consistence between the TargetType and the Classes values in the Setters.

Yes. Your first example is not illegal, since a button inherits its Margin property from FrameworkElement, it is just redundant. The follwing setters are essentially the same in this case.

<Setter Property="FrameworkElement.Margin" Value="4,0,0,0"/>
<Setter Property="Control.Margin" Value="4,0,0,0"/>
<Setter Property="Margin" Value="4,0,0,0"/>

I do not think that your second example makes much sense. When you define a key, you have to apply the style explicitly to a control and that makes only sense for TextBox, so you could define it as target type.

Phase 2: Associating the style with controls

2.a Does the control have a style defined ()? Lookup in the resources if there is such a style. If there is, check if it has also a TargetType; if the control is not that class of a subclass, raise the exception [...]

Basically yes, the target type has to match the type or a derivative. The resource lookup differs between StaticResource and DynamicResource. Please refer to the linked source to get a better understanding.

It would throw an InvalidOperationException wrapped in a XamlParseException, if the types do not match. The exception that you show is different and indicates that the style is not found at all.

2.b If the control doesn't have a style defined, lookup the resources for a style with the key equals to the control's class name. This comes from a style defined with only TargetType.

Yes, the type has to match the type of the control exactly with implicit styles.

Notice that styles that are defined with a TargetType of a superclass of the control wont be applied. They have to match exactly. This is another 'limitation' of a style system that is far from the complexity of CSS;

Yes this is a corollary for implicit styles. The key is the type of the target control and if that is a base type of the control both types are not equal.

WPF styles seem to support little more than a dictionary lockups.

If you look closely, a ResourceDictionary is exactly that, a dictionary of resources. Applying resources is looking up a key defined as x:Key in said dictionary.

Phase 3: Applying the style

How does it work then? It simply ignores the class information (if present) and it goes on trying to apply every setter to the control. [...] all of the three controls get the LightPink background, not only the Label.

The class information is not disregarded, as explained in the beginning.

Lastly, why should I ever specify classes names in Setters if the famework doesn't use them for filtering; it's just a useless repetition. Why that feature at all?

Look at it the other way around. Normally, you would have to specify the target type, e.g. Button in each and every setter explicitly. Defining a TargetType that lets you omit the full qualification is a convenience feature. Moreover, as you examples have shown you can use this to apply style to multiple controls.

1
votes
<StackPanel Height="40" Orientation="Horizontal">
    <Button Style="{StaticResource MyStyle}" Content="First"/>
    <TextBox Style="{StaticResource MyStyle}" Text="Second"/>
    <Label Style="{StaticResource MyStyle}" Content="Third"/>
</StackPanel>

<Style x:Key="MyStyle">
    <Setter Property="Button.Foreground" Value="Red"/>
    <Setter Property="TextBox.BorderBrush" Value="DarkBlue"/>
    <Setter Property="Label.Background" Value="LightPink"/>
    <Setter Property="Control.Margin" Value="4,0,0,0"/> 
</Style>

Qualifying the base property via a subclass/derived type is always possible. The XAML parser will automatically infer the base type of the DependencyProperty by accessing the DependencyProperty.OwnerType property. For example, since Foreground is defined on the type Control, the parser will translate Button.Foreground to Control.Foreground. Since all three elements extend Control, the properties can be resolved on each.

This also adds explanation to your Phase 3 section:

"Lastly, why should I ever specify classes names in Setters if the famework doesn't use them for filtering; it's just a useless repetition. Why that feature at all?"

As said before, this is the class qualifier to reference the member of a class. It is always required to reference the exact memory location in order to distinguish or allow duplicate member names (of different calsses): Button.Foreground. The complete reference (FQN Fully Qualified Name) also includes the namespace: System.Windows.Controls.Button.Foreground. The qualification of the namespace can be avoided when importing the namespace either C# using System.Windows.Controls; or XAML xmlns:system="clr-namespace:System;assembly=mscorlib (note that the System.Windows.Controls namespace is imported by default in XAML scope).

In XAML you can shorten the qualification further by specifying the TargetType on e.g., a Style or ControlTemplate. This allows you to omit the class qualifier since the the XAML parser knows that all referenced properties in the current scope refer to the type specified by the TargetType property.

"If there is, check if it has also a TargetType; if the control is not that class of a subclass, raise the exception:

XamlParseException. Exception: Cannot find resource named 'MyStyle'. Resource names are case sensitive

So, in this case TargetType is not meant for filtering. It just has to match."

Wrong. The exception you were showing is thrown when the resource key was not found. In case the Style.TargetType doesn't match with the referencing element type, a type mismatch exception is thrown.

"styles that are defined with a TargetType of a superclass of the control wont be applied. They have to match exactly."

Wrong. The following example applies a Style that targets a superclass to a subclass. If this Style would be implicit (without x:Key defined) then it would automatically apply to all types of ButtonBase and all subtypes too within the styles scope:

<Style x:Key="ButtonBaseStyle" TargetType="ButtonBase">
  <Setter Property="BorderBrush" Value="Red"/>
</Style>

<Button Style="{StaticResource ButtonBaseStyle}" />
<ToggleButton Style="{StaticResource ButtonBaseStyle}" />