When I create a style using XAML:
<Style x:Key="tbxWithValidation" TargetType="TextBox">
<Style.Setters>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource errorTemplate}"/>
...
</Style.Setters>
</Style>
what does mean this declaration? Do I create a class or an instance?
I assume I create an instance of class Style, but I wonder in this case if this instance will be used each time I use the resource.
In other words, does Style="{StaticResource tbxWithValidation}" reuse the same instance, or does it create a new instance so that each control has its own style instance?
I'm asking this question, because while this may be not important for a style (maybe controls can share the same style instance without problems, not sure...), it seems to me there would be a problem with declaring a control template and using it multiple times (this template is used in the style above):
<ControlTemplate x:Key="errorTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,0,5,0" Foreground="Red" FontWeight="Bold" Text="!"/>
<AdornedElementPlaceholder/>
</StackPanel>
</ControlTemplate>
Can you help me clarifying this (I'm learning WPF)?
<Style x:Key="TbxWithValidation" TargetType="TextBox">
<Style.Setters>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ErrorTemplate}"/>
...
</Style.Setters>
</Style>
This declaration instructs the XAML parser create an instance of the class Style.
In this case, the Style (or the resource in general) and all nested ResourceDictionary or referenced objects (and therefore also the the ErrorTemplate) is 'reused' for every reference to it.
This is because of the x:Shared attribute. This attribute is implicitly set and true by default. When true this attribute instructs the XAML parser to reuse the instance of the object. You can set it explicitly to false:
<Style x:Shared="False" x:Key="TbxWithValidation" TargetType="TextBox">
...
</Style>
By design each instance of an object is allowed to exist only once in the object graph. 'Reuse' or sharing (x:Shared="True") instructs the XAML parser to internally create a copy of the shared instances, so that the XAML parser can insert them in various places in the element tree. But since this are copies they are actually referencing the same shared instance.
An exception is made to all objects that extend UIElement. Instances of those objects can only exist once in the object graph: that's why elements like System.Windows.Controls.Image will magically disappear when used in multiple positions in the graph e.g. like when a single icon is used with multiple Button elements. In this case only one icon at time would be visible.
Instances of UIElement can not be shared (referenced by more than one resource instance) - they are unique. This means the author has to create the required number of instances explicitly.
This is why e.g. when creating a Button instance in C# (code-behind) and then add it to e.g. two different Grid elements the following exception is thrown:
Specified element is already the logical child of another element.
So, this means a shared resource like a Style is only critical when it declares UIElement objects (like Image) that are referenced in more than one resource:
<Style x:Key="SaveButton" TargetType="ButtonBase">
<Setter Property="Content">
<Setter.Value>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Save"/>
<Image Source="Resources\icons\save.png" />
</StackPanel>
</Setter.Value>
</Setter>
</Style>
Here the XAML parser will reuse the same instance of Style (x:Shared is true by default). To do so it creates copies. But the Image can't be copied: it will only appear on one Button.
To solve this you would have to mark the Style as not shared:
<Style x:Shared="False" x:Key="SaveButton" TargetType="ButtonBase">
<Setter Property="Content">
<Setter.Value>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Save"/>
<Image Source="Resources\icons\save.png" />
</StackPanel>
</Setter.Value>
</Setter>
</Style>
Now the XAML parser must create a new instance of the Style for each reference.
Something somehow similar applies to ControlTemplate. It is also shared by default but not its content.
For each reference to the ControlTemplate, new instances of the content elements are created.
Therefore declaring the content of the Button as ControlTemplate or ContentTemplate (instead of setting the Button.Content directly via a Style like above), would also solve the problem described above.
StaticResource does not mean that the resource is static. It's a markup extension that instructs the XAML parser to lookup the resource tree to find a predefined instance. Same does DynamicResource. The only difference is that StaticResource instructs the XAML parser to resolve the reference to a resource at compile time, whereas DynamicResource let's the XAML parser create an expression instead, that will be evaluated at run time (deferred). DynamicResource therefore resolves the resource at run time.
Related
If there are multiple styles like this:
<Style TargetType="{x:Type local:MyControl}">
...
that are merged in from resource dictionaries that target the same type of control, which should have precedence, is it the first style encountered or last one?
Where can I find the rules that govern such things?
Styles are applied from the ResourceDictionary closest to the control in question. An example:
<Window>
<Window.Resource>
<Style 1/>
<Window.Resources>
<Grid>
<Grid.Resources>
<Style 2/>
</Grid.Resources>
<TextBox/>
</Grid>
</Window>
In the above example, Style 2 will be applied to the TextBox. Should you wish to cascade the styles (apply both styles to the TextBox), you can set BasedOn on Style2 to point to Style1 using BasedOn="{StaticResource {x:Type TextBox}}". Please check the syntax, I don't have VS here.
As you can see, the type becomes the Key. Since it is not permissible to have two elements with the same key in a single ResourceDictionary, you cannot merge two ResourceDictionaries with overlapping styles. It should be possible to design around such a requirement, remembering that a ResourceDictionary can reference another use. Again, you use BasedOn.
This is the answer for your comment and for question at all. Name, x:Name doesn't play on the scene in this case. Each resource in the dictionary must have the Key. For targeted styles WPF infrastructure generates the Key, so, the styles with the same target type will have same key, thus you can't use more than one targeted style for each type in the dictionary.
It will throw an exception. This is what I tried to test this:
I wrote a simple ResourceDictionary with 2 styles, with same TargetType but without x:Key (not x:Name).
<Style TargetType="TextBox">
<Setter Property="Height" Value="100"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="Height" Value="200"/>
</Style>
Rebuilt the project and it complied successfully. Now which will get applied?
Well, when I ran it, it threw a big exception when loading the styles. So in short, it doesn't work.
I'm defining the following style in XAML:
<Style TargetType="telerik:RadDiagramShape" x:Key="styleShapeBase">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="60" />
<Setter Property="IsResizingEnabled" Value="False" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock x:Name="lblName" Text="{Binding Name}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then in the code-behind I'm assigning the data context. I want to draw a shape with some text in it that comes from an object (if this end up working I'm going to put more info there). I'm doing it like this:
var shape = new RadDiagramShape();
shape.Style = (Style)Resources["styleShapeBase"];
shape.DataContext = item.DataContext;
Where item is a simple POCO that has a Name property of type string (this part works, I've traced it, i.e. the DataContext is correctly assigned).
But the data binding never occurs. Is it by design (i.e. no data binding inside a content template), if not what's wrong? Thanks,
You can use Bindings in your DataTemplates. In this case, the Binding will look for a Name property on whatever you set as the Content of your RadDiagramShape.
You should ensure that your class has this property and that it is a string.
If that still doesn't work, can you post details of how you set the style and Content of each instance of RadDiagramShape, and of the object you are trying to bind to?
Somewhere in the Control Template for the RadDiagramShape class, there will be a ContentPresenter with its ContentTemplate bound to the one you have defined. The problem is that the ContentTemplate is only used if the Content property is also set. Otherwise nothing will be loaded into that ContentPresenter.
To make this work, you must set the Content property on the instance of this element.
This is a good place to start understanding what the DataContext property is
I have two styles set in my UserControl.Resources
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="white" />
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="white" />
</Style>
So that in my DataTemplate (and note that I chopped the rest off) I will have white text applied without having to change the properties on each and every Label and TextBlock element.
<DataTemplate x:Key="FileTransferItemTemplate">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="Transferring With: " />
<TextBlock Text="{Binding Path=OtherUserName, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
What happens though(and this caused me a long nightmare where I thought I was databinding improperly because I couldn't see any changes), is when the data is bound the foreground color defaults to black. My databound text was black on a black background, and I didn't even realize for the longest time.
The only way I can override this is manually setting the Foreground="White" on the TextBlock. The Label works fine for the color, because it's not databound.
Why is this happening, and how can I fix it?
The problem is not related to binding. It just seems that the lookup of an externally defined default style from inside a DataTemplate only works for elements that are derived from Control. Since TextBlock isn't derived from Control, your default style is not found.
This page cites the following two statements given by Microsoft:
This behavior is 'By Design' and this is why. Templates are viewed as
an encapsulation boundary. Elements produced by these templates fall
within this boundary. And lookup for a style with a matching
TargetType stops at this boundary. Hence the TextBlock in the repro
which is produced through a template does not pick up the Style in
question. Whereas the TextBlock defined outside the template does.
One way to work around this problem is to give an explicit name to the
Style and reference the style by this name on the TextBlock within the
template.
and
Templates are viewed as an encapsulation boundary when looking up an
implicit style for an element which is not a subtype of Control.
I have several resource dictionaries with theme-related data, where I declared styles for particular element this way:
<Style TargetType="sdk:DataForm">
<Setter Property="Background" Value="{StaticResource Bckgrnd}"/>
</Style>
And also I have Generic.xaml, where I want to set the template for this target type, but I was faced with a situation where in one template I have to use several colors but target type have only one property for color. Something like this:
<Style TargetType="sdk:DataForm">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="sdk:DataForm">
<Grid ctl:DataField.IsFieldGroup="True">
....
<StackPanel Background="{TemplateBinding Background}" ...>
...
...
<!-- and I need another background from themes here -->
<StackPanel Background="{???}" ...>
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the question is: how can I use different colors in this case without something like target type extension? It will be great if you'll find pure xaml solution.
Thanks
I don't know of a pure XAML solution. I think I would create a subclass of DataForm and add dependency properties of type Brush to it. Then use that class in the XAML instead of DataForm, and use TemplateBindings that reference the new properties.
Or, if you don't want to subclass DataForm, perhaps you could create attached properties of type Brush.
I'm trying to do something like this...
<Style
x:Key="MyBorderStyle"
TargetType="Border">
<Setter
Property="Padding"
Value="{TemplateBinding Padding}" />
</Style>
...but I get the error:
'Padding' member is not valid because it does not have a qualifying type name.
How do I provide a "qualifying type name"?
Note: The reason I'm trying to do this, is that I'd like to include the same Border in a series of similar ControlTemplates.
I also tried this:
<Setter
Property="Padding"
Value="{TemplateBinding GridViewColumnHeader.Padding}" />
...and it actually compiled, but then when I ran the app, I got a XamlParseException:
Cannot convert the value in attribute 'Value' to object of type ''.
I thought maybe qualifying Padding with GridViewColumnHeader (which is the ControlTemplate I want to use this style with) would work, but no dice.
EDIT:
Well, according to the documentation for TemplateBinding, it says:
Links the value of a property in a control template to be the value of some other exposed property on the templated control.
So it sounds like what I'm trying to do is just plain impossible. I really would like to be able create reusable styles for certain controls in my control templates, but I guess the template bindings cannot be included in these styles.
TemplateBinding should work for the case where you're templating a control and you want to bind the value of a property of that control to a property of a different control inside the template. In your case you're templating something (call it MyControl), and that template will include a border whose Padding should be bound to MyControl's padding.
From MSDN documentation:
A TemplateBinding is an optimized form of a Binding for template scenarios, analogous to a Binding constructed with {Binding RelativeSource={RelativeSource TemplatedParent}}.
But for whatever reason, specifying TemplatedParent as the source for the binding doesn't seem to work within Style Setters. To get around that you can specify the relative parent to be an AncestorType of the control you're templating (which effectively finds the TemplatedParent providing you haven't embedded other MyControls in the MyControl template).
I used this solution when I was trying to custom template a Button control in which the (String) Content of the Button needed to be bound to the Text property of a TextBlock in the ControlTemplate for the button. Here's what that code looked like:
<StackPanel>
<StackPanel.Resources>
<ControlTemplate x:Key="BarButton" TargetType="{x:Type Button}">
<ControlTemplate.Resources>
<Style TargetType="TextBlock" x:Key="ButtonLabel">
<Setter Property="Text" Value="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type Button}} }" />
</Style>
</ControlTemplate.Resources>
<Grid>
<!-- Other controls here -->
<TextBlock Name="LabelText" Style="{StaticResource ButtonLabel}" />
</Grid>
</ControlTemplate>
</StackPanel.Resources>
<Button Width="100" Content="Label Text Here" Template="{StaticResource BarButton}" />
</StackPanel>
The {TemplateBinding ...} shortcut is not available in a Setter.
But nobody will stop you using the full verbose version such as:
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Padding}".
A property can be qualified simply by prefixing it with the type name. For example, Border.Padding instead of Padding.
However, I'm not sure it makes sense for your scenario. TemplateBindings are used inside a control template.