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