Reputation: 7683
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:
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.
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.
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.
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>
Upvotes: 1
Views: 1705
Reputation: 22119
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 Control
s 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.
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.
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.
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.
Upvotes: 1
Reputation: 29028
<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}" />
Upvotes: 1