LukeImYourFather
LukeImYourFather

Reputation: 45

ListView ItemClick with MVVM

I'm new with MVVM and there is something I don't understand. The code I have so far :

In ItemListPage.xaml :

<ListView ItemsSource="{Binding itemList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
          SelectedItem="{Binding itemSelected, Mode=TwoWay}"
          ItemClick="{x:Bind ViewModel.ClickItemList}">

In ItemListViewModel.cs :

    private List<Item> _itemList;
    public List<Item> itemList { get { return _itemList; } set { Set(ref _itemList, value); } }
    private Item _itemSelected;
    public Item itemSelected { get { return _itemSelected; } set { Set(ref _itemSelected, value); } }

    public void ClickItemList()
    {
        System.Diagnostics.Debug.WriteLine("Click on itemlist");
        if (itemSelected != null)
        {
            ((App)App.Current)._itemID = itemSelected._ID;
            NavigationService.Navigate(typeof(Views.ShowItem));
        }
    }

The problem I have is on my first click on an item, the function ClickItemList is called but the attribute itemSelected is still empty, so I need to click a 2nd time on an item to actually do the action I want to do (save this item's ID and navigate to the next page). How can I change my code to do that ?

Aside from this problem, I don't understand why I should use 2 attributes (for example the private attribute _itemSelected and the public "fake" attribute itemSelected), why can't I use only one ?

Upvotes: 2

Views: 5463

Answers (4)

LukeImYourFather
LukeImYourFather

Reputation: 45

Thank for all your answers, that helped me to better understand the problem and the MVVM pattern.

The simple solution I found here is to change the trigger event ItemClick to SelectionChanged in my .xaml, because the ItemClick event is raised before the ListView update the SelectedItem, but the SelectionChanged event is raised right after.

Now it's working as intended : one click on an item of my listbox => update the SelectedItem binded property => SelectionChanged event is fired => my function ClickItemList is called and go to the next page after saving the selected item's ID

Upvotes: 1

Noctis
Noctis

Reputation: 11783

Both other answers are correct, but specifically, the reason the first time you click nothing happens is because there was no change selection.
To go around this, when you open the page (in the constructor for example), you can set the SelectedItem to be the first item of the itemList, and then your first should work.

Upvotes: 0

Decade Moon
Decade Moon

Reputation: 34306

The problem I have is on my first click on an item, the function ClickItemList is called but the attribute itemSelected is still empty, so I need to click a 2nd time on an item to actually do the action I want to do (save this item's ID and navigate to the next page). How can I change my code to do that ?

This is because the ListView will update the SelectedItem after it has raised the ItemClick event. If you want to know what item was clicked, you will need access to the event arguments:

public void ClickItemList(object sender, ItemClickEventArgs e)
{
    var clickedItem = (Item)e.ClickedItem;
}

Aside from this problem, I don't understand why I should use 2 attributes (for example the private attribute _itemSelected and the public "fake" attribute itemSelected), why can't I use only one ?

Just to clarify some terminology:

  • _itemList is a private field.
  • itemList is a public property*.

A property is nothing more than a getter and setter function. A field actually stores the object in an instance of your class. For this reason, public properties will usually have a private backing field which it delegates access to.

itemList must be a public property in order to bind to it (via {Binding}). If you want to be able to change itemList to an entirely new list at runtime and have the ListView update its items automatically, then your view model must implement INotifyPropertyChanged and raise the PropertyChanged event when itemList is changed. This is what is happening in the setter of your property (thanks to the helper Set method you have). In this case, you will need to have a private field to store the actual list, and use the public property to make sure that PropertyChanged is raised when itemList is changed by you.

It doesn't make sense to bind ItemsSource two-way (as you have) since the ListView never changes ItemsSource. In fact, if you never change itemList at all, then you can change your view model like this**:

// Generates a hidden private field automatically, and is used as if it were a public field
public List<Item> itemList { get; set; }

and XAML like this:

<ListView ItemsSource="{x:Bind ViewModel.itemList}" ItemClick="{x:Bind ViewModel.ClickItemList}">

* I recommend sticking to Microsoft's convention by naming public properties starting with a capital letter, like ItemList instead of itemList.

** x:Bind supports binding to public fields too, not just properties, but I recommend always using properties in the public interface of your classes, but it's up to you.

Upvotes: 2

J. Pichardo
J. Pichardo

Reputation: 3115

sometimes private properties are not necessary, most of their uses are:

  • When you need to do some processing before "returning" or assigning the value.

  • When you need or want to cache or lazy load the property

  • When properties are not for public access (redundant)

So to answer your question, there is no need for a private field if your public property is just encapsulating it. As an example.

private string _property;
public string Property {
    get { return _property; }
    set { _property = value; }
}

Could be replaced by:

public string Property { get; set; }

Upvotes: 0

Related Questions