How to use default style with datatriggers? - wpf

I have default styles set from MaterialDesignInXaml and when I try to add a datatrigger to the control it does not use that same style.
How do I still use the default style while having datatriggers?
<TextBox Margin="10" VerticalAlignment="Bottom" Padding="5" materialDesign:HintAssist.Hint="Search">
<TextBox.Style>
<Style BasedOn="{StaticResource MaterialDesignOutlinedTextBox}"> <!--Not Allowed to do this -->
<Setter Property="TextBox.Visibility" Value="Collapsed"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SearchStyle, Path=SelectedItem.Tag}" Value="Search">
<Setter Property="Label.Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

You have to specify the exact TargetType for the Style that matches the base style.
[...] if you create a style with a TargetType property and base it on another style that also defines a TargetType property, the target type of the derived style must be the same as or be derived from the target type of the base style.
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
In the source code, the MaterialDesignOutlinedTextBox style has a TextBox target type.

Related

Why style targettype has to be rebinded back to the original properties?

I am referring to the code block here, on Data triggers
<Window x:Class="WpfTutorialSamples.Styles.StyleDataTriggerSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleDataTriggerSample" Height="200" Width="200">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<CheckBox Name="cbSample" Content="Hello, world?" />
<TextBlock HorizontalAlignment="Center" Margin="0,20,0,0" FontSize="48">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="No" />
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbSample, Path=IsChecked}" Value="True">
<Setter Property="Text" Value="Yes!" />
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Window>
To me, from programming language design point of view, the line <Style TargetType="TextBlock"> is completely unnecessary, because it is already nested inside the <TextBlock>, so of course whatever setter property should be applied to the TextBlock type. So why need the line <Style TargetType="TextBlock">?
Can <Style TargetType> be of other type except TextBlock?
So why need the line ?
A Style may be defined as a resource, i.e. not inline, and if doesn't have a TargetType specified, how is the XAML parser supposed to be able to parse it and set the properties specified by the setters? It can't. Or at least it doesn't.
Just because you can define a Style inline you are still creating an instance of the exact same class that may be used as a (global) resource and then setting a TargetType is indeed required.
Can be of other type except TextBlock?
No, apart from a type that is derived from TextBlock. If you specify another type you will get an exception at runtime when the BAML (the compiled XAML) is parsed.
You could use any class TextBlock derives from (for example FrameworkElement).
If you implement your own CustomizedTextBlock for example you are able to use styles defined for TextBlock in your project.
You find an example for this here.

Is it possible in WPF to define multiple 'keyed'-Styles but with differing TargetTypes?

I am trying to get to grips with WPF Styles.
I was wondering if it is possible to define one Style key and then describe how it should be applied to different TargetTypes.
This approach is not working for me. I get an error message to say that "TargetType 'TextBlock'" does not match the Element "Image".'
It seems strange that every style:type combination needs it's own key name. I am doing something wrong? Is it completely the wrong approach?
e.g. in the Window.xaml:
<TabControl TabStripPlacement="Bottom">
<TabItem Content="{Binding UserContent}">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="users_24.gif" Style="{StaticResource TabHdr}"/>
<TextBlock Text="{x:Static r:Messages.Tab_Users}" Style="{StaticResource TabHdr}"/>
</StackPanel>
</TabItem.Header>
</TabItem>
</TabControl>
and in the Resources.xaml
<Style x:Key="TabHdr" TargetType="{x:Type Image}">
<Setter Property="Width" Value="20"/>
<Setter Property="Height" Value="20"/>
<Setter Property="Margin" Value="2, 1, 2, 1"/>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="5, 1, 1, 1"/>
</Style>
You can do that if you plan on explicitly setting the Style as you are (i.e. they are keyed styles - implicit styles are always found using the exact type of the class). You just have to set the TargetType to be the lowest base type that defines all the dependency properties you are setting. So in your case you are setting properties that are defined on FrameworkElement so you can just set the TargetType to FrameworkElement.
<Style x:Key="TabHdr" TargetType="FrameworkElement">
<Setter Property="Width" Value="20"/>
<Setter Property="Height" Value="20"/>
<Setter Property="Margin" Value="2, 1, 2, 1"/>
</Style>
Note: If however you wanted to set something like Background (which is defined on Control) then you wouldn't be able to share that style with Image/TextBlock (which don't derive from Control) but you could create a style where the TargetType is Control in that case. You could even set the BasedOn of that style to the style you have for FrameworkElement so you can still share the other settings. E.g.
<Style x:Key="ctrl" TargetType="Control" BasedOn="{StaticResource TabHdr}">
<Setter Property="Background" Value="Red" />
</Style>
And then use it on multiple controls. E.g.
<TextBox Style="{StaticResource ctrl}" />
<Button Content="Foo" Style="{StaticResource ctrl}" />
To short answer your question.. you can't!
You are assigning TabHdr style to a TextBlock, but the style is defined as a Image control style. You can't do that.
If all of your control have to have a specific style, you can define a style without a key but with a specific TargetType. For example, in the code you provided, the TextBlock is applied to all TextBlocks.
PS: If you have to create a kind of theme for your application you can inherit and extend a base style using the BasedOn attribute.

Markup Extension in Data Trigger

To translate my WPF application I use a Markup extension which returns a Binding object. This allows me to switch the language while the application is running. I use this Markup like this:
<TextBlock Text="{t:Translate 'My String'}" />"
I would like to change a Buttons text through a data Trigger:
<Button>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<!-- Custom control template, note the TextBlock formating -->
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="ContentHolder">
<ContentPresenter TextBlock.Foreground="Red" TextBlock.FontWeight="Bold" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<!-- Custom text triggered by Data Binding... -->
<Style.Triggers>
<DataTrigger Binding="{Binding MessageRowButton}" Value="Retry">
<Setter Property="Button.Content" Value="{t:Translate Test}" />
</DataTrigger>
<DataTrigger Binding="{Binding MessageRowButton}" Value="Acknowledge">
<Setter Property="Button.Content" Value="{t:Translate Test}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
This leads to the following exception:
A 'Binding' cannot be set on the 'Value' property of type 'Setter'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Ok, this make sense to me. I tried to define TextBlock in my Resource and use {StaticResource MyResource} in the DataTrigger's Setter Value. But when I do this, the style of my Button is not correctly applied...
How can I work with my markup extension and change text on the button without destring the ability to style the string inside the button?
Try returning the markup extension itself (this) if the target (IProvideValueTarget.TargetObject) is a setter. It will be reevaluated when the style is applied to an actual element.
public object ProvideValue(IServiceProvider serviceProvider)
{
var pvt = service.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (pvt.TargetObject is Setter)
return this;
...
}

Apply default WPF style

I have a global style that sets all my TextBox styles, but in some cases I want to revert just the Foreground color to the original non-custom-style color. I tried using {TemplateBinding Foreground} inside the specific TextBoxes that I wanted to revert. It didn't end up being valid XAML and I'm not sure that's the right way anyhow.
Any ideas? Thanks.
There's a few ways this could be done. If you look at the Precedence List on the MSDN
then you can see that the Forground set in ways 1-8 will override the Foreground from a default style. The easiest way being just to set the local value in the TextBox.
<TextBox Foreground="Red" />
Another thing that you can do is use the 'BasedOn' property of styles to override the other versions. This does require giving a key value to your default style, but that can then be used to also apply the default like in this example:
<Style TargetType="{x:Type TextBox}"
x:Key="myTextBoxStyle">
<Setter Property="Foreground"
Value="Red" />
<Setter Property="FontWeight"
Value="Bold" />
</Style>
<!-- Style applies to all TextBoxes -->
<Style TargetType="{x:Type TextBox}"
BasedOn="{StaticResource myTextBoxStyle}" />
<TextBox Text="Hello">
<TextBox.Style>
<Style BasedOn="{StaticResource myTextBoxStyle}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground"
Value="Blue" />
</Style>
</TextBox.Style>
</TextBox>
Edit:
In the case that the default style is applying a value and you want to revert it to the base value there are a few ways I can think of, off hand, to get this behavior. You can't, that I know of, bind back to the default theme value in a generic manner.
We can however do some other things. If we need the style to not apply some properties, we can set the style to {x:Null}, thus stopping the default style from applying. Or we can give the element it's own style that does not inherit from the base style and then re-apply only the setters that we need:
<TextBox Text="Hello" Style="{x:Null}" />
<TextBox Text="Hello">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="FontWeight"
Value="Bold" />
</Style>
</TextBox.Style>
</TextBox>
We could modify the default style so that the Foreground will only be set on certain conditions, such as the Tag being a certain value.
<Style TargetType="{x:Type TextBox}"
x:Key="myTextBoxStyle">
<Setter Property="FontWeight"
Value="Bold" />
<Style.Triggers>
<Trigger Property="Tag"
Value="ApplyForeground">
<Setter Property="Foreground"
Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
<TextBox Text="Hello" />
<TextBox Text="Hello" Tag="ApplyForeground" />

Can you define multiple TargetTypes for one XAML style?

In HTML/CSS you can define a style which can be applied to many types of elements, e.g.:
.highlight {
color:red;
}
can be applied to both P and DIV, e.g.:
<p class="highlight">this will be highlighted</p>
<div class="highlight">this will also be highlighted</div>
but in XAML you seem to have to define the TargetType for styles, otherwise you get an error:
<Style x:Key="formRowLabel" TargetType="TextBlock">
is there a way to allow a XAML style to be applied to multiple elements or even to leave it open as in CSS?
The setters in WPF styles are checked during compile time; CSS styles are applied dynamically.
You have to specify a type so that WPF can resolve the properties in the setters to the dependency properties of that type.
You can set the target type to base classes that contain the properties you want and then apply that style to derived classes. For example, you could create a style for Control objects and then apply it to multiple types of controls (Button, TextBox, CheckBox, etc)
<Style x:Key="Highlight" TargetType="{x:Type Control}">
<Setter Property="Foreground" Value="Red"/>
</Style>
...
<Button Style="{StaticResource Highlight}" Content="Test"/>
<TextBox Style="{StaticResource Highlight}" Text="Test"/>
<CheckBox Style="{StaticResource Highlight}" Content="Test"/>
<!-- Header text style -->
<Style x:Key="headerTextStyle">
<Setter Property="Label.VerticalAlignment" Value="Center"></Setter>
<Setter Property="Label.FontFamily" Value="Trebuchet MS"></Setter>
<Setter Property="Label.FontWeight" Value="Bold"></Setter>
<Setter Property="Label.FontSize" Value="18"></Setter>
<Setter Property="Label.Foreground" Value="#0066cc"></Setter>
</Style>
<!-- Label style -->
<Style x:Key="labelStyle" TargetType="{x:Type Label}">
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Margin" Value="0,0,0,5" />
</Style>
I think both of these methods of declaring a style might answer your question.
In the first one, there is no TargetType specified, but the property names are prefixed with 'Label'. In the second one, the style is created for Label objects.
Another method to do it is:
<UserControl.Resources>
<Style x:Key="commonStyle" TargetType="Control">
<Setter Property="FontSize" Value="24"/>
</Style>
<Style BasedOn="{StaticResource commonStyle}" TargetType="ListBox"/>
<Style BasedOn="{StaticResource commonStyle}" TargetType="ComboBox"/>
</UserControl.Resources>
I wanted to apply a style to a Textblock and a TextBox but the selected answer didn't work for me because Textblock doesn't inherit from Control, in my case I wanted to affect the Visibility property, so I used FrameworkElement
<Style x:Key="ShowIfRequiredStyle" TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowIfRequiredStyle, UpdateSourceTrigger=PropertyChanged}" Value="true">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBlock Style="{StaticResource ResourceKey=ShowIfRequiredStyle}"/>
<TextBox Style="{StaticResource ResourceKey=ShowIfRequiredStyle}"/>
This works for the Visibility property because both items inherit from Frameworkelement and the property is defined there. Of course this will not work for properties defined only in Control, you can search the hierarchy tree and try to find a base class, anyway I thought this could help someone since this is a top search result and the selected answer is a little incomplete.
There is an alternative answer to the question. You CAN leave the TargetType parameter off the style altogether which will allow it to apply to various different controls, but only if you prefix the property name with "Control."
<Style x:Key="Highlight">
<Setter Property="Control.Foreground" Value="Red"/>
</Style>
Obviously, this only works for properties of the base control class. If you tried to set ItemsSource say, it would fail because there is no Control.ItemsSource
I got this working
<Style x:Key="HeaderStyleThin" TargetType="{x:Type Border}">
<Setter Property="Background" Value="Black" />
<Style.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background=" Value="Red" />
</Style>
</Style.Resources>
</Style>

Resources