So the way I had to do this was very complex so listen carefully.
I found that the real problem here was that I needed to "Theme" my Xamarin.Forms application.
So heres what I did.
First made an abstract Theme
class:
public abstract class Theme
{
public Dictionary<string, Color> Colours { get; set; } = new Dictionary<string, Color>();
protected void DictionaryThemes(Type type)
{
var properties = type.GetRuntimeProperties();
foreach (PropertyInfo propInfo in properties)
{
if (propInfo.PropertyType == typeof(Color))
{
var key = propInfo.Name;
var value = propInfo.GetValue(this);
Colours.Add(key, (Color)value);
}
}
}
public abstract Color TitleColour { get; }
}
With a nice method using reflection to fill a dictionary full of my Resources
I then extended this class with my actual theme:
public class MyTheme : Theme
{
public MyTheme()
{
DictionaryThemes(typeof(MyTheme));
}
//Slate
public override Color TitleColour { get; } = Color.FromHex("#00ff00");
}
Still with me? good...
I then had to load this theme into my Application Resources
and merge it with my other application resources. I have to split the MyTheme
resource from the other Resources
so that I can use it in my main Resources
file later.
Let me show you, here is my CurrentTheme
resource file:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:styling="clr-MyApp.Styling;assembly=MyApp"
x:Class="MyApp.Styling.CurrentTheme">
<ContentPage.Resources>
<ResourceDictionary>
<styling:MyTheme x:Key="CurrentTheme"/>
</ResourceDictionary>
I have to do it as part of a page as Xamarin.Forms
has Sealed
the ResourceDictionary
class (see here) meaning you can't extend it and create your own.. Shame. Anyway I digress.
I then set my application Resources to CurrentTheme
like so:
var currentTheme = new CurrentTheme();
Xamarin.Forms.Application.Current.Resources = currentTheme.Resources;
I can then merge in other styles from my main Resource
file, in this case called Styles
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:styling="clr-MyApp.Styling;assembly=MyApp"
x:Class="MyApp.Styling.Styles">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="TitleStyle" TargetType="Label">
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="FontSize" Value="30" />
<Setter Property="TextColor" Value="{styling:Colour TitleColour}" />
</Style>
</ResourceDictionary>
I merge this into my main application resources like so:
var styles = new Styles();
foreach (var item in styles.Resources)
{
Application.Current.Resources.Add(item.Key,item.Value);
}
Now as you can see one of the setter properties in the Styles
class looks like so:
and you're saying whats that value mean?
Here comes the final piece of the puzzle. An extension method that allows you to define your xaml
like the above.
Firstly the ColourExtension
class:
// You exclude the 'Extension' suffix when using in Xaml markup
[ContentProperty("Text")]
public class ColourExtension : IMarkupExtension
{
public string Text { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Text == null)
throw new Exception("No Colour Key Provided");
return StylingLookup.GetColour(Text);
}
}
and finally the StylingLookup
class:
public static class StylingLookup
{
public static Color GetColour(string key)
{
var currentTheme = (Application.Current.Resources["CurrentTheme"] as Theme);
return currentTheme.Colours[key];
}
}
And now it all makes sense, Why we had to split CurrentTheme
from the main Styles
resources. Because of the line:
var currentTheme = (Application.Current.Resources["CurrentTheme"] as Theme);
If anyone has a better pattern for Styling an application I'd love to hear it