11
votes

I need to change the text color of a button when it is disabled, I have created a custom renderer for iOS and one for Android. The iOS worked perfectly, since the android does not change the color, I have also created triggers via styles and also does not solve.

How to make color swap work for Xamarin.Forms?

Android Renderer:

[assembly: ExportRenderer(typeof(Button), typeof(MyButtonRenderer))]
namespace xxxxxx.Droid.Renderers
{
    public class MyButtonRenderer : ButtonRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
                Control.SetTextColor(Color.White.ToAndroid());

            if (e != null)
            {
                e.NewElement.TextColor = Color.White;
                e.OldElement.TextColor = Color.White;
            }
        }
    }
}

This state change is changed according to the CanExecute of my commands, this would be the default style applied.

Neither of these ways solves

6

6 Answers

17
votes

For Android:

[assembly: ExportRenderer(typeof(Button), typeof(MyButtonRenderer))]
namespace Forms.Droid.Renderers
{
    public class MyButtonRenderer : ButtonRenderer
    {
        public MyButtonRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<Button> args)
        {
            base.OnElementChanged(args);
            if (Control != null) SetColors();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(sender, args);
            if (args.PropertyName == nameof(Button.IsEnabled)) SetColors();
        }

        private void SetColors()
        {
            Control.SetTextColor(Element.IsEnabled ? Element.TextColor.ToAndroid() : Android.Graphics.Color.Gray);
            Control.SetBackgroundColor(Element.IsEnabled ? Element.BackgroundColor.ToAndroid() : Android.Graphics.Color.DarkGray);
        }
    }
}

For iOS:

[assembly: ExportRenderer(typeof(Button), typeof(MyButtonRenderer))]
namespace Forms.iOS.Renderers
{
    public class MyButtonRenderer : ButtonRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Button> args)
        {
            base.OnElementChanged(args);
            if (Control != null) SetColors();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(sender, args);
            if (args.PropertyName == nameof(Button.IsEnabled)) SetColors();
        }

        private void SetColors()
        {
            Control.SetTitleColor(Element.IsEnabled ? Element.TextColor.ToUIColor() : UIColor.Gray, Element.IsEnabled ? UIControlState.Normal : UIControlState.Disabled);
            Control.BackgroundColor = Element.IsEnabled ? Element.BackgroundColor.ToUIColor() : UIColor.DarkGray;
        }
    }
}
10
votes

I use VisualStateManager to add different styles for each button state: normal, focused, pressed, disabled

<Style TargetType="Button">
        <Setter Property="VisualStateManager.VisualStateGroups">
            <VisualStateGroupList>
                <VisualStateGroup x:Name="CommonStates">
                    <VisualState x:Name="Normal">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor" Value="{StaticResource ColorAccent}" />
                            <Setter Property="TextColor" Value="{StaticResource ColorForegroundIcon}" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState x:Name="Pressed">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor" Value="{StaticResource ColorAccentTransparent}" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState x:Name="Focused">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor" Value="{StaticResource ColorAccentTransparent}" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState x:Name="Disabled">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor" Value="{DynamicResource ColorBackgroundDisabled}" />
                            <Setter Property="TextColor" Value="{DynamicResource ColorForegroundFaded}" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateGroupList>
        </Setter>
    </Style>

For more info and examples, check out: Understanding Visual State Manager in Xamarin Forms

1
votes

Another way to do this without messing with custom renderers is to make the button InputTransparent and to block presses when input transparent. When InputTransparent is set to true the user will not be able to click on the button but it will keep all its styling. The only problem is that it is possible to still click the button if you have the button activate when the last input field is filled out. The way to solve this is to add a simple InputTransparent check in the button click method.

XAML:

<!-- Uses "InputTransparent" instead of "IsEnabled" so we can have control of the text color when the button is "Disabled" -->
<!-- Must add "if (btnLogin.InputTransparent == true) return;" code to the top of the button clicked method of this event to block "LastControl" auto click -->
<Button Text="im back!" x:Name="btnLogin" Style="{StaticResource BaseButtonStyleTransparent}" WidthRequest="225" InputTransparent="True" TextColor="#999999">
    <Button.Triggers>
        <MultiTrigger TargetType="Button">
            <MultiTrigger.Conditions>
                <BindingCondition Binding="{Binding Source={x:Reference txtEmail}, Path=IsValid, Converter={StaticResource dataHasBeenEntered}}" Value="true" />
                <BindingCondition Binding="{Binding Source={x:Reference txtPassword}, Path=IsValid, Converter={StaticResource dataHasBeenEntered}}" Value="true" />
            </MultiTrigger.Conditions>
            <Setter Property="InputTransparent" Value="False" />
            <Setter Property="TextColor" Value="White" />
        </MultiTrigger>
    </Button.Triggers>
</Button>

C#:

private async void LoginClicked(object sender, EventArgs e)
{
    // If the button is psudo disabled dont let the user click the button
    if (btnLogin.InputTransparent == true)
        return;

    // Rest of LoginClicked Code   
}
1
votes

I know this is an old thread but would like to share a simple solution that worked for me. Hope this helps if this is what you are looking for. In order to assign Text Color or any property to Button which is not accessible in code behind, try doing,

("YourControl" as Button).TextColor= .... You will be exposed to all the properties of that control.

0
votes

If CanExecute works as expected, then your IsEnabled property should be updated accordingly. You can listen to this property value changes through the OnElementPropertyChanged method.

public class MyButtonRenderer : ButtonRenderer
{

    ....

    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == nameof(Button.IsEnabled))
        {
            Element.TextColor = Element.IsEnabled ? Color.White : Color.Gray;
        }
    }

    ...

}
0
votes

since the android does not change the color, I have also created triggers via styles and also does not solve.

There are some possibilities that might cause the problem:

  1. Most likely, you are defining the renderer using wrong Button type, there are Android.widget.Button and Xamarin.Forms.Button in Droid project, what you need for defining a renderer is Xamarin.Forms.Button:

    [assembly:ExportRenderer(typeof(Xamarin.Forms.Button),
                             typeof(MyButtonRenderer))]
    namespace xxxxxxxxx.Droid
    {
      ...
    }
    
  2. You don't need to set e.OldElement.TextColor = Color.White;. And actually at that time e.OldElement is probably null, as it stands for the old element. So just remove this line. and the codes will work fine.