1
votes

I am trying to implement context actions on my list in Xamarin Forms but can't get it to work. I am not using XAML, but instead creating my layout in code. I am trying to follow the steps in https://developer.xamarin.com/guides/xamarin-forms/user-interface/listview/interactivity/#Context_Actions and I want to push a new page when "Edit" is clicked.

I cleaned up my code and removed my feeble attempts to make things work.

So this is my custom list cell:

public class PickerListCell : TextCell
{
    public PickerListCell ()
    {
        var moreAction = new MenuItem { Text = App.Translate ("Edit") };
        moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
        moreAction.Clicked += async (sender, e) => {
            var mi = ((MenuItem)sender);
            var option = (PickerListPage.OptionListItem)mi.CommandParameter;

            var recId = new Guid (option.Value);

            // This is where I want to call a method declared in my page to be able to push a page to the Navigation stack

        };
        ContextActions.Add (moreAction);
    }
}

And here is my model:

public class OptionListItem
{
    public string Caption { get; set; }

    public string Value { get; set; }
}   

And this is the page:

public class PickerPage : ContentPage
{
    ListView listView { get; set; }

    public PickerPage (OptionListItem [] items)
    {
        listView = new ListView () ;

        Content = new StackLayout {
            Children = { listView }
        };

        var cell = new DataTemplate (typeof (PickerListCell));
        cell.SetBinding (PickerListCell.TextProperty, "Caption");
        cell.SetBinding (PickerListCell.CommandParameterProperty, "Value");


        listView.ItemTemplate = cell;
        listView.ItemsSource = items;
    }

    // This is the method I want to activate when the context action is called
    void OnEditAction (object sender, EventArgs e)
    {
        var cell = (sender as Xamarin.Forms.MenuItem).BindingContext as PickerListCell;

        await Navigation.PushAsync (new RecordEditPage (recId), true);
    }

}

As you can see by my comments in the code, I have indicated where I believe things are missing.

Please assist guys! Thanks!

2

2 Answers

4
votes

Probably is too late for you, but can help others. The way i've found to do this is passing the instance of page on creation the ViewCell.

public class PickerListCell : TextCell
{
    public PickerListCell (PickerPage myPage)
    {
        var moreAction = new MenuItem { Text = App.Translate ("Edit") };
        moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
        moreAction.Clicked += async (sender, e) => {
            var mi = ((MenuItem)sender);
            var option = (PickerListPage.OptionListItem)mi.CommandParameter;

            var recId = new Guid (option.Value);

            myPage.OnEditAction();

        };
      ContextActions.Add (moreAction);
    }
}

So, in your page:

public class PickerPage : ContentPage
{
    ListView listView { get; set; }

    public PickerPage (OptionListItem [] items)
    {
        listView = new ListView () ;

        Content = new StackLayout {
            Children = { listView }
        };

        var cell  = new DataTemplate(() => {return new PickerListCell(this); });            
        cell.SetBinding (PickerListCell.TextProperty, "Caption");
        cell.SetBinding (PickerListCell.CommandParameterProperty, "Value");


        listView.ItemTemplate = cell;
        listView.ItemsSource = items;
    }


   void OnEditAction (object sender, EventArgs e)
   {
        var cell = (sender as Xamarin.Forms.MenuItem).BindingContext as PickerListCell;

        await Navigation.PushAsync (new RecordEditPage (recId), true);
   }

}
1
votes

Ok, so with the help of some posts, specifically this one https://forums.xamarin.com/discussion/27881/best-practive-mvvm-navigation-when-command-is-not-available, I came to the following solution, though I'm not perfectly satisfied with the way it looks.

My custom cell now announces when a command is being executed using MessagingCenter:

public class PickerListCell : TextCell
{

    public PickerListCell ()
    {
        var moreAction = new MenuItem { Text = App.Translate ("Edit") };
        moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
        moreAction.Clicked += async (sender, e) => {
            var mi = ((MenuItem)sender);
            var option = (PickerListPage.OptionListItem)mi.CommandParameter;

            var recId = new Guid (option.Value);

            // HERE I send a request to open a new page. This looks a 
            // bit crappy with a magic string. It will be replaced with a constant or enum
            MessagingCenter.Send<OptionListItem, Guid> (this, "PushPage", recId);
        };
        ContextActions.Add (moreAction);
    }
}

And in my PickerPage constructor I added this subscription to the Messaging service:

MessagingCenter.Subscribe<OptionListItem, Guid> (this, "PushPage", (sender, recId) => {
            Navigation.PushAsync (new RecordEditPage (recId), true);
        });

All this works just find, but I'm not sure if this is the way it was intended to. I feel like the binding should be able to solve this without the Messaging Service, but I can't find out how to bind to a method on the page, only to a model, and I don't want to pollute my model with methods that have dependencies on XF.