Search code examples
c#xamlbindingmauicollectionview

Binding a ViewModel property to a SwipeView control inside a CollectionView


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.


Solution

  • 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;