1

I am building an application in Xamarin.Forms, and just discovered Pixate Freestyle, which is fantastic. I have used it to set a default button class, for example, to put base default styling on all my buttons, which is great.

However, I have not been able to figure out if there is a way to apply styles to specific objects using views written in Xamarin.Forms. I have tried setting button.StyleId, but that has not done it. I also tried adding the PixateFreestyle DLL to the base project, but A)wasn't sure if I could add both iOS and Android and B)even when added, they didn't provide the calls (not surprised, but figured I'd give it a shot)

Is there a way to apply styles for Freestyle through Xamarin.Forms? Possibly some sort of call in the iOS project to utilize a feature passed through .Forms to set the class using Freestyle?

Thanks in advance for any help.

Mike
  • 629
  • 5
  • 18
  • Thank you to all for your help! Unfortunately I have not had a chance to try and apply these answers, but will respond when I do! – Mike Dec 02 '14 at 16:00

2 Answers2

1

Yes and Maybe No.

Yes: you can create custom renderer for the view types you want to be able to customize, which will give you a link between your XAML and your iOS app. On the iOS side you can perform all the calls to all libraries you need. You can then create attached properties (such as the Style example below) that can hold additional information you may need to configure the render and put these attached properties in a Style resource and use them around your project. Xamarin.Forms doesn't yet support merging resource dictionaries but you can write a bit of code to do that so all your styles would be in one location

however... Maybe No: there are many predefined controls and their respective renderers in Xamarin.Forms, it will be difficult to create a consistent addon to the framework that works in all scenarios, for example a button inside a ListView may not render correctly (give it a shot)

If you do give it a shot, once you have the basic renderer that works on properties you can package a set of properties together with a Style extension

public class Setter
    {
        public string Property { get; set; }

        public object Value { get; set; }

        public string ConverterKey { get; set; }

        public object ConverterParameter { get; set; }
    }

[ContentProperty ("Children")]

public class Style

    {
        public ResourceDictionary Resources { get; set; }

        public Style ()
        {
            Children = new List<Setter> ();
        }

        public List<Setter> Children { get; private set; }

        public static readonly BindableProperty StyleProperty = 
            BindableProperty.CreateAttached<BindableObject, Style> ((bob) => GetStyle (bob), null, BindingMode.OneWay
                , propertyChanged: (bindable, oldvalue, newvalue) => {
                    if (newvalue != null) { 
                        var tinf = bindable.GetType ().GetTypeInfo ();
                        foreach (var setter in newvalue.Children) {  
                            PropertyInfo pinfo = null;
                            while (pinfo == null && tinf != null) {
                                pinfo = tinf.DeclaredProperties.FirstOrDefault (p => p.Name == setter.Property);
                                if (pinfo == null) {
                                    tinf = tinf.BaseType.GetTypeInfo ();
                                    if (tinf == typeof(object).GetTypeInfo ())
                                        break;
                                } 
                            }

                            if (pinfo != null) {
                                object convertedValue = null;

                                if (setter.ConverterKey != null && newvalue.Resources != null) {
                                    object valCon;
                                    if (newvalue.Resources.TryGetValue (setter.ConverterKey, out valCon) && valCon != null) { 
                                        if (valCon is IValueConverter)
                                            convertedValue = ((IValueConverter)valCon).Convert (setter.Value, pinfo.PropertyType, setter.ConverterParameter, System.Globalization.CultureInfo.CurrentUICulture);
                                        else if (valCon is TypeConverter)
                                            convertedValue = ((TypeConverter)valCon).ConvertFrom (setter.Value);
                                        else
                                            convertedValue = Convert.ChangeType (setter.Value, pinfo.PropertyType);
                                    } else
                                        convertedValue = Convert.ChangeType (setter.Value, pinfo.PropertyType);
                                } else
                                    convertedValue = Convert.ChangeType (setter.Value, pinfo.PropertyType);

                                pinfo.SetMethod.Invoke (bindable, new [] { convertedValue  });  
                            }
                        } 
                    }
                });

        public static Style GetStyle (BindableObject bindable)
        {
            return (Style)bindable.GetValue (StyleProperty);
        }

        public static void SetStyle (BindableObject bindable, Style value)
        {
            bindable.SetValue (StyleProperty, value);
        } 
    }

And in XAML...

<f:Style x:Key="stackStyle">
    <f:Style.Resources>
        <ResourceDictionary>
            <ColorTypeConverter x:Key="colorConverter" />
        </ResourceDictionary>
    </f:Style.Resources>
    <f:Setter Property="BackgroundColor" Value="#3898DC" ConverterKey="colorConverter" />
</f:Style>

...

<StackLayout f:Style.Style="{StaticResource stackStyle}">
...
Sten Petrov
  • 10,943
  • 1
  • 41
  • 61
0

I updated this to get it work. There is a custom renderer for each control, and on the events: OnElementPropertyChanged, OnElementChanged the style class and id are taken from the xamarin.forms control and transfered to the native control.

public static class StyledRenderer {

    public static void UpdateStyle (UIView view, VisualElement model = null) {
        var styleId = model.StyleId;
        var classId = model.ClassId;
        UpdateStyle (view, styleId, classId);
    }

    public static void UpdateStyle (UIView view, string styleId, string classId) {
        Console.WriteLine ("Update style " + styleId + " " + classId + " for " + view);

        if (!string.IsNullOrWhiteSpace (styleId)) {
            view.SetStyleId (styleId);
        }
        if (!string.IsNullOrWhiteSpace (classId)) {
            view.SetStyleClass(classId);
        }
        view.UpdateStylesNonRecursivelyAsync ();
    }

    public static void StyleOnElementPropertyChanged (UIView control, VisualElement element, object sender, PropertyChangedEventArgs e) {
        if (e.PropertyName == "ClassId" || e.PropertyName ==  "StyleId") {
            StyledRenderer.UpdateStyle (control, element);
        }
    }

    public static void StyleOnElementChanged ( UIView control, VisualElement element) {
        StyledRenderer.UpdateStyle (control, element);
    }
}

public class StyledEntryRenderer : EntryRenderer
{   
    protected override void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e) {
        base.OnElementPropertyChanged (sender, e);
        StyledRenderer.StyleOnElementPropertyChanged (Control, Element, sender, e);
    }

    protected override void OnElementChanged (ElementChangedEventArgs<Entry> e) {
        base.OnElementChanged (e);
        StyledRenderer.StyleOnElementChanged (Control, e.NewElement);
    }
}

It works great.

Sabina
  • 1
  • 1