I have created a WPF application that uses styles extensively, and in particulare style inheritance (using BasedOn) to describe its appearance. So, throughout the application's main files (in <Application.Resources> and <Control.Resources>) I have defined styles such as the following:
<Color x:Key="MusicTextColor">Red</Color>
<SolidColorBrush x:Key="MusicTextColorBrush" Color="{DynamicResource MusicTextColor}" />
<Style x:Key="MusicText" TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="Foreground" Value="{DynamicResource MusicTextColorBrush}" />
</Style>
<Style x:Key="MusicTitle" BasedOn="{StaticResource MusicText}" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="40" />
</Style>
And in the UI I would use it normally like this to theme contents:
<TextBlock Style="{DynamicResource MusicTitle}">Artist:</TextBlock>
<Rectangle Background="{DynamicResource MusicTextColorBrush}" />
Now to theme the application, I am loading a theme.xaml file containing the theme changes in a <ResourceDictionary> at runtime and merge its resource dictionary with the application resources. The file might e.g. contain the following, which is supposed to change the color and font for all "music" labels:
<ResourceDictionary ... >
<Color x:Key="MusicTextColor">Green</Color>
<Style x:Key="MusicText" TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontFamily" Value="Consolas" />
<Setter Property="Foreground" Value="{DynamicResource MusicTextColorBrush}" />
</Style>
</ResourceDictionary>
The problem is, this does not work. Neither the color change nor the font change is picked up. Note that the font IS actually changed (and this confirms that a resource merge takes place) if I use any of the redefined styles directly (e.g. "MusicText"), but not the derived one. The explanation is most likely that the BasedOn attribute uses a StaticResource binding, so it will not find the redefined styles, even after the merge. As I learned, BasedOn does not allow a DynamicResource binding that I would need here.
To me this looks like a common problem - theming an application with style inheritance at runtime, but I have not found a realistic solution until now.
Of course I could stop using style inheritance at all, but that would result in a lot of redunant code.
Also, I could copy ALL derived styles into the theme, but that would be error-prone and prevent further application updates (without manually updating each theme with each change).
Did I miss something or is there really no convenient solution for this?
Related
I am writing Style Setters for custom WPF controls and would like to set values to resources (such as Brushes) that would ideally (read "hopefully") be defined at an application-level (so that app-wide theming is enabled). One example would look something like this:
<Style.Triggers>
<DataTrigger Binding="{Binding IsInteresting}" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<!-- Use the brush that is (hopefully) defined at the application-level -->
<Setter Property="Foreground" Value="{StaticResource InterestingBrush}" />
</DataTrigger>
</Style.Triggers>
Now to make the Style usable in the case that the resource has not yet been defined at the application-level, I would like to be able to define the brush locally in Xaml and have that definition be used if a resource with that same key is not found elsewhere. Conceptually speaking, it would be analogous to using an "if not defined" preprocessor directive such as #ifndef.
Perhaps there already exists a mechanism for doing this. If not, how could it be implemented? Could an attached property/behavior somehow do this? If so, I envision its usage looking something like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:HopefulBehaviors="clr-namespace:HopefulBehaviors"
xmlns:My_Controls="clr-namespace:My_Controls"
>
<!-- IDEA:
Attached property being used to "use the brush that is defined elsewhere
if it was found, if not use this locally-defined one" -->
<SolidColorBrush x:Key="InterestingBrush"
HopefulBehaviors:UseExternalIfFound="True" Color="Red" />
<Style TargetType="{x:Type My_Controls:AwesomeControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsInteresting}" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<!-- now this brush would be defined (at least locally
but an external definition is used if it is found) -->
<Setter Property="Foreground" Value="{StaticResource InterestingBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
I realize that one workaround might be to leave the generic custom control Style pretty bland, then write inherited Styles that for each application that use references to resources that exist in that application. But I'm hoping there is a more elegant approach that generalizes across applications better.
I have a WPF application with many windows and user controls, and I'd like to implement standard styles for certain controls that appear throughout the application. As an example, say I need two standard TextBlocks throughout the application: one for large headings, one for small headings. And the only difference between them is the font size, say 36 and 24 respectively. All other properties (color, fontfamily, etc.) could be set by a TextBlock template or global TargetType="{x:Type TextBlock}" styles.
Of course I could create two global named styles that just set the font size and apply those staticresource styles liberally throughout the XAML to my TextBlocks, or at the highest possible level above the TextBlocks that would not interfere with other TextBlocks. But as an alternative, which would remove the requirement for setting the Style tag in many places, is inheriting from TextBlock is a good way to go?
TextBlock controls:
class TextBlockLargeHeading : TextBlock
{
public TextBlockLargeHeading()
{ }
}
class TextBlockSmallHeading : TextBlock
{
public TextBlockSmallHeading()
{ }
}
Global resource:
<Application.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style TargetType="MyApp:TextBlockLargeHeading" BasedOn="{StaticResource {x:Type TextBlock}}" >
<Setter Property="FontSize" Value="36" />
</Style>
<Style TargetType="MyApp:TextBlockSmallHeading" BasedOn="{StaticResource {x:Type TextBlock}}" >
<Setter Property="FontSize" Value="24" />
</Style>
</Application.Resources>
Then, to use them anywhere, simply reference the custom textblocks:
<StackPanel>
<MyApp:TextBlockLargeHeading Text="Large" />
<MyApp:TextBlockSmallHeading Text="Small" />
</StackPanel>
Which would create two Red TextBlocks with the appropriate font sizes.
Is this a reasonable approach? Are there any gotcha's if I've got 100's of instances of these, maintainability-wise or otherwise? Is there a better (safer or less code/XAML) approach? Perhaps using User Controls instead?
Thanks!
There's no reason to do all that. Create your styles and use them directly.
....
<Style x:Key="DefaultTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontSize" Value="24" />
</Style>
<Style x:Key="LargeTextBlockStyle" TargetType="TextBlock" BasedOn="{StaticResource DefaultTextBlockStyle}">
<Setter Property="FontSize" Value="36" />
</Style>
<!-- Style applies to all TextBoxes -->
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource DefaultTextBlockStyle}" />
...
<StackPanel>
<TextBlock Text="Large" Style="{StaticResource LargeTextBlockStyle}"/>
<TextBlock Text="Small"/>
</StackPanel>
I am currently working on creating a theme for charts.
Beside other things, I want to make the bars in BarSeries have a flat look(without borders).
I want to make it to work with implicit styling so I added a Style to BarDataPoint(without a Key because it needs to work by implicit styling) but it is not applied.
Any idea why is it is not applied?
Is it because of DataPointStyle style from Palette which is applied instead?
What I am trying to do is to change the look of the BarDataPoint but still have the colors from the palette applied. And also try to make this work by pure XAML(if possible).
In order to make this work, the only way I see is by changing DataPointStyle(in each ResourceDictionary from Chart.Palette) to have TargetType set to BarDataPoint and Template set to my template implementation:
<toolkit:Chart.Palette>
<toolkit:ResourceDictionaryCollection>
<ResourceDictionary>
<Style x:Key="DataPointStyle" TargetType="toolkit:BarDataPoint" BasedOn="{StaticResource BarDataPointStyle}">
<Setter Property="Background" Value="Yellow" />
<Setter Property="BorderBrush" Value="Black" />
</Style>
</ResourceDictionary>
<ResourceDictionary>
<Style x:Key="DataPointStyle" TargetType="toolkit:BarDataPoint" BasedOn="{StaticResource BarDataPointStyle}">
<Setter Property="Background" Value="Red" />
<Setter Property="BorderBrush" Value="Black" />
</Style>
</ResourceDictionary>
</toolkit:ResourceDictionaryCollection>
</toolkit:Chart.Palette>
But since this is for implicit style for Chart control, how would that work if I have a Char control with a different type of series, for example a Chart with ColumnSeries? I don't think the DataPointStyle will work in this case because it is targeting the BarDataPoint type(I suppose the app will crash).
Am I forced to create different Chart styles with different keys(each style having DataPointStyle changed to target different control template)?
But then, how will that work for a Chart control with several different series?
I also tried to use an implicit style for toolkit:BarDataPoint in the palette's resource dictionary like this, but without success:
<Setter Property="Palette">
<Setter.Value>
<toolkit:ResourceDictionaryCollection>
<ResourceDictionary>
<SolidColorBrush x:Key="Background"
Color="#FFCA294D" />
<Style TargetType="toolkit:BarDataPoint">
<Setter Property="Template"
Value="{StaticResource BarDataPointTemplate}" />
<Setter Property="Background"
Value="{StaticResource Background}" />
</Style>
I tried to look to themes like JetPack but they don't seem to do what I want.
Thanks!
sorry for late update.
check this solution. may help others too
Silverlight 4: Chart Toolkit Color Set
I have a resource dictionary in my WPF application which contains the style information for the various controls.
Can it be used like the way we use in CSS in HTML? For example
p
{
margin:20px;
font:Tahoma;
}
this applies to all "p" tags in HTML. We dont have to specifically mention that in the HTML for "p" tag.
Is the same approach applicable in WPF, or do we have to specifically
mention the style
<TextBlock Text="Test" Style="{DynamicResource SomeTextblockStyle}" />
in the XAML
You can certainly set a default style for each type. You can do this within your Generic.xaml, note that I am not providing a key.
<Style TargetType="{x:Type Button}">
<Setter Property="Height" Value="25"/>
<Setter Property="Foreground" Value="White"/>
</Style>
This will style every instance of a Button within your application as such.
If you were go to a XAML file and define an instance of a Button, overriding the Foreground value, that local instance will take precedence over the global style.
<Button Foreground="Black"/>
You can set style like using key
<Style TargetType="{x:Type TextBlock}" x:Key="myStyle">
<Setter Property="Margin" Value="20"/>
<Setter Property="FontFamily" Value="Tahoma"/>
</Style>
And in the Window.Xaml
<TextBlock Text="Hello" Style="{DynamicResource myStyle}"/>
I have the following style defined in the ResourceDictionary of a Silverlight 4.0 application
<Style x:Key="GridSplitterStyle" TargetType="sdk:GridSplitter">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template" Value="{StaticResource GridSplitterTemplate}" />
</Style>
<ControlTemplate x:Key="GridSplitterTemplate" TargetType="sdk:GridSplitter">
<StackPanel Background="Transparent" Height="32">
<!-- ... -->
</StackPanel>
</ControlTemplate>
When I apply the style on my GridSplitter, the style is found and properly applied. However, when linking the Template property to the ControlTemplate defined in the same dictionary file, the following error comes up:
Cannot find a Resource with the Name/Key GridSplitterTemplate
How come Silverlight can find the style but not the template? They are located in the same file...
Static resource references are resolved during Xaml parsing. As a result you cannot have use forward referencing.
Place the control template above the style in document order so that the parser finds "GridSplitterTemplate" first. Then when "GridSplitterStyle" references it, the parser will be able to find it.