Click here to Skip to main content
15,867,453 members
Articles / Mobile Apps / Xamarin

Xamarin Style Based On Implicit Style

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
23 Apr 2016CPOL5 min read 9.4K   1  
If you are using Styles in Xamarin, you probably know how to define implicit styles. But how to inherit from them in other Styles? It is possible but require so work. Here is how you can do it.

This article is an entry in our Microsoft Azure IoT Contest. Articles in this section are not required to be full articles so care should be taken when voting.

Next article about bugs in Xamarin will touch topic of Styles. Styles for all of controls not only Frame (and if you are wondering why Frame, you should check the 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:

XML
<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 problems with inheritance from them. How to do it? First consider this XAML:

XML
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             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 has 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:

Image 1

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

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

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

 Image 2

Why is this resource 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?

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

You will see another error:

 Image 3

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:

 Image 4

Implicit style is there, oh right, why Framework cannot find something that puts in there itself? If you are perceptive, you probably noticed that Keys is a 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:

XML
<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 the first solution is that Resharper marks it as invalid (yeah, this is a really weak problem, but I like my code to be clean that way) and the second is not even checked for validity - you can put anything there - so it is even worse since you have only runtime errors. Of course, 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 Wink.

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.

XML
{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 it 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, i.e., in 1.3.3.6323, this class is internal, which is interesting how it is available in XAML. In earlier version, you have resolved 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:

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

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

 Image 7

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:

Image 8

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

Really, I expected a bug, but not like this one. If you will look closely at the first screen with error, you will notice that the same thing happens in Xamarin extension (and why I think that using Xamarin is like running through minefield). Apparently, XAML parser does 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 the first problem originates from the 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.

XML
<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. Good thing IServiceProvider has access for type just for that: IXamlTypeResolver. With method of this type:

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

we can easily obtain the 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.

 Image 10

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 doing 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. The previous solution is then sufficient. A sample application is available at the top of this post.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Poland Poland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --