Search code examples
c#xamlmauimaui-community-toolkit

Use styles with CommunityToolkit IconTintColorBehavior


EDIT : H.A.H pointed me to the right direction, my solution at the end of this post !

I'm trying to use the IconTintColorBehavior behavior to change the tint of a picture in my MauiApp, and I want to specify the tint in a style ResourceDictionary.

I tested setting the colors manually by setting the Color value in the content page, the icon is correctly recolored, now I want to avoid code repetition by specifying the coloring in my style page.

I've tried two approaches :

  • Specifying the colors directly in the style page :

In my Style page :

<Style TargetType="tk:IconTintColorBehavior">
    <Setter Property="TintColor" Value="{AppThemeBinding Light={StaticResource Yellow100Accent}, Dark={StaticResource Blue200Accent}}"/>
</Style>

In my Content page :

<Image Source="logo_transparent.svg" Aspect="AspectFit">
    <Image.Behaviors>
        <tk:IconTintColorBehavior/>
    </Image.Behaviors>
</Image>

In that case, the icon appears completely white (My guess is since I don't specify any color, it takes a default white as TintColor, and the style fails to apply)


  • Creating a custom behavior

I also tried following this guide, and since just trying to set the TintColor property of IconTintColorBehavior didn't work, implement my own IconThemeColorBehavior class inheriting from IconTintColorBehavior with all extra methods given by the guide, but no success as well :

My Behavior :

public class IconThemeColorBehavior : IconTintColorBehavior
{
    public static readonly BindableProperty AttachBehaviorProperty =
    BindableProperty.CreateAttached("AttachBehavior", typeof(bool), typeof(IconThemeColorBehavior), false, propertyChanged: OnAttachBehaviorChanged);

    public static bool GetAttachBehavior(BindableObject view)
    {
        return (bool)view.GetValue(AttachBehaviorProperty);
    }

    public static void SetAttachBehavior(BindableObject view, bool value)
    {
        view.SetValue(AttachBehaviorProperty, value);
    }

    static void OnAttachBehaviorChanged(BindableObject view, object oldValue, object newValue)
    {
        Entry entry = view as Entry;
        if (entry == null)
        {
            return;
        }

        bool attachBehavior = (bool)newValue;
        if (attachBehavior)
        {
            entry.Behaviors.Add(new IconThemeColorBehavior());
        }
        else
        {
            Behavior toRemove = entry.Behaviors.FirstOrDefault(b => b is IconThemeColorBehavior);
            if (toRemove != null)
            {
                entry.Behaviors.Remove(toRemove);
            }
        }
    }
}

In my Style page:

<Style x:Key="ThemedIconStyle" TargetType="Image">
    <Setter Property="utils:IconThemeColorBehavior.TintColor" Value="{AppThemeBinding Light={StaticResource Yellow100Accent}, Dark={StaticResource Blue200Accent}}"/>
</Style>

In my Content page :

<Image Source="logo_transparent.svg" Aspect="AspectFit" Style="{StaticResource ThemedIconStyle}"/>

In that case I always end up with my icon keeping its base color instead of being recolored.


  • Correct approach : Custom image control with behaviors
using CommunityToolkit.Maui.Behaviors;

namespace MauiUtils
{
    public class ImageTintColor : Image
    {
        public static readonly BindableProperty TintColorProperty =
        BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(ImageTintColor), propertyChanged: OnTintColorChanged);

        public Color TintColor
        {
            get => (Color)GetValue(TintColorProperty);
            set => SetValue(TintColorProperty, value);
        }

        static void OnTintColorChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (ImageTintColor)bindable;
            var tintColor = control.TintColor;

            if (control == null)
            {
                return;
            }

            Behavior toRemove = control.Behaviors.FirstOrDefault(b => b is IconTintColorBehavior);

            if (toRemove is not null)
            {
                control.Behaviors.Remove(toRemove);
            }
            if (newValue is not null)
            {
                IconTintColorBehavior tintBehavior = new IconTintColorBehavior
                {
                    TintColor = tintColor
                };
                control.Behaviors.Add(tintBehavior);
            }

            // This part actually seems to be useless
            void OnHandlerChanged(object s, EventArgs e)
            {
                OnTintColorChanged(control, oldValue, newValue);
                control.HandlerChanged -= OnHandlerChanged;
            }
        }
    }
}


Solution

  • This here:

    <Image.Behaviors>
        <tk:IconTintColorBehavior/>
    </Image.Behaviors>
    

    Makes absolutely no sense, and will not work. (You can always click F12 to see IconTintColorBehavior class, and understand what "white" color are you actually setting, and what filter is being applied like that.)

    This is what you usually do:

    <Image.Behaviors>
       <toolkit:IconTintColorBehavior TintColor="{AppThemeBinding Light={StaticResource IconTintLight}, Dark={StaticResource IconTintDark}}"/>
    </Image.Behaviors>
    

    You have IconTintLight and IconTintDark in the resources. This is not exactly code repetition, because you can adjust project wide tints with one resource value change. (You won't have to visit every repetition and apply the change yourself).

    This is more than enough, as far as I am concerned.

    You can, of course write your own MyIconTintBehavior class. By making a copy of the original as starting point. (This what you have is so far from it. F12 ctrl-a, ctrl-c, ctrl-v, let's call it "getting inspired by it").

    And of course, proper action will also be to make a custom Image control, or Control Template. So instead of targeting the reuse of behavior, you can target reuse of controls.