Xamarin Style based on implicit Style

Next article about bugs in Xamarin will touch topic of Styles. Styles for all of controls not only Frame (and if you wondering why Frame you should check first two articles about Xamarin here and here).
First of all if you are using Styles in Xamarin you probably know how to define implicit styles. If not here is a sample:

<Application.Resources>
  <ResourceDictionary>
    <Style TargetType="Label">
      <Setter Property="FontSize" Value="45" />
    </Style>
  </ResourceDictionary>
</Application.Resources>

Almost the same as in WPF. If you will, or already did, use those kind of styles extensively you probably encountered problem with inheritance from them. How to do it? First consider this xaml:

<?xml version="1.0" encoding="utf-8" ?>
<Application 
             
             x_Class="StylesInheritance.App">
  <Application.Resources>
    <ResourceDictionary>
      <Style TargetType="Label">
        <Setter Property="FontSize" Value="45" />
      </Style>
    </ResourceDictionary>
  </Application.Resources>
  <Application.MainPage>
    <ContentPage>
      <Grid>
        <Grid.Resources>
          <ResourceDictionary>
            <Style TargetType="Label">
              <Setter Property="BackgroundColor" Value="Red"></Setter>
            </Style>
          </ResourceDictionary>
        </Grid.Resources>
        <Label Text="Label Text" />
      </Grid>
    </ContentPage>
  </Application.MainPage>
</Application>

You have global implicit style for Label and want, locally for Grid, add red background too. According to this Xamarin documentation it should be possible right? Style have BaseResourceKey property, which won’t be helpful since implicit styles do not have keys (actually they do but we will get back to this), but there is also property BasedOn. If you have Resharper you can even taste how this should work with Intellisense:

Should work, but it do not. Ok. Resharper inserts what it looks like a correct Xaml:

<Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

But after application starts we have (un)welcoming error:

Why this resource is not found? It is there, right? Notice strange key value, we will get back to that.

Maybe you will (or already did tried) to use Key property of StaticResourceExtension?

<Style TargetType="Label" BasedOn="{StaticResource Key={x:Type Label}}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

You will see another error:

That was something to be expected really since Key property is a string.

If you are so distrustful as me, maybe you tried to actually inspect Application resources to check if they are really there:

Implicit style is there oh right. Why Framework cannot find something that put in there itself? If you are perceptive you probably noticed that Keys is collection of strings and implicit styles actually do have keys, which are FullName of target types. We will have to do figure out how to set appropriate key for this type of styles. The easiest way is:

<Style TargetType="Label" BasedOn="{StaticResource Xamarin.Forms.Label}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

<Style TargetType="Label" BasedOn="{StaticResource Key=Xamarin.Forms.Label}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

The problem with first solution is that Resharper marks it as invalid (yeah this is really weak problem, but I like to my code to be clean that way) and second is not even checked for validity – you can put there anything – so it is even worse since you have only runtime errors. Of cource you can also assign proper key to implicit styles and make them explicit, and then assign this key to all controls, and then assign this key to child styles, and… well… this is to much work 😉

On the side, I really do not get why Xamarin Team decided to make Resources keys a string and not objects like in WPF. If this would be the case, second Style declaration would work just fine.

{StaticResource Key={x:Type Label}}

In my opinion this is first bug/improvement they should make.

For fixing this issue I decided to write my own StaticResourceExtension, that will correctly calculate resources keys. Unfortunately as almost everything that could be useful to override in Xamarin is sealed (see Binding i.e.; not sealed in WPF). Because of that we have to do this this way:

[ContentProperty("KeyOrType")]
public class StaticResourceExtExtension : IMarkupExtension
{
    private readonly StaticResourceExtension _xamarinStaticExtension;

    public StaticResourceExtExtension()
    {
        _xamarinStaticExtension = new StaticResourceExtension();
    }

    public object KeyOrType { get; set; }

    public object ProvideValue(IServiceProvider serviceProvider)
    {
        var type = KeyOrType as Type;
        if (type != null)
        {
            _xamarinStaticExtension.Key = type.FullName;
        }
        else
        {
            var s = KeyOrType as string;
            if (s != null)
            {
                _xamarinStaticExtension.Key = s;
            }
        }
        return _xamarinStaticExtension.ProvideValue(serviceProvider);
    }
}

This will work only in newer versions of Xamarin. For example in 1.3.3.6323 this class is internal (which is interesting how it is available in Xaml). In earlier versions you have resolve to reflection or write your own mechanism for obtaining correct resources (it is not really that complicated).

With this you declare your style like this:

<Style TargetType="Label" BasedOn="{stylesInheritance:StaticResourceExt {x:Type Label}}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

Which will still throw and error :/, which is strange to me and it is second bug involving styles inheritance.

It is the same error as in the first try with Xamarin StaticResourceExtension. If you will debug this code you can check for KeyOrType property value:

This is the point when I just stopped amazed what I found. 😀

Really, I expected a bug, but not like this one. If you will look closely on first screen with error, you will notice that the same thing happens in Xamarin extension (and why I think that using Xamarin is like run through minefield). Apparently Xaml parser do not expect here another extension and it threats everything as plain string instead of evaluating it and then putting it in StaticResourceExtension. However this is only one thing. Why there is no closing bracket ‘}’? Maybe first problem originates from second one and parser threats unclosed brackets as just string and not correctly as Xaml declaration. If we add explicitly KeyOrType property to declaration it will be resolved by parser just right.

<Style TargetType="Label" BasedOn="{stylesInheritance:StaticResourceExt KeyOrType={x:Type Label}}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

But we did not got so far without doing it (almost) perfect! Good thing we can change how our extension behaves, in contrast to Xamarin one. We have only to resolve type from its xaml abbreviation. Luckily IServiceProvider have access for type just for that: IXamlTypeResolver. With method of this type:

Type Resolve(string qualifiedTypeName, IServiceProvider serviceProvider = null);

we can easily obain correct type.

public object ProvideValue(IServiceProvider serviceProvider)
{
    var type = KeyOrType as Type;
    if (type != null)
    {
        _xamarinStaticExtension.Key = type.FullName;
    }
    else
    {
        var s = KeyOrType as string;
        if (s != null)
        {
            const string xType = "{x:Type ";
            if (s.StartsWith(xType))
            {
                var typeName = s.Replace(xType, "");
                var xamlTypeResolver = (IXamlTypeResolver)serviceProvider.GetService(typeof(IXamlTypeResolver));
                _xamarinStaticExtension.Key = xamlTypeResolver.Resolve(typeName, serviceProvider).FullName;
            }
            else
            {
                _xamarinStaticExtension.Key = s;
            }
        }
    }
    return _xamarinStaticExtension.ProvideValue(serviceProvider);
}

And it will work. As we wanted we will see big font label on red background.

Of course instead of removing “{x:Type ” part of string it is possible to do this:

public object ProvideValue(IServiceProvider serviceProvider)
{
    var type = KeyOrType as Type;
    if (type != null)
    {
        _xamarinStaticExtension.Key = type.FullName;
    }
    else
    {
        var s = KeyOrType as string;
        if (s != null)
        {
            const string bracket = "{";
            if (s.StartsWith(bracket))
            {
                var xamlTypeResolver = (IXamlTypeResolver)serviceProvider.GetService(typeof(IXamlTypeResolver));
                var extensionName = s.Replace(bracket, "").Split(' ')[0];
                var extension = (TypeExtension)Activator.CreateInstance(xamlTypeResolver.Resolve(extensionName, serviceProvider));
                extension.TypeName = s.Split(' ')[1];
                var typeName = extension.ProvideValue(serviceProvider).FullName;
                _xamarinStaticExtension.Key = typeName;
            }
            else
            {
                _xamarinStaticExtension.Key = s;
            }
        }
    }
    return _xamarinStaticExtension.ProvideValue(serviceProvider);
}

But I think there is no point in that. This code is much less clear and also not 100% percent bulletproof. I think it is worth to do only if you planning to add your own extension for type resolving, because styles added to resources either have Type.FullName key or just some arbitrary name as key. Previous solution is then sufficient. Below is sample application.

StylesInheritance.zip (54.13 kb)

Also there is available public github repository with code for this article and other ones from Xamarin series.

Leave a Reply

Your email address will not be published. Required fields are marked *

Solve : *
1 + 15 =