简体   繁体   中英

Databinding problem in Xamarin.Forms CollectionView

I'm having trouble finding a good way of changing a property of an object inside a list using a CollectionView in Xamarin.Forms.

Below is my code (only the relevant code for readability).

I have a list which I'm databinding to a CollectionView. Each entry in the collectionview contains a label with a number and two buttons to increase and decrease that number by 1.

Note that the code below is working fine. However, I'm not satisfied with the INotifyPropertyChanged implementation in my model, which should just be a simple DTO. I'd like to remove this interface from my model along with the OnPropertyChanged. When I do that, the label with the number doesn't change anymore when I click a button.

So I should make these changes in the ViewModel, but I haven't been able to figure out how. What would be an appropriate way of implementing this in the viewmodel so I can keep my model clean with only a simple property?

Note that the BaseViewModel already implements the INotifyPropertyChanged interface.

在此处输入图像描述

Xaml:

<CollectionView ItemsSource="{Binding MyList}" SelectionMode="Single">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <StackLayout Orientation="Horizontal">
                <Button Text="-"
                        Command="{Binding Source={x:Reference MyPage}, Path=BindingContext.QuantityMinusCommand}"
                        CommandParameter="{Binding .}" />
                <Label Text="{Binding Quantity, Mode=TwoWay}" />
                <Button Text="+"
                        Command="{Binding Source={x:Reference MyPage}, Path=BindingContext.QuantityPlusCommand}"
                        CommandParameter="{Binding .}" />
            </StackLayout>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Viewmodel:

public class CollectionViewModel : BaseViewModel
{
    private List<MyObject> _myList = new List<MyObject>();

    public ICommand QuantityMinusCommand { get; }
    public ICommand QuantityPlusCommand { get; }

    public CollectionViewModel()
    {
        QuantityMinusCommand = new Command(OnQuantityMinusCommand);
        QuantityPlusCommand = new Command(OnQuantityPlusCommand);
    }

    public List<MyObject> MyList
    {
        get => _myList;
        set
        {
            _myList = value;
            OnPropertyChanged("MyList");
        }
    }

    private void OnQuantityMinusCommand(object o)
    {
        var myObject = (MyObject)o;
        myObject.Quantity = --myObject.Quantity;
    }


    private void OnQuantityPlusCommand(object o)
    {
        var myObject = (MyObject)o;
        myObject.Quantity = ++myObject.Quantity;
    }
}

Model:

public class MyObject : System.ComponentModel.INotifyPropertyChanged
{
    private int _quantity;
    public int Quantity
    {
        get => _quantity;
        set
        {
            _quantity = value;
            OnPropertyChanged("Quantity");
        }
    }


    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
    {
        var changed = PropertyChanged;
        if (changed == null)
            return;

        changed.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}

Your BaseViewModel can inherit INotifyPropertyChanged. And then your model can be a simple DTO.

public class BaseViewModel : ObservableObject
{
    public BaseViewModel()
    {

    }

    bool isBusy = false;
    public bool IsBusy
    {
        get { return isBusy; }
        set { SetProperty(ref isBusy, value); }
    }
}

And the ObservableObject

/// <summary>
/// Observable object with INotifyPropertyChanged implemented
/// </summary>
public class ObservableObject : INotifyPropertyChanged
{
    protected bool SetProperty<T>(ref T backingStore, T value,
        [CallerMemberName]string propertyName = "",
        Action onChanged = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingStore, value))
            return false;

        backingStore = value;
        onChanged?.Invoke();
        OnPropertyChanged(propertyName);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        var changed = PropertyChanged;
        if (changed == null)
            return;

        changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

And the model can be like this:

public class MyObject
{
    public int Quantity { get; set; }
}

The viewmodel:

public class CollectionViewModel : BaseViewModel
{
    // ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM