DancingBaptist
DancingBaptist

Reputation: 33

Xamarin: ImageButton not invoking command from style setter

I am trying to create a custom navigation/"back" button in my mobile app. I want to bind the command as a setter property in a custom style and then apply that style to the ImageButton element in a larger custom element called HeaderNavigation.

Everything builds so that I can see my ImageButton with its styles applied. I can also see the command applied to the ImageButton when I watch its styles but, if I click on the ImageButton, the command is not called.

I've played around with the binding a little bit because I'm assuming that's the issue but no dice yet so I'm worried what I'm attempting isn't possible or maybe there's something stupid I'm missing.

Even if someone could just give me some debugging tips for mobile bindings, that would be helpful.

Thanks!

Code below:

app.xaml

<Style x:Key="NavButton" TargetType="ImageButton" BasedOn="{StaticResource IconButton}">
        <Setter Property="Source" Value="back.png"/>
        <Setter Property="HorizontalOptions" Value="EndAndExpand"/>
        <Setter Property="Command" Value="{Binding Source={RelativeSource AncestorType={x:Type local:App}},Path=BindingContext.OnBackButtonPressed}"/>
        <Setter Property="BackgroundColor" Value="{StaticResource Background}"/>
</Style>

App.xaml.cs

public async void OnBackButtonPressed(object sender, EventArgs e)
{
        // I do not get called :(
        _ = await Navigation.PopAsync();
}

HeaderNavigation.cs

public class HeaderNavigation : StackLayout
{
    private ImageButton _returnButton;

        public HeaderNavigation()
        {
            _returnButton = new ImageButton();

            _returnButton.Style = (Style)Application.Current.Resources["NavButton"];

            Children.Add(_returnButton);
        }
    }
}

Upvotes: 3

Views: 131

Answers (2)

IV.
IV.

Reputation: 9438

My understanding is that your goal is to have the back button in your custom NavigationHeader call the OnBackButtonPressed method in your App class:

public partial class App : Application
{
    public async void OnBackButtonPressed(object sender, EventArgs e)
    {
        // This pop up will be the test.
        await MainPage.DisplayAlert("Message from button", "It worked", "OK");
    }
}

An approach that I've tested Download from GitHub to achieve the outcome you want factors in the likelihood that your custom NavigationHeader is going to be consumed in a view of some kind (e.g. ContentPage).

Try making this change in your Style declaration:

<Setter Property="Command" Value="{Binding BackButtonPressedCommand}" />

This way, when you consume your custom NavigationHeader e.g. in your MainPage.xaml ...

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:custom_back_button="clr-namespace:custom_back_button"
         x:Class="custom_back_button.MainPage">

<StackLayout>
    <custom_back_button:HeaderNavigation  VerticalOptions="CenterAndExpand"/>
</StackLayout>

...the bound command will be invoked in the context of the MainPage. (Likewise, if you consume it on some other page it will soak up the BindingContext of that page.)

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        BindingContext = new MainPageBinding();
        InitializeComponent();
    }
}

The ICommand is resolved in the BindingContext resulting in a call to the OnBackButtonPressed in this class. Now use ((App)Application.Current) to pass the action along to the App class as shown.

class MainPageBinding
{
    public MainPageBinding()
    {
        BackButtonPressedCommand = new Command(OnBackButtonPressed);
    }

    public ICommand BackButtonPressedCommand { get; private set; }
    private void OnBackButtonPressed(object o)
    {
        ((App)Application.Current).OnBackButtonPressed(o, EventArgs.Empty);
    }
}

Pressing the back button shows a successful call to the popup coded in App.OnBackButtonPressed.

enter image description here

Upvotes: 1

ToolmakerSteve
ToolmakerSteve

Reputation: 21321

Two problems:

  1. That method isn't a command. Property Command needs to be bound to a Command. in App.xaml.cs:
public Command BackButtonCommand { get; } = new Command(BackButtonAction);

private void BackButtonAction()
{
    ...
}
  1. App does not have a BindingContext. Refer to a command on the App object itself. Change xaml value to:
... Value="{Binding Source={RelativeSource AncestorType={x:Type local:App}}, Path=BackButtonCommand}" ...

NOTE: Not tested.

Upvotes: 1

Related Questions