Style WPF normal Control depending on Windows Theme - wpf

I have read lots of thing about WPF Theme, Skin, Style etc... But there is still something I cannot achieve.
I have custom controls, which are styled depending on the OS theme, by having a different style in each of the theme file (Aero.NormalColor.xaml, Luna.NormalColor.xaml or Aero2.NormalColor.xaml), this work like a charm.
I don't load/force any theme in my App.xaml, each controls (like buttons) keep there style depending on the OS theme.
So I see XP buttons on XP, Win7 buttons on windows 7 and Win8 buttons on Windows 8.
I also have ResourceDictionaries which are loaded in the App.xaml that contains "named" (explicit x:Key) styles for different normal wpf controls. They look like this:
<Style x:Key="BlackComboBox" TargetType="{x:Type ComboBox}"></Style>
and I use them like this
<ComboBox Style="{StaticResource BlackComboBox}"></ComboBox>
So for now, my BlackComboBox is the same on every Windows (XP/7/8).
What I try to achieve is to have a different Style for these normal Controls depending on the OS theme, without having to subclass the Control (I think it will be overkill to have a subclass for each control that will need an OS specific them), so BlackComboBox could be different on each OS.
I have already tried to put a style with the same key in a theme file, but this doesn't seem to work.
I have thought about loading at runtime a different ResourceDictionary containing the style for the desired OS version:
But it looks like an ugly solution.
I don't like having to check for System.Environment.OSVersion.
And it will not be theme dependant, but OS dependent.
For me the best way seems to be able to have "named" style in a Theme file that kind of overrides the one in the ResourceDictionaries.
Thanks for the help!

I believe the only way to do this would be to create resource dictionaries for each theme the same you would if you created a custom control and wanted to have a different look for each theme. Then you would create a Style in each for the ComboBox and provide a ResourceKey derived class (e.g. ComponentResourceKey) as the x:Key for the Style using the same value for the x:Key in each theme's resource dictionary. Then when you reference the Style you would use a DynamicResource to that ResourceKey.
So a simplified example would be to create a new WpfApplication (e.g. I named its WpfResourceKeys). In this case I'm going to put the theme resource dictionaries in the main assembly so I go into the AssemblyInfo.cs and set the ThemeInfo's 1st parameter (i.e. the themeDictionaryLocation) to SourceAssembly.
Then create a folder named "themes" and in it create a resource dictionary for each theme you want to support.E.g. aero.normalcolor.xaml, aero2.normalcolor.xaml, luna.normalcolor.xaml, classic.xaml, etc..
In each ResourceDictionary define a Style for ComboBox or whatever control you want and give it an x:Key of the same ResourceKey. The easiest thing to use is ComponentResourceKey. In my case I'll use a TextBox since I'll just set the Background and that will be honored regardless of the template defined for each theme. E.g.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:WpfResourceKeys"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="TextBox"
x:Key="{ComponentResourceKey
ResourceId=Foo,
TypeInTargetAssembly={x:Type local:MainWindow}}">
<Setter Property="Background" Value="Purple" />
</Style>
</ResourceDictionary>
In my case I just put this into each theme xaml file but with a different value for the Background setter to test it out. So in my aero2.normalcolor.xaml the setter value was Purple and in the classic.xaml the setter value was Orange. When I run my test in Windows 8 with the default theme the TextBox is purple but if I switch to one of the high contrast themes the TextBox is Orange.
Then in the place you are going to reference it you would use a DynamicResource instead of a StaticResource since you won't be defining the Style within the resources of the window or app.xaml (because you want the framework to locate it considering the OS theme).
<Window x:Class="WpfResourceKeys.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfResourceKeys"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Style="{DynamicResource ResourceKey={ComponentResourceKey
ResourceId=Foo,
TypeInTargetAssembly={x:Type local:MainWindow}}}" Text="ABC" />
</Grid>
You just need to make sure you use an equivalent resource key to how you define it in the theme dictionaries. In the case of ComponentResourceKey that means the ResourceId and TypeInTargetAssembly are equivalent.

Related

How to get (MahApps) metro style in derived control?

I create my own control to look and act like ComboBox.
Approach A: I create UserControl with ComboBox as a content. It HAS the metro style, it looks perfect. It works. However I have to manually recreate ComboBox properties in my control. A lot of redundant, ugly code.
Approach B: I extend ComboBox control itself, so no extra coding is needed. It works as charm, however - it's a ComboBox with a different name, so it's not targeted with the metro style for ComboBox.
How to make my new derived control use the metro style for ComboBox?
Add an implicit style to your App.xaml file (replace CustomComboBox with the name of your derived class):
<Style TargetType="local:CustomComboBox" BasedOn="{StaticResource MetroComboBox}" />
If you are using the Material Design toolkit, it should be:
<Style TargetType="local:CustomComboBox" BasedOn="{StaticResource MaterialDesignComboBox}" />

Change Style Values of ResourceDictionary at RunTime

I have defined a Font style common for all controls in a ResourceDictionary.
Now, the requirement is if user sets any other different Font from some application level configuration then all Controls should get that new Font properties/style.
I know ResourceDictionary can't have DataContext. But there can be hundreds of Fonts(so different Themes not possible), I am thinking if is there any way I can bind Font properties of ResourceDictionary to a Property which will take that new Font at runtime.
So Is there any other way to satisfy this in a Generic way.
You can set new values for app.resources via code like this
app.Resources[name] = new FontFamily(value);
where app is your App (obviously) and can be set with this when your changing the Resource directly in the App.xaml.cs
name is your resource key as string, which could be e.g. "fontFamily"
<FontFamily x:Key="fontFamily">TheSansOsF</FontFamily>
You have to set the bindings for your font to dynamic resource, otherwise changing the font won´t work "on the fly"
<Setter Property="Label.FontFamily" Value="{DynamicResource fontFamily}"></Setter>
EDIT: Furthermore when you try to use e.g. double as a resource you can use resources like this
<sys:Double x:Key="ctrlWidth">50</sys:Double>
and adding the sys to the application tag
<Application x:Class="Presenter.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"

Place style inside Custom Control

I'm creating the custom control. And suddenly faced a question: why to put control's style separately from the control?
I mean that using the standard way you must: derive from base control (for example, from TextBox) and add a style for it in general.xaml.
But why can't we do just like this:
<TextBox x:Class="CustomTest.CoolTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox.Style>
<Style>
<Setter Property="TextBox.FontSize" Value="20" />
</Style>
</TextBox.Style>
</TextBox>
And code-behind:
public partial class CoolTextBox : TextBox
{
public CoolTextBox()
{
InitializeComponent();
}
}
Update #1
I'm not writing my own library but creating a custom control inside my main executable. Application support themes but they differ only by colors. So each theme is a set of brush resources and my style will refer them using DynamicResource.
What I want to know is the drawbacks of that solution. I mean performance, simplicity of usage and etc.
WPF allows changing themes at runtime, means the style shall be stored separatly from the controls. Futhermore the control shall be lookless when designining in order to other programmers to have their custom styles though somewhere there should be a default style which must be stored separatly in a Generic.xaml file. If your app doesn't support changing themes then you can define the style wherever you wish.
If you are writing a library of custom controls I suggest you to stick to standards.
Here is a link how shall a custom controls be created:
http://wpftutorial.net/HowToCreateACustomControl.html
In addition to dev hedgehog's answer about performance, I found the following drawbacks:
Style object is own for each instance of control. So you end up with number of clones of the same Style object.
You cannot override style using BasedOn property. Only completely replace is possible.

Design templates in a class library at top scope

I use a class library (WPF user control library) to host some user controls which other (C#-) applications in the project solution consume. I want these controls to use XAML ControlTemplates residing at the top scope of the class library. The ControlTemplates do not have to be consumed outside the class library.
Here a template declaration:
<ControlTemplate TargetType="{x:Type Button}" x:Key="TemplateImageButtonSmall">
<Grid>
<Image Name="img" Source="/PSCommonUI;component/Images/Buttons/ButtonMinus_normal.png"/>
</Grid>
</ControlTemplate>
Then I have a user control in the class library, containing:
<Button Height="57" Margin="10,0,6,5" Name="button3" Template="{StaticResource TemplateImageButtonSmall}" Width="82">
In an application, I can use the App.xaml file for defining the templates. However, in a class library I don't have this option.
I have searched the web and found some answers including the use of a generic.xaml file, ComponentResourceKey, merging resource files and other stuff I find exaggeratedly complicated.
Also I read that theme definitions (resources in general) shouldn't reside in a class library.
But if I need some themes ONLY in this class library for the there hosted controls, how is best practice then?
Thanks in advance,
Julian
I am not sure what you meant, however, if you want child UIElements from a specific UIElement and below to use control templates, then you can define the templates in a resource dictionary and merge the dictionary into the top control that you want the dictionary to be visible to.
Edit:
The assembly just contains the classes and resources within it. It has no events of its own (e.g. OnApplicationLoaded).
A control's XAML can contain resources of its own (e.g. control templates) for consumption by itself and child controls and thus define default styling.
Your application can merge the resource dictionaries into any level of the tree (application, window, control, ...) and thus override defaults.
If you want the styling to be dynamic (overrable by importing resource dictionaries) then using the DynamicResource keyword your XAML. If your resource is defined in the same XAML and can not be overridden then use the StaticResource keyword.
Add a resource dictionary to your class library and define your resources (templates) there. It doesn't have to be generic.xaml.
Then in each user control or other .xaml file reference the resource dictionaries you require using Xaml similar to:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="... path to dictionary 1"/>
<ResourceDictionary Source="... path to dictionary 2"/>
<ResourceDictionary Source="... etc"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
You can then use resource keys from the merged dictionaries.

Getting started with a derived custom control?

I am experimenting with derived custom controls, and I created what I thought would be the simplest possible derivation:
I created a custom control project in VS 2010 and changed the base class for CustomControl1 from Control to Calendar.
Then I went into Generic.xaml and removed the default Style created for CustomControl1.
Finally I created a WPF app to consume the control.
When I add the custom control to the app's MainWindow, I had expected to see a regular WPF calendar, since I had derived from Calendar and made no changes to the Calendar control templates.
Instead, nothing shows up at design time or run time. MainWindow remains empty. I am not sure what is going on, but it is pretty obvious that I have made a faulty assumption somewhere along the line.
Can anyone clear this up for me? Thanks for your help.
BTW--why am I doing this? I am extending the Calendar control, but I will only need to modify the CalendarDayButton control template. Before I get to my modifications, I figure I should be able to display the unmodified Calendar first. Like I said, I think I'm making a faulty assumption somewhere.
CustomControl1.cs
Here is the code for CustomControl1:
using System.Windows;
using System.Windows.Controls;
namespace WpfCustomControlLibrary1
{
public class CustomControl1 : Calendar
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
}
Generic.xaml
Here is the markup for Generic.xaml, which is located in the control's Themes folder:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
</ResourceDictionary>
MainWindow
Finally, here is the MainWindow.xaml markup:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1" Title="MainWindow" Height="350" Width="525">
<Grid>
<WpfCustomControlLibrary1:CustomControl1 />
</Grid>
</Window>
WpfApplication1 contains a reference to the WpfCustomControlLibrary1 project.
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
->What this line says is that CustomControl1 has its default style defined in Generic.xaml
Then I went into Generic.xaml and removed the default Style created for CustomControl1.
-> What this does is remove the default style for CustomControl1
So your control has no style so it shows nothing :D
Rather than removing the style from generic.xaml, you should copy the the style of the Calender control and change TargetType to CustomControl1 or create a new style and add BasedOn Calender
Edit to add a little more info to David's answer below for people having a look down the road
<Style TargetType="{x:Type local:FsCalendar}" BasedOn={x:Type Calender}>
<Setter Property="CalendarDayButtonStyle" Value="{StaticResource FsCalendarDayButtonStyle}" />
</Style>
This is all you need in the style. BasedOn will take care of copying everything from the default style and it will also take care of different themes. If you copy the style from the default theme of calender you will break the look for all the themes except for the one from which you copied the 'default' style.
I found my answer--thanks to NVM for all the help! This applies to controls generally, but it applies particularly to the Calendar control. If you are going to modify only part of the control, you don't have to include all of the constituent control templates.
But you do have to include the main control template, which you point to your custom control, and you have to establish a chain from the main control to the template you want to modify. In the case of my Calendar control, I need to modify only the CalendarDayButton template to implement the changes I want to make. So, here is what I did:
I included the main Calendar template, and point that toward my custom control.
Then, to get down to the CalendarDayButton, I added a property setter to point my main Calendar style's CalendarDayButtonStyle property to my custom CalendarDayButton style.
Here is what the main Calendar style declaration in my Generic.xaml file ends up looking like:
<!-- Calendar Style -->
<Style TargetType="{x:Type local:FsCalendar}">
<Setter Property="CalendarDayButtonStyle" Value="{StaticResource FsCalendarDayButtonStyle}" />
...
</Style>
The remainder of the main Calendar style is unchanged--it is a copy of the default style.
BTW, the CalendarDayButton style definition must appear before the main Calendar style definition in Generic.xaml, or the main Calendar style won't be able to find it.
I have written a Code Project Article titled Extending the WPF Calendar Control. It walks through the step involved in extending a complex control like the WPF Calendar. Hopefully, it will help others who are grappling with the same issues.
BTW, I have since discovered the Style.BasedOn property, which will let you derive a style from an existing style without having to repeat the base style. There is a good blog post on it here.

Resources