1

I made a custom control called ImageButton that allows me to set a different image for Up, Down, and Inactive states. It can also operate in "normal" mode or in "latched" mode.

It works fine except for one small piece ... the values I set in the XAML aren't applied immediately. It only uses the default values.

Here is the ImageButton.cs

public class ImageButton : Image
{
    public enum State
    {
        Inactive,
        Up,
        Down
    };

    public static readonly BindableProperty CommandProperty =
        BindableProperty.Create("Command", typeof(ICommand), typeof(ImageButton), null);

    public static readonly BindableProperty SourceUpProperty =
        BindableProperty.Create("SourceUp", typeof(string), typeof(ImageButton), null);

    public static readonly BindableProperty SourceDownProperty =
        BindableProperty.Create("SourceDown", typeof(string), typeof(ImageButton), null);

    public static readonly BindableProperty SourceInactiveProperty =
        BindableProperty.Create("SourceInactive", typeof(string), typeof(ImageButton), null);

    public static readonly BindableProperty ToggleProperty =
        BindableProperty.Create("Toggle", typeof(bool), typeof(ImageButton), false);

    public static readonly BindableProperty ToggleStateProperty =
        BindableProperty.Create("ToggleState", typeof(State), typeof(ImageButton), State.Up, BindingMode.TwoWay);

    public ImageButton()
    {
        Initialize();
    }

    public void Initialize()
    {
        switch (ToggleState) // <- this is returning "State.Up" (the default) no matter what is set in the xaml.
        {
            case State.Up:
                Source = SourceUp; 
                break;
            case State.Down:
                Source = SourceDown;
                break;
            case State.Inactive:
                Source = SourceInactive;
                break;
            default:
                Source = SourceUp;
                break;
        }
        GestureRecognizers.Add(new TapGestureRecognizer
        {
            Command = TransitionCommand
        });
    }

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    private ICommand TransitionCommand
    {
        get
        {
            return new Command(async () =>
            {
                if (ToggleState != State.Inactive)
                {
                    AnchorX = 0.48;
                    AnchorY = 0.48;
                    await this.ScaleTo(0.8, 50, Easing.Linear);
                    if (Toggle)
                    {
                        if (ToggleState == State.Down)
                            ToggleState = State.Up;
                        else
                            ToggleState = State.Down;
                    }
                    await this.ScaleTo(1, 50, Easing.Linear);
                    if (Command != null)
                    {
                        Command.Execute(null);
                    }
                }
            });
        }
    }

    public string SourceUp
    {
        get { return (string)GetValue(SourceUpProperty); }
        set { SetValue(SourceUpProperty, value); }
    }

    public string SourceDown
    {
        get { return (string)GetValue(SourceDownProperty); }
        set { SetValue(SourceDownProperty, value); }
    }

    public string SourceInactive
    {
        get { return (string)GetValue(SourceInactiveProperty); }
        set { SetValue(SourceInactiveProperty, value); }
    }

    public bool Toggle
    {
        get { return (bool)GetValue(ToggleProperty); }
        set { SetValue(ToggleProperty, value); }
    }

    public State ToggleState
    {
        get { return (State)GetValue(ToggleStateProperty); }
        set
        {
            SetValue(ToggleStateProperty, value);
            switch (value)
            {
                case State.Up:
                    Source = SourceUp;
                    break;
                case State.Down:
                    Source = SourceDown;
                    break;
                case State.Inactive:
                    Source = SourceInactive;
                    break;
                default:
                    Source = SourceUp;
                    break;
            }
        }
    }
}

The button works if I set it up with a "Source" like so:

<custom:ImageButton 
    Source="i_left.png"
    SourceUp="i_left.png"
    SourceDown="i_right.png"
    SourceInactive="i_close.png"
    Toggle="True"
    ToggleState="Up"
    WidthRequest="{StaticResource IconMedium}"
    HeightRequest="{StaticResource IconMedium}"
    Command="{Binding ImageButton1Command}"/>

I shouldn't need to specify "Source" because in the constructor I set it according to the initial state.
But it appears that "ToggleState" isn't set to my xaml value yet.

I am trying to set it up like this

<custom:ImageButton 
    SourceUp="i_left.png"
    SourceDown="i_right.png"
    SourceInactive="i_close.png"
    Toggle="True"
    ToggleState="Down"
    WidthRequest="{StaticResource IconMedium}"
    HeightRequest="{StaticResource IconMedium}"
    Command="{Binding ImageButton1Command}"/>

And upon load it should be on the "i_right.png" image, but it isn't.

Edit per answer: The following class works as expected!

public class ImageButton : Image
{
    public enum State
    {
        Inactive,
        Up,
        Down
    };

    public static readonly BindableProperty CommandProperty =
        BindableProperty.Create("Command", typeof(ICommand), typeof(ImageButton), null, propertyChanged: OnStateChanged);

    public static readonly BindableProperty SourceUpProperty =
        BindableProperty.Create("SourceUp", typeof(ImageSource), typeof(ImageButton), null, propertyChanged: OnStateChanged);

    public static readonly BindableProperty SourceDownProperty =
        BindableProperty.Create("SourceDown", typeof(ImageSource), typeof(ImageButton), null, propertyChanged: OnStateChanged);

    public static readonly BindableProperty SourceInactiveProperty =
        BindableProperty.Create("SourceInactive", typeof(ImageSource), typeof(ImageButton), null, propertyChanged: OnStateChanged);

    public static readonly BindableProperty ToggleProperty =
        BindableProperty.Create("Toggle", typeof(bool), typeof(ImageButton), false);

    public static readonly BindableProperty ToggleStateProperty =
        BindableProperty.Create("ToggleState", typeof(State), typeof(ImageButton), State.Up, BindingMode.TwoWay, propertyChanged: OnStateChanged);

    public ImageButton()
    {
        Initialize();
    }

    public void Initialize()
    {
        GestureRecognizers.Add(new TapGestureRecognizer
        {
            Command = TransitionCommand
        });
    }

    static void OnStateChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var imageButton = bindable as ImageButton;
        imageButton.SetState();
    }

    public void SetState()
    {
        switch (ToggleState)
        {
            case State.Up:
                Source = SourceUp;
                break;
            case State.Down:
                Source = SourceDown;
                break;
            case State.Inactive:
                Source = SourceInactive;
                break;
            default:
                Source = SourceUp;
                break;
        }
    }

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    private ICommand TransitionCommand
    {
        get
        {
            return new Command(async () =>
            {
                if (ToggleState != State.Inactive)
                {
                    AnchorX = 0.48;
                    AnchorY = 0.48;
                    await this.ScaleTo(0.8, 50, Easing.Linear);
                    if (Toggle)
                    {
                        if (ToggleState == State.Down)
                            ToggleState = State.Up;
                        else
                            ToggleState = State.Down;
                    }
                    await this.ScaleTo(1, 50, Easing.Linear);
                    if (Command != null)
                    {
                        Command.Execute(null);
                    }
                }
            });
        }
    }

    public ImageSource SourceUp
    {
        get { return (ImageSource)GetValue(SourceUpProperty); }
        set { SetValue(SourceUpProperty, value); }
    }

    public ImageSource SourceDown
    {
        get { return (ImageSource)GetValue(SourceDownProperty); }
        set { SetValue(SourceDownProperty, value); }
    }

    public ImageSource SourceInactive
    {
        get { return (ImageSource)GetValue(SourceInactiveProperty); }
        set { SetValue(SourceInactiveProperty, value); }
    }

    public bool Toggle
    {
        get { return (bool)GetValue(ToggleProperty); }
        set { SetValue(ToggleProperty, value); }
    }

    public State ToggleState
    {
        get { return (State)GetValue(ToggleStateProperty); }
        set { SetValue(ToggleStateProperty, value); }
    }
}
jo phul
  • 639
  • 1
  • 9
  • 29

1 Answers1

0

At the time the constructor is called - the bindable properties from XAML haven't been set yet. Hence, you get the default values. To detect the properties' update, and setup your control's state properly - you can use property-changed callback methods.

My recommendation would be to use property-changed callback for every bindable property, the value for which might affect control's current state (because you can never be too sure of the order/sequence the property-values will be set from XAML during load).

For eg:

 public static readonly BindableProperty ToggleStateProperty =
    BindableProperty.Create("ToggleState", typeof(State), typeof(ImageButton), State.Up, BindingMode.TwoWay, propertyChanged: OnToggleStateChanged);


 static void OnToggleStateChanged (BindableObject bindable, object oldValue, object newValue)
 {
      // Property changed implementation goes here
      Initialize();
 }
Sharada Gururaj
  • 13,471
  • 1
  • 22
  • 50
  • Thank you. I briefly tried that and noticed, as you said, the order/sequence of property-values being set by XAML were a problem. I'll rethink the logic and see if I can sort it out. – jo phul May 08 '17 at 01:45
  • It worked wonderfully! Thank you. I have another control to fix now too .. Thanks again! – jo phul May 08 '17 at 01:53