How to bind the items of a listview to a seperate viewmodel?

Hi, this is about seperation of concerns and is about binding a listview item to a seperate viewmodel instead of the model itself. I was looking for a way to clean up my models that implemented a few properties only for presentation purpuses. Then I stumbled upon this post: "burkharts dot net slash apps slash blog slash advanced-listview-bindings" <- I am not yet allowed to post the source...
But maybe because I'm fairly new to Xamarin.Forms and startet learning with FreshMvvm I do not understand one or two points that are going on right here.

I hope it is ok if I cite the code passages.
So, instead of this common scenario where the model (dog listview item) is directly tied to the view

// viewmodel
[ImplementPropertyChanged]  //I use Fody here to not have to bother with INotifyPropertyChanged
public class DemoListViewPageModel
{
    public ObservableCollection<Dog> DogList

    public DemoListViewPageModel()
    {
        DogList = new ObservableCollection<Dog>();

        DogList.Add(new Dog() { Name = "Rex", Race = "German Sheppard" });
        DogList.Add(new Dog() { Name = "Barney", Race = "Poodle" });
        DogList.Add(new Dog() { Name = "Jimmy", Race = "Beagle" });
        DogList.Add(new Dog() { Name = "Rob", Race = "Labrador" });
    }
}

// model
// Should be something that makes a bit of sense, so why not dogs
[ImplementPropertyChanged]
public class Dogs
{
    public string Name {get; set}

    public string Race {get; set;}
}

// view
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="..."
             xmlns:x="..."
             x:Class="Examples.DemoListViewPage">
  <ContentPage.Content>
    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="Center">
      <ListView ItemsSource="{Binding DogList}" HasUnevenRows="True">
        <ListView.ItemTemplate>
          <DataTemplate>         
            <ViewCell>
              <StackLayout HorizontalOptions="Start" >
                <Label x:Name="Name"  Text="{Binding Name}"  TextColor="Navy" FontSize="20"/>
                <Label x:Name="Race" Text="{Binding Race}"/>
              </StackLayout>
            </ViewCell>           
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackLayout>
  </ContentPage.Content>
</ContentPage>

he recommends to introduce another viewmodel for the listview items like this:

// dog item viewmodel
public class DogsItemViewModel
{
    private Dog DogObject;
    ICommand AddToCartCommand;

    public DogsItemViewModel(Dog theDog)
    {
        DogObject = theDog;
        AddToCartCommand = new Xamarin.Forms.Command(AddToCart);
    }

    public string Name => DogObject.Name;
    public string Race => DogObject.Race.ToUpper();
    public bool IsChecked { get; set; }

    void AddToCart()
    {
        WebService.Instance.AddToCart(DogObject);  // Whatever this will do ;-) 
    }
}

// dog item view
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="..." xmlns:x="..." 
             x:Class="YourNameSpace.DogItemView">


  <StackLayout HorizontalOptions="Start" >
    <CheckBox State = "{Binding IsChecked}" />
    <Label x:Name="Name"  Text="{Binding Name}"  TextColor="Navy" FontSize="20"/>
    <Label x:Name="Race" Text="{Binding Race}"/>
    <Button Text="Add to Cart" Command="{Binding AddToCartCommand}"/>
  </StackLayout>
</ViewCell>

// listview pages view
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="..."
             xmlns:x="..."
             x:Class="Examples.DemoListViewPage">
  <ContentPage.Content>
    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="Center">
      <ListView ItemsSource="{Binding DogList}" HasUnevenRows="True">
        <ListView.ItemTemplate>
          <DataTemplate>         
            <DogItemView BindingContext = "{Binding}" />
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackLayout>
  </ContentPage.Content>
</ContentPage>

First of all: Does this line mean
<DogItemView BindingContext = "{Binding}" />
that the path of the binding context is set to Path=. which means it set to DogsItemViewModel()?
If so, how is the constructor of DogsItemViewModel called with the listview item from DogList?

Is there any other approach you could recommend to stop pulluting the model with view-bound properties?

Best Answer

Answers

  • LarsNymandLarsNymand DKMember ✭✭

    I'm not sure if my approach is correct (haven't looked into it in details yet for a better approach) but at least it works :-)

    Instead of having a ObservableCollection I have a ObservableCollection

  • stev_estev_e Member ✭✭

    Hi Lars, I think you made a typo :) What exactly do you use and what not? Can you explain a bit more?

  • LarsNymandLarsNymand DKMember ✭✭

    argh think the system removed < and > and in between.

    Instead of
    ObservableCollection<Dogs>
    I use
    ObservableCollection<DogsItemViewModel>

  • stev_estev_e Member ✭✭
    edited November 2018

    Hi Lars, thank you for your suggestion :) But I would prefer to get the ObservableCollection of Dogs way working.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    @LarsNymand said:
    argh think the system removed < and > and in between.

    Instead of
    ObservableCollection<Dogs>
    I use
    ObservableCollection<DogsItemViewModel>

    That's not really how it should go. Your ObservableCollection should be of a model datatype, not a VIewModel. 90% of the time you don't have collections of ViewModels. You can. And I do in like... 2 cases... where the ViewModel is the features the user has permissions to use. But otherwise a ListView would bind to a collection of things... not a collection of ViewModels.

    Since you are using pets as your example, feel free to look at my 2017 tutorial app which also uses pets. I think it will be a smooth translation of concept for you since we're dealing with the same type objects. I find everyone grasps the concept of animals and pets so they are good learning classes to use.
    https://redpillxamarin.com/2016/12/14/red-pill-xamarin-introduction-to-this-series/

    My 2018 series leads up to (so far) an example of binding controls WITHIN a ListView so you can direct commands to the model, UI code behind or the ViewModel - the 3 common things everyone asks about from a ListView. Without having to go that odd route you've described of using ViewModels as the collection type.
    http://redpillxamarin.com/2018/03/12/2018-101-vs2017-new-solution/

  • LarsNymandLarsNymand DKMember ✭✭

    Hi @ClintStLaurent as mentioned I know it's not the right/best solution - but it works and I just haven't got the time find the better solution.. yet.

    I look forward to have a look at your examples!

  • stev_estev_e Member ✭✭

    Hi Clint, I just want to make sure everybody knows that my initial question had a bit different direction and that it is not answered yet (at least I think from what I'm reading). After re-visiting your blog I'm not quite sure if the solution for my problem is also in there (e.g. shall I use a composite control to avoid binding to a model directly?).

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Maybe rephrase the question then, if it wasn't addressed. What question can we help you with?

  • stev_estev_e Member ✭✭

    Hi Clint, thank you for helping!! So:

    At first I would like to get an understanding how the referenced code (it is not mine) is supposed to work (because I did not manage to get it working). Especially the line
    <DogItemView BindingContext = "{Binding}" />
    confuses me. My questions were: Does this line mean that the path of the binding context is set to Path=. which means it set to DogsItemViewModel()? If so, how is the constructor of DogsItemViewModel called with the listview item from DogList?

    The idea behind all that is to seperate the view from the model (that resides in a ObservableCollection and is so directly connected to the view, right?) by putting a view model between them. It does not intend to replace the model by a viewmodel inside the ObservableCollection. In my eyes this seems to be quite good MVVM coding practice that I want to adopt as a newcomer :) The full explaination (that is maybe better understandable than mine) can be found in the referenced link I had to "encode", because I'm not allowed to post links.

    Further, if this approach does not work how could one seperate the model (like a dog) from the view (listview item with direct access to models properties). And here I am not sure if you tried to answer this question by pointing to your blog.

    I hope I was able to give an understandable explaination of my problem :)

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited November 2018

    First of all: Does this line mean
    <DogItemView BindingContext = "{Binding}" />
    that the path of the binding context is set to Path=. which means it set to DogsItemViewModel()?

    No. That is in the DateTemplate. Which is applied to each element of the binded list. Each Dog in the collection of Dogs; which should be a model, not a viewmodel. A dog. Not a DogViewModel.
    So the DogItemView binding context is set to the Dog model. Which makes sense. The view for a given dog uses that dog out of the collection for its binding context. The View to present "Zeus" uses the instance of "Zeus" as its binding context.

    honestly, I'm not sure that's accurate to what you've actually done. Because as you said you've done something odd that works even if you don't understand why. So if your collection is ViewModels instead of models then I'm as confused about what you do have versus what you should have as you might be.

    Further, if this approach does not work how could one seperate the model (like a dog) from the view (listview item with direct access to models properties).

    That's exactly what the 2017 tutorial project I linked you to does. It presents a collection of pets using a pet view. Please don't be offended if I'm not going to spend hours rehashing what has already been done as a step-by-step walk through. That's why I did the walkthroughs with all the nice screenshots and very wordy detailed explanations.

  • stev_estev_e Member ✭✭

    Hi Clint, please excuse my persistence :) And I am absolutely not offended by any serious try to help me. But I still believe that we are not talking about exactly the same. Let me explain again.
    All of the above code is not from me. I copied it from another website for better understanding of my concern. I did try to get it working in a project but couldn't. But I think the original author was able in his project :) There is the upper part of the code that represents "the old school way":
    <ListView ItemsSource="{Binding DogList}" // a listview with an item source that is an observable collection of the model
    He states that:

    • It violates MVVM in exposing the model behind it because the DataTemplate binds to model objects
    • The moment we want to store additional data for each item or execute a command from an ViewCell it get’s difficult

    This beeing said he brings in the viewmodel in the lower part. If my explaination does not make sense to you: Would you mind to read the article? It can be found here (burkharts.net/apps/blog/advanced-listview-bindings/). Can you then tell me if this code still does not make any sense to you? I think this is such an interesting point to discuss :)

  • stev_estev_e Member ✭✭

    Hm ok. Now that I have read the article for the thousandth time, I notice that I have missed something that is not reflected in the code.
    The improved version links to the same DogList as the former version of the code does:
    <ListView ItemsSource="{Binding DogList}" HasUnevenRows="True"> // In the articles code snippet that is still a ObservableCollection<Dog> DogList
    What he says in the text but does not reflect in the code is that
    ... our page’s ViewModel no longer exposes a ObservableColletion<Dogs> but ObservableColletion<DogsItemViewModel>
    Now everything slowly fits together (including your explanation). I thought until now that the dog list remains a observable collection of dog in his code. Damn...

    Thank you!!!

  • stev_estev_e Member ✭✭

    I will have a deeper look to all of that. It seems to be very promising. Thank you very much for all the detailled information. Have a nice day!

  • I ended up here because I found the same article as you @stev_e .
    burkharts dot net slash apps slash blog slash advanced-listview-bindings
    I also found it very promising. Using a Separated ViewModel for a ListView Item makes a lot of things easier. I'm trying this approach here. But I couldn't find anyone else on the web using this pattern. Since the last time you were trying it, what are your conclusions @stev_e ?

Sign In or Register to comment.