App Light-Theme
Wenn man einer Anwendung, zu einem einheitlichen Aussehen verhelfen oder die Anwendung um eine Theming Fähigkeit erweitern möchte, dem hat Microsoft mit dem WPF-Framework einen starken Partner, an die Seite gestellt. Um dieses zu bewerkstelligen stehen nun 2 Möglichkeiten zur Verfügung.
- Design-Ressourcen
- hmmmmm… sagen wir mal Light-Theme
an dieser Stelle möchte ich heute, mal die zweite Möglichkeit etwas näher beleuchten.
Erst einmal etwas dazu, was die Light Variante eigentlich sein soll.
In der Light Variante, setzten wir die Properties, der einzelnen Steuerelemente, über die Setter Möglichkeiten der Style Klasse innerhalb der Datei App.xaml.
<Application x:Class="TestApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
</Style>
</Application.Resources>
</Application>
zb Durch diesen Code würden alle Buttons, die diese Properties nicht definiert , sowie auch keinen eigenen Style, definiert haben einen schwarzen Hintergrund und einen Weißen Vordergrund erhalten. Um das ganze, jetzt aber austauschbarer zu halten, ist es nicht sinnvoll den Style direkt in die App.xaml zu schreiben. Für diesen Anwedungsfall würde sich doch ein ResourceDictinary viel besser eignen, also ziehen wir den kompletten Style Tag mal in ein ResourceDictionary um und binden dieses anstatt des Styles in die App.xaml. Nach diesem ersten aufräumen, erhalten wir nun folgenden Code.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
</Style>
</ResourceDictionary>
<Application x:Class="TestApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Durch diese Veränderungen hat sich der Code zwar etwas geändert, jedoch wurde am Verhalten nichts geändert. Wenn man sich seine Anwendung einmal startet, könnte einem auffallen, dass (wir bleiben mal bei unserem Button) manche Buttons immer noch in seinen default Farben erscheinen.
Das könnte zb daran liegen, dass diese Buttons einen eigenen Style definiert haben, zb für einen Trigger.
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button>
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Foo}" Value="True">
<Setter Property="Button.ToolTip" Value="Foo ist True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
An dieser Stelle ist das Style Property, bereits im Button definiert, also kann der Style aus der App.xaml nicht greifen. Jedoch mit einer kleineren Änderung am Code, lässt sich dieses Problem schnell beseitigen.
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button>
<Button.Style>
<Style BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Foo}" Value="True">
<Setter Property="Button.ToolTip" Value="Foo ist True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
nach diesen Änderungen, sollten nun endlich alle Controls, den im ResourceDictionary hinterlegten Styles entsprechen.
Solange man in seiner Anwendung, keine Unterstützung für Themes haben möchte, wäre man an dieser Stelle schon fertig, falls doch geht es noch ein wenig weiter.
Zum verändern des Themes, brauchen wir also eine Methode die die Kollektion Resources in der App.xaml verändert.
ResourceDictionary rd = new ResourceDictionary();
StreamReader sr = new StreamReader(string.Format("{0}/styles/{1}_styles.xaml", AppDomain.CurrentDomain.BaseDirectory, StyleName));
rd = XamlReader.Load(sr.BaseStream) as ResourceDictionary;
this.Resources.MergedDictionaries.Clear();
//sicherheitsebene falls die externe Style Datei nicht komplett implementiert ist
this.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("DefaultStyle.xaml", UriKind.Relative) });
this.Resources.MergedDictionaries.Add(rd);
so Fertig, Anwendung starten und an dem neuen Theming Feature erfreuen, doch was ist das die Buttons mit den Triggern und eigenen Style verändern sich nicht.
Warum?
Wir benutzen zwar die BasedOn Eigenschaft, doch leider ist es per StaticResource eingebunden, es reagiert also nicht auf die Veränderungen der ResourceDicts.
Aber auch für diesen Problem gibt es eine Lösung, die Werte aus dem Style müssen doch nochmal aus den Style Implementierungen heraus, damit die Werte dann wieder als DynamicResource eingebunden werden können.
Resource1.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="ButtonBackground" Color="Black"/>
</ResourceDictionary>
Resource2.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{DynamicResource ButtonBackground}"/>
</Style>
</ResourceDictionary>
inklusive der Zusammenführung in der App.xaml
<Application x:Class="TestApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resource1.xaml"/>
<ResourceDictionary Source="Resource2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
an dieser Stelle wäre es dann endlich vollbracht, die Anwendung kann nun mit verschiedenen Themes ausgeliefert werden.