How to "inject" style in a DataTemplate - wpf

I am trying to separate the DataTemplates from the Styles in my code.
I use DataTemplate to define, e.g. that the data should be displayed as two buttons and I use styles to define, e.g. that the background of those buttons should be green.
What I am trying to achieve is having a DataTemplate defined in one file and using that in multiple UserControls where the style comes from the UserControls.
Let's say I have the following style in the Resources of a UserControl:
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="Green"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
Another UserControl might have something similar with different colors.
Then, I have a ContentControl in that UserControl that will have some view model and some DataTemplate:
<ContentControl Content="{Binding SelectedViewModel}"
ContentTemplate="{Binding SelectedDataTemplate}"/>
The DataTemplate can be something as simple as this:
<DataTemplate x:Key="TwoButtonsTemplate">
<StackPanel>
<Button Content="One"/>
<Button Content="Two"/>
</StackPanel>
</DataTemplate>
I would like the two buttons to have the ButtonStyle from the UserControl.Resources without directly referencing it. (So the DataTemplate can come from a different file or being able to use the DataTemplate in a similar context with another UserControl's style).
I tried to change the TargetType of ButtonStyle to ContentControl, assign the style to the ContentControl and set Foreground="{TemplatedParent Foreground}" on the Buttons, but in this way both Foreground will change when any of them (i.e. the ContentControl itself) is hovered.
Is there any way of "inheriting" a style in the DataTemplate or "injecting" the style from the UserControl?
P.S. I understand if I move the style into a separate file and reference that in the DataTemplate file I can simply use it as StaticResource, but that will couple the DataTemplate to that specific style and I won't be able to re-use it with other styles.

try DynamicResource:
<DataTemplate x:Key="TwoButtonsTemplate">
<StackPanel>
<Button Content="One" Style="{DynamicResource ButtonStyle}"/>
<Button Content="Two" Style="{DynamicResource ButtonStyle}"/>
</StackPanel>
</DataTemplate>
when TwoButtonsTemplate template is instantiated in UserControl, which declares ButtonStyle resource, that resource will be found and applied to buttons.

Related

XAML : Set Label Margin inside a DataTemplate

I have a DataTemplate directly inside a Resource Dictionary. Inside the template is a label. The margin property isn't being applied how I expected (it has no effect)
<DataTemplate x:Key="HeaderContainerStyle">
<Label Margin="10" Text="{Binding}"/>
</DataTemplate>
And I can't solve the issue with a border as it appears its illegal (?)
<DataTemplate x:Key="HeaderContainerStyle">
<Border Padding="10">
<Label Text="{Binding}"/>
</Border>
</DataTemplate>
I get an error saying cannot resolve symbol Border.
When I try to add it in a ViewCell, application throws an exception:
System.ArgumentException: Value was an invalid value for HeaderTemplate
Parameter name: value
In Xamarin.Forms there is no Border class. Instead you should use Frame class which I think is equivalent for Border in WPF.
Like #Nick said, if you want to use DataTemplates in Xamarin.Forms you need to add ViewCell in DataTemplate and then next next element inside that (for example Grid, StackLayout or Label).
If it comes to Padding, in Xamarin.Forms its only applicable for layout (e.g. Grid, StackLayout) classes. Margin can be specified for view (e.g. Label, Button) and layout classes.
Getting back to your code basing on your HeaderContainerStyle I think you are trying to create style for Label, right?
To do that in Xamarin.Forms you should add new create new Style in ResourceDictionary for specific TargetType.
Example Style for Label class:
<ResourceDictionary>
<Style x:Key="labelRedStyle" TargetType="Label">
<Setter Property="HorizontalOptions"Value="Center" />
<Setter Property="VerticalOptions" Value="Center" />
<Setter Property="FontSize" Value="15" />
<Setter Property="TextColor" Value="Black" />
<Setter Property="Margin" Value="15,10" />
</Style>
<ResourceDicrionary>
And example usage:
<Grid>
<Label Style="myLabelStyle" />
</Grid>
Let me know if it helped! Waiting for more questions :)
I'm assuming this is Xamarin.Forms and not WPF but if your DataTemplate is used by a ListView it is missing a ViewCell.
<DataTemplate x:Key="HeaderContainerStyle">
<ViewCell>
<Label Margin="10" Text="{Binding}"/>
</ViewCell>
</DataTemplate>
However, if your DataTemplate is used in your own custom control that was created then the ViewCell may not be needed and another issue with that control may be the cause of why Margin is not working.

Toggle two different elements in DataTemplate using Style Triggers

I have an object in ViewModel whose properties are displayed by a datatemplate. The screen also has a button toggling the IsEditing flag in ViewModel, which should make the object properties editable, like the following:
Name should change from TextBlock to TextBox;
Color should change from colored rectangle to ComboBox with color options;
Category should change from TextBlock to ComboBox;
I know how to implement this with two completely independent DataTemplates, using a Style and a DataTrigger to toggle between them:
<ContentControl Content="{Binding FancyObject}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource DisplayTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsEditing, ElementName=UserControl}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
And currently the DisplayTemplate is like this:
<DataTemplate x:Key="DisplayTemplate" DataType="my:FancyObject">
<Border>
<DockPanel DataContext="{Binding Metadata}">
<Border>
<TextBlock Text="{Binding Name}"/>
</Border>
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding FancyObjectCollection}">
<DataGrid.Columns>
<!-- Text and Template columns -->
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Border>
</DataTemplate>
The problem is: using two independent but similar templates would mean a duplication of layout, since only some fields would change, but the overall structure is the same.
Another option I imagine is to use a single template defined inside the Style, and use the Trigger to change the fields individually, but I don't know how to do it, or even if it is possible at all.
You can use one template.
In the template add both TextBlock and TextBox, same for all your other controls on the original template.
Bind the controls visibility to bool to visibility converter. (Or use triggers) Only one set of your control will be seen each time (based on IsEditing flag)
The ControlTemplate is only used upon generating the UI Elements. If you change the template AFTER generating the items, the generated items will not change.
You can also not use a trigger to change a TextBox to a TextBlock and vice versa.
Your only option is indeed to mirror the layout twice and hide/display it via the data bound property.

Reset ControlTemplate

I'm using some fancy WPF-based UI framework that defines ControlTemplates for all the basic controls. So if I have a ListBox, it is styled according to the theme of this framework.
I'd like to use a single ListBox that has a completely different style, and so I'd like to disable the ControlTemplate for this particular control only and build up a style from scratch.
I've tried setting the Template property to null on this control, as shown below, but it didn't work:
<Setter Property="Template" Value="{x:Null}" />
How can I reset the ControlTemplate for this control in order to get rid of the framework-specific styles and weave my own?
No need to resort to styles and setters if you only want to change the template for this one control:
<TextBox>
<TextBox.Template>
<ControlTemplate>
<Rectangle Width="200" Height="20" Fill="Red"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>

WPF ControlTemplate breaks style

The stuff that does work
I need to style controls of a certain type that are children of a StackPanel. I'm using:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">...</Style>
</StackPanel.Resources>
<TextBlock ...>
...
</StackPanel>
And this works fine! Each TextBlock looks to the resources of it's parent (the StackPanel) to find out how it should be styled. It doesn't matter how far down you nest the TextBlock down a StackPanel... if it doesn't find a style in its direct parent, it will look at its parent's parent and so on, until it finds something (in this case, the style that was defined in ).
The stuff that doesn't work
I ran into a problem when I nested a TextBlock inside a ContentControl, which had a Template (see code below). The ControlTemplate seems to disrupt the way a TextBlock retrieves its style from its parents, grandparents,...
The use of a ControlTemplate effectively seems to knock out cold the TextBlock's means of finding its rightful style (the one in StackPanel.Resources). When it encounters a ControlTemplate, it stops looking for its style in the resources up the tree, and instead defaults to the style in MergedDictionaries of the Application itself.
<StackPanel Orientation="Vertical" Background="LightGray">
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
</StackPanel.Resources>
<TextBlock Text="plain and simple in stackpanel, green" />
<ContentControl>
<TextBlock Text="inside ContentControl, still green" />
</ContentControl>
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<StackPanel Orientation="Vertical">
<ContentPresenter />
<TextBlock Text="how come this one - placed in the template - is not green?" />
</StackPanel>
</ControlTemplate>
</ContentControl.Template>
<TextBlock Text="inside ContentControl with a template, this one is green as well" />
</ContentControl>
</StackPanel>
Is there a way - besides duplicating the Style in StackPanel.Resources to ControlTemplate.Resources - to make the TextBlock inside that ControlTemplate find the defined style?
Thanks...
WPF considers ControlTemplates to be a boundry, and will not apply implicit styles (styles without an x:Key) inside of templates.
But there is one exception to this rule: anything that inherits from Control will apply implicit styles.
So you could use a Label instead of a TextBlock, and it would apply the implicit style defined further up your XAML hierarchy, however since TextBlock inherits from FrameworkElement instead of Control, it won't apply the implicit style automatically and you have to add it manually.
My most common way to get around this is to add an implicit style in the ControlTemplate.Resources that is BasedOn the existing implicit TextBlock style
<ControlTemplate.Resources>
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource {x:Type TextBlock}}" />
<ControlTemplate.Resources>
Other common ways of getting around this are:
Place the implicit style in <Application.Resources>. Styles placed here will apply to your entire application, regardless of template boundaries. Be careful with this though, as it will apply the style to TextBlocks inside of other controls as well, like Buttons or ComboBoxes
<Application.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
</Application.Resources>
Use a Label instead of a TextBlock since it's inherited from Control, so will apply implicit Styles defined outside the ControlTemplate
Give the base style an x:Key and use it as the base style for an implicit TextBlock styles inside the ControlTemplate. It's pretty much the same as the top solution, however it's used for base styles that have an x:Key attribute
<Style x:Key="BaseTextBlockStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
...
<ControlTemplate.Resources>
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource BaseTextBlockStyle}" />
<ControlTemplate.Resources>

Access property of element in controltemplate in XAML

I want to use templated ComboBoxItems which consist of an Image and a Label. If I assign the template to a ComboBoxItem, can I somehow set the Source-Property of the Image? The goal is to use the same template for different ComboBoxItems but with different pictures in each Item.
I also thought about binding the Image.Source-Property in the Template, but this fails because the "parent" ComboBoxItem has of course no Source-Property I could bind to.
The code illustrates my problem:
<Style x:Key="ComboBoxPictureItem" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<StackPanel Orientation="Horizontal">
<Image x:Name="StatusImage" />
<Label x:Name="StatusLabel" Content="Green"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ComboBox>
<ComboBoxItem Style="{StaticResource ResourceKey=ComboBoxPictureItem}"
-> sth. like: StatusImage.Source="PathToMyImage.png"/>
</ComboBox>
Thank you!
You should use template bindings to expose internal properties, e.g. bind the Label's content to the ComboBoxItem's content:
<Label Content="{TemplateBinding Content}"/>
If you now set the Content outside it is transferred to the label, you can do the same for the image, you may run out of properties though so if you want to do things that way you could inherit from ComboBoxItem and create more properties.
Here i do not think you want to mess with control templates really, just use the ItemTemplate to specify how the items look.

Resources