Binding a ViewModel property to a SwipeView control inside a CollectionView

85 Views Asked by At

I am trying to implement a ContentView which represents a list of items, and allows for manipulating these items (delete, edit...) by clicking on a button. Now I'm trying to have it look nice and fancy so I'm using a SwipeView for the item manipulation button to show only when the "show more" of the item is clicked.

I am close to achieving what I want, but the last thing I could not quite figure out is how to bind each of the items of my list to each SwipeView control instance. Here is a trimmed version of my ContentView XAML:

<?xml version="1.0" encoding="utf-8" ?>
<CollectionView>
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="local:TimerPresetListItem">
            <Grid>
                <SwipeView BindingContext="{Binding SwipeView}"/>
                <Button Text="Show more..." Command="{Binding ShowMoreCommand}"/>
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

And here is the code-behind:

public partial class TimerPresetList : ContentView
{
    public static readonly BindableProperty TimerPresetListItemsProperty = BindableProperty.Create(nameof(TimerPresetListItems), typeof(ObservableCollection<TimerPresetListItem>), typeof(TimerPresetList));
    public static readonly BindableProperty TimerPresetsProperty = BindableProperty.Create(nameof(TimerPresets), typeof(ObservableCollection<TimerPresetViewModel>), typeof(TimerPresetList));
    public ObservableCollection<TimerPresetListItem> TimerPresetListItems
    {
        get => (ObservableCollection<TimerPresetListItem>)GetValue(TimerPresetListItemsProperty);
        set => SetValue(TimerPresetListItemsProperty, value);
    }

    public ObservableCollection<TimerPresetViewModel> TimerPresets
    {
        get => (ObservableCollection<TimerPresetViewModel>)GetValue(TimerPresetsProperty);
        set => SetValue(TimerPresetsProperty, value);
    }

    public TimerPresetList()
    {
        InitializeComponent();
    }

    protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);

        if (propertyName == nameof(TimerPresets))
        {
            TimerPresetListItems = new ObservableCollection<TimerPresetListItem>();
            TimerPresets.CollectionChanged += TimerPresetsCollectionChanged;
        }
    }

    private void TimerPresetsCollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                foreach (var item in e.NewItems)
                {
                    TimerPresetListItems.Add(new TimerPresetListItem((TimerPresetViewModel)item));
                }
                break;
        }
    }
}

public partial class TimerPresetListItem : ObservableObject
{
    [ObservableProperty]
    private TimerPresetViewModel _timerPresetViewModel;

    [ObservableProperty]
    private SwipeView _swipeView;

    public TimerPresetListItem(TimerPresetViewModel timerPresetViewModel)
    {
        TimerPresetViewModel = timerPresetViewModel;
    }

    [RelayCommand]
    public void ShowMore()
    {
        if (SwipeView != null)
        {
            SwipeView!.Open(OpenSwipeItem.RightItems);
        }
    }
}

    public partial class TimerPresetList : ContentView
{
    public static readonly BindableProperty TimerPresetListItemsProperty = BindableProperty.Create(nameof(TimerPresetListItems), typeof(ObservableCollection<TimerPresetListItem>), typeof(TimerPresetList));
    public static readonly BindableProperty TimerPresetsProperty = BindableProperty.Create(nameof(TimerPresets), typeof(ObservableCollection<TimerPresetViewModel>), typeof(TimerPresetList));
    public ObservableCollection<TimerPresetListItem> TimerPresetListItems
    {
        get => (ObservableCollection<TimerPresetListItem>)GetValue(TimerPresetListItemsProperty);
        set => SetValue(TimerPresetListItemsProperty, value);
    }

    public ObservableCollection<TimerPresetViewModel> TimerPresets
    {
        get => (ObservableCollection<TimerPresetViewModel>)GetValue(TimerPresetsProperty);
        set => SetValue(TimerPresetsProperty, value);
    }

    public TimerPresetList()
    {
        InitializeComponent();
    }

    protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);

        if (propertyName == nameof(TimerPresets))
        {
            TimerPresetListItems = new ObservableCollection<TimerPresetListItem>();
            TimerPresets.CollectionChanged += TimerPresetsCollectionChanged;
        }
    }

    private void TimerPresetsCollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                foreach (var item in e.NewItems)
                {
                    TimerPresetListItems.Add(new TimerPresetListItem((TimerPresetViewModel)item));
                }
                break;
        }
    }
}

public partial class TimerPresetListItem : ObservableObject
{
    [ObservableProperty]
    private TimerPresetViewModel _timerPresetViewModel;

    [ObservableProperty]
    private SwipeView _swipeView;

    public TimerPresetListItem(TimerPresetViewModel timerPresetViewModel)
    {
        TimerPresetViewModel = timerPresetViewModel;
    }

    [RelayCommand]
    public void ShowMore()
    {
        if (SwipeView != null)
        {
            SwipeView!.Open(OpenSwipeItem.RightItems);
        }
    }
}

As you can see I simply have a TimerPresetListItem which wraps a TimerPresetViewModel object and (is supposed to) bind to a SwipeView instance.

Now everything works fine except the binding to the SwipeView:

<SwipeView BindingContext="{Binding SwipeView}"/>

Indeed when the ShowMore method gets call, SwipeView is still always null. Any idea what went wrong here ? Thanks a lot for any feedback it's much appreciated.

1

There are 1 best solutions below

0
Guangyu Bai - MSFT On BEST ANSWER

First, you need to create a custom SwipeView which inherits SwipeView and you can define a BindableProperty OpenFlag which controls the state of swipeView.

public class CustomSwipeView : SwipeView
{
    public static readonly BindableProperty OpenFlagProperty =
BindableProperty.Create("OpenFlag", typeof(bool), typeof(CustomSwipeView), null, propertyChanged:OnPropertyChanged);

    private static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var flag = (bool)newValue;
        SwipeView m = bindable as SwipeView;
        
        if(m != null)
        {
            if (flag)
            {
                m.Open(OpenSwipeItem.RightItems);
            }
            else
            {
                m.Close();
            }
        }
    }

    public bool OpenFlag
    {
        get { return (bool)GetValue(OpenFlagProperty); }
        set { SetValue(OpenFlagProperty, value); }
    }
}

Then, you can put the custom Swipeview in the xaml.

 <local:CustomSwipeView x:Name="myview" OpenFlag="{Binding OpenFlag}" >
                <SwipeView.RightItems>
                ...
                </SwipeView.RightItems>

In the viewmodel you can create a data collection to control the OpenFlag.

public ObservableCollection<Item> ItemCollection { get; set; } = new ObservableCollection<Item>();

Here is the code in the item class:

public class Item : INotifyPropertyChanged
{

    private bool openFlag;
    public bool OpenFlag
    {
        get
        {
            return openFlag;

        }

        set
        {
            openFlag = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenFlag)));
        }

    }
    public string MyTitle { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
}

Last, you can use the code below to control the Swipeview.

viewModel.ItemCollection[0].OpenFlag = true;