Forum Xamarin Xamarin.Forms

Access to ViewModel from ViewCell in separated code

clopezclopez ESMember ✭✭✭

Hello to all of you:

Yes, I know this was asked a lot of times before, I read about it and I tryed some of your solutions, but it didn't solved my issue. So let me explain before sending me to use the search button ^^U

First, I have a tabbed page, who creates a viewmodel and two different pages like this:

editOrderViewModel = new EditOrderViewModel(orderDetails, this) { Navigation = this.Navigation };

        editOrderDetailPage = new EditOrderDetailPage() { BindingContext = editOrderViewModel, Title = TextResources.OrderTab_Details };
        editOrderItemListPage = new EditOrderItemListPage() { BindingContext = editOrderViewModel, Title = TextResources.OrderTab_Lines };


        Children.Add(editOrderDetailPage);
        Children.Add(editOrderItemListPage);

So far, so good.
I have a model like:

public class OrderModel : BaseModel
{
public bool updated { get; set; }
public string CustomerName { get; set; }
public List lines { get; set; }
}

public class OrderItemModel : BaseModel
{
public string description { get; set; }
public int quantity { get; set; }
}

And what I want to achieve is to modify the quantity of an item, and set "updated" to "true".

For that, in the EditOrderItemListPage, I added a ListView from a different file (a View):

<·ListView
x:Name="lst_ListaArticulosPedido"
IsPullToRefreshEnabled="False"
ItemsSource="{Binding Order.lines}"
<·ListView.ItemTemplate>
<·DataTemplate>
<·editOrderLinesViews:EditOrderLineViewCell
x:Name="EditOrderLineListViewObj">
<·/editOrderLinesViews:EditOrderLineViewCell>
<·/DataTemplate>
<·/ListView.ItemTemplate>
<·/ListView>

Well, that EditOrderLineViewCell has the typical Entry with +/- buttons to change the quantity (and lots of extra information). That buttons were easy to implement, but then I want to use the TextChanged event of the entry to set "updated" to "true" and perform some other OrderModel modifications.

I read a lot of ways to do it if I have the content of the ViewCell written in the EditOrderItemListPage (yes, changing the BindingContext of the control to the EditOrderItemListPage page's BindingContext), but I don't want to do that (i don't like files with more than 500 lines :D).
I read that you can achieve this if you use Parent variable to find the Parent Page instance, and then execute a public method. That's a good idea but it's said that it's a bad practise.

So, I though, to make things easier, to pass to the ViewCell the ViewModel instance using constructor parameters or some other way, or better yet (for maintain MVVM esque) the EditOrderItemListPage instance to it's children. But I don't know how to do it via XAML.
What do you think? Could you lend me a hand?

Thank you.

Posts

  • clopezclopez ESMember ✭✭✭
    edited April 2018

    So, there is no way to change the value of a variable of the ViewCells from the main page? or the opposite?

    I tried to copy the entire View to the main page file, but then I loose the code behind of the view cell, and all their functions had to be attached to the page ViewModel, so my button inside the ViewCell looks like this:

    <·Button
    x:Name="btn_MinusNArticulos"
    Text="+"
    FontSize="Medium"
    FontAttributes="Bold"
    BindingContext="{Binding Source={x:Reference myListView}, Path=BindingContext}"
    Command="{Binding AddQuantityCommand}"
    CommandParameter="{Binding Source={x:Reference txt_NArticulos}}"
    Margin="20,0,0,0"
    />

    Where txt_NArticulos is a label in the same ViewCell. But the parameter I really need to pass to AddQuantityCommand function is the OrderItemModel asociated to that Cell. How can I do that?

    Sorry for insist, but I'm a bit hurry, because I have a very tight deadline for this.

  • clopezclopez ESMember ✭✭✭

    Ok, so I got this almost working, but binding is not working:

    At the end, my button has this code in the xaml:
    <·Button
    x:Name="btn_MinusNArticulos"
    Text="+"
    FontSize="Medium"
    FontAttributes="Bold"
    Clicked="btn_PlusNArticulos_Clicked"
    CommandParameter="{Binding .}"
    Margin="20,0,0,0"/>

    That way, with "Clicked" and "CommandParameter", I can go to code behind and write this method:

    private void btn_PlusNArticulos_Clicked(object sender, EventArgs e)
    {
    Button _button = (Button)sender;
    OrderItemModel _item = (OrderItemModel)_button.CommandParameter;

            if (BindingContext != null)
                ((EditOrderViewModel)BindingContext).AddQuantityCommand.Execute(_item);
    
        }
    

    Now sender has the Button instance, and in its CommandParameter attribute, the OrderItemModel of the Cell. With that information, I can execute the command AddQuantityCommand from the Page's ViewModel, and change the data of the OrderItemModel and the OrderModel where it's contained.

    public OrderModel Order { get; set; }

    private Command _AddQuantityCommand;
    public Command AddQuantityCommand
    {
    get { return _AddQuantityCommand ?? (_AddQuantityCommand = new Command(AddQuantityToItem)); }
    }

        private void AddQuantityToItem(object _obj)
        {
            OrderItemModel _orderItem = (OrderItemModel)_obj;
            _orderItem.quantity++;
            _orderItem.lineTotal = _orderItem.unitPrice * _orderItem.quantity;
            Order.updated = true;
        }
    

    But none of the changes done on the Order variables are shown in the form. :S
    Any clue?

  • clopezclopez ESMember ✭✭✭

    Ok, so I wasn't updating the bindable variable of the OrderModel ^^U. Solved that part.
    But the list is not updating. At first I though that it was because I assigned a List<·OrderItemModel> to ItemSource, but I moved that list to a new ObservableCollection, but the values are not changing, and in the ObservableCollection they do.

    VIEWMODEL

        public OrderModel Order { get; set; }
    
        private ObservableCollection<·OrderItemModel> _OrderLines;
        public ObservableCollection<·OrderItemModel> OrderLines
        {
            get { return _OrderLines; }
            set
            {
                _OrderLines = value;
                OnPropertyChanged("OrderLines");
            }
        }
    
        public EditOrderViewModel(OrderModel _order, Page currentPage)
        {
            if (_order != null)
            {
                Order = _order;
                OrderLines = new ObservableCollection<OrderLineModel>(_order.lines);
                Order.lines = null;  //Just in case
            }
            else
            {
                Order = new OrderDetailModel(true);
                OrderLines = new ObservableCollection<OrderLineModel>();
            }
    
            _CurrentPage = currentPage;            
        }
    
        private Command _SubstractQuantityCommand;
        public Command SubstractQuantityCommand
        {
            get { return _SubstractQuantityCommand ?? (_SubstractQuantityCommand = new Command(SubstractQuantityToItem)); }
        }
    
        private void SubstractQuantityToItem(object _obj)
        {
            OrderItemModel _orderLine = (OrderItemModel)_obj;
            _orderLine.quantity--;
            _orderLine.lineTotal = _orderLine.unitPrice * _orderLine.quantity;
            Order.updated = true;
        }
    

    XAML

              <·ListView
                    x:Name="lst_MyList"
                    IsPullToRefreshEnabled="False"
                    ItemsSource="{Binding OrderLines}"
                    HasUnevenRows="True"
                    <·ListView.ItemTemplate>
                        <·DataTemplate>
    
    
                            <·ViewCell>
                                <·ViewCell.View>
                                    <·Grid>
                                            <·StackLayout
                                                Grid.Column="0"
                                                Orientation="Horizontal"
                                                Spacing="0">
                                                <·Button
                                                    x:Name="btn_MinusNArticulos"
                                                    Text="-"
                                                    Clicked="btn_MinusNArticulos_Clicked"
                                                    CommandParameter="{Binding .}"
                                                    />
                                                <·Label
                                                    x:Name="lbl_NArticulos"
                                                    Text="{Binding quantity, Mode=TwoWay}"
                                                    />
    

    CODEBEHIND

        private void btn_MinusNArticulos_Clicked(object sender, EventArgs e)
        {
            Button _button = (Button)sender;
            OrderItemModel _item = (OrderItemModel)_button.CommandParameter;
    
            if (_item.quantity > _item.openQuantity)
            {
                if (BindingContext != null)
                    ((EditOrderViewModel)BindingContext).SubstractQuantityCommand.Execute(_item);
    
            }
        }
    
  • clopezclopez ESMember ✭✭✭

    Ok, I went back to the original idea of having a custom ViewCell.

    I was able to add the main page BindingContext to the custom ViewCells. I created a bindable property using this:
    https://forums.xamarin.com/discussion/71462/datatemplate-with-templateselector-bind-to-parent-command
    And using this as the main idea:
    https://forums.xamarin.com/discussion/61317/calling-command-from-viewmodel-using-button-within-listview

    I wrote this on the XAML page:

    <·?xml version="1.0" encoding="utf-8" ?>
    <·ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:editOrderLinesViews="clr-namespace:App.Views.Orders"
    x:Name="pag_editOrderItemListPage"
    x:Class="App.Pages.Orders.EditOrderItemListPage">

    <·ContentPage.Content>
        <·StackLayout
        BackgroundColor="{x:Static statics:Palette._MainColorDark}"
        Spacing="2">
    
                <·ListView
                    IsPullToRefreshEnabled="False"
                    ItemsSource="{Binding OrderLines}"
                    HasUnevenRows="True"
                    <·ListView.ItemTemplate>
                        <·DataTemplate>
    
    
                            <·editOrderLinesViews:EditOrderLineViewCell
                                x:Name="EditOrderLineListViewObj"
                                ParentBindingContext="{Binding Source={x:Reference pag_editOrderItemListPage}, Path=BindingContext}">
                            <·/editOrderLinesViews:EditOrderLineViewCell>
    
    
                        <·/DataTemplate>
                    <·/ListView.ItemTemplate>
                <·/ListView>
    

    And in the code behind the ViewCell I added

        public static BindableProperty ParentBindingContextProperty = BindableProperty.Create(nameof(ParentBindingContext), typeof(object), typeof(EditOrderLineViewCell), null);
    
        public object ParentBindingContext
        {
            get { return GetValue(ParentBindingContextProperty); }
            set { SetValue(ParentBindingContextProperty, value); }
        }
    

    This way, I can use ParentBindingContext to call ViewModel page commands

        ((EditOrderViewModel)ParentBindingContext).UpdateOrderPriceCommand.Execute(null);
    

    But I still have a problem: I have this label.

    <·Label
    Grid.Column="2"
    x:Name="lbl_TotalData"
    Text="{Binding lineTotal, Mode=TwoWay, Converter={StaticResource currencyDoubleConverter}}"
    FontSize="Medium"
    FontAttributes="Bold"
    />

    And in the code behind this method:

        private void txt_NArticulos_TextChanged(object sender, TextChangedEventArgs e)
        {
            OrderItemModel _orderLine = (OrderItemModel)BindingContext;
            _orderLine.lineTotal = _orderLine.unitPrice * _orderLine.quantity;
            _orderLine.UpdatedAt = DateTime.Now;
        }
    

    But when this method runs, the OrderItemModel got changed, but not the label. Why?

  • c0deOn3c0deOn3 USMember ✭✭

    @clopez thank you! I faced a similar issue and was able to fix it with your example.

Sign In or Register to comment.