

Multi-Select ListView
source link: https://dotnetdevaddict.co.za/2017/06/29/multi-select-listview/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

I was just hanging around in the IDE this week and I had a chat with ListView
. He was a very nice guy, but he was complaining about the fact that for some reason he could only ever select one item at a time…
I think of myself as a somewhat decent coder, and I decided to solve this problem – at least some part of it.
The easiest, and quickest, way to add multi-select capabilities to a ListView
requires two steps: the “selectable” item and the “select” action.
The Selectable Item
I started with the model:
public
class
SelectableItem
{
public
object
Data {
get
;
set
; }
public
bool
IsSelected {
get
;
set
; }
}
But, since we are doing data binding, I created a bindable object that has several bindable properties:
public
class
SelectableItem : BindableObject
{
public
static
readonly
BindableProperty DataProperty =
BindableProperty.Create(
nameof(Data),
typeof
(
object
),
typeof
(SelectableItem),
(
object
)
null
);
public
static
readonly
BindableProperty IsSelectedProperty =
BindableProperty.Create(
nameof(IsSelected),
typeof
(
bool
),
typeof
(SelectableItem),
false
);
public
SelectableItem(
object
data)
{
Data = data;
IsSelected =
false
;
}
public
SelectableItem(
object
data,
bool
isSelected)
{
Data = data;
IsSelected = isSelected;
}
public
object
Data
{
get
{
return
(
object
)GetValue(DataProperty); }
set
{ SetValue(DataProperty, value); }
}
public
bool
IsSelected
{
get
{
return
(
bool
)GetValue(IsSelectedProperty); }
set
{ SetValue(IsSelectedProperty, value); }
}
}
You may have noticed that I didn’t create a generic type. This is because later on we are going to want to get the value of the IsSelected
property from the ItemSelected
event handler, and we won’t know what the type is. However, we can create a generic type that inherits from this base type:
public
class
SelectableItem<T> : SelectableItem
{
public
SelectableItem(T data)
:
base
(data)
{
}
public
SelectableItem(T data,
bool
isSelected)
:
base
(data, isSelected)
{
}
// this is safe as we are just returning the base value
public
new
T Data
{
get
{
return
(T)
base
.Data; }
set
{
base
.Data = value; }
}
}
We now have a nice generic type for the developer (us) and a non-generic type for the event (the machine).
The Selectable ListView
The next step is to hook up the ListView
with the selectable items. So, we will need a data field on the view model or the Page
– depending on how you work (note the generic SelectableItem
):
public
partial
class
MainPage : ContentPage
{
public
MainPage()
{
InitializeComponent();
Items =
new
ObservableCollection<SelectableItem<
string
>>();
BindingContext =
this
;
}
public
ObservableCollection<SelectableItem<
string
>> Items {
get
; }
}
And, we need a list item that has a “checked” state (here it is just a red block):
<
ListView
ItemsSource
=
"{Binding Items}"
>
<
ListView.ItemTemplate
>
<
DataTemplate
>
<
ViewCell
>
<
StackLayout
Orientation
=
"Horizontal"
>
<!-- the "check" mark -->
<
BoxView
IsVisible
=
"{Binding IsSelected}"
WidthRequest
=
"12"
HeightRequest
=
"12"
Color
=
"Red"
/>
<!-- the text/data -->
<
Label
Text
=
"{Binding Data}"
/>
</
StackLayout
>
</
ViewCell
>
</
DataTemplate
>
</
ListView.ItemTemplate
>
</
ListView
>
We should now have a list of items that can show either a selected or unselected state. To handle when an item is selected by the user, we just need an event on the list:
<
ListView
...
ItemSelected
=
"OnItemSelected"
>
...
</
ListView
>
And then we write the code to toggle the selection (note the non-generic SelectableItem
):
private
void
OnItemSelected(
object
sender, SelectedItemChangedEventArgs e)
{
var
item = e.SelectedItem
as
SelectableItem;
if
(item !=
null
)
{
// toggle the selection property
item.IsSelected = !item.IsSelected;
}
// deselect the item
((ListView)sender).SelectedItem =
null
;
}
This is all we need, and we will have a ListView
that that can support multiple items being selected.
Improvements
We could call it a day, but we are not going to. The day has just started, and we are thinking that we might want to use this in multiple places. We can just copy the logic – mainly the event and cell view, but there are better ways (and we don’t like duplicate code).
Attached Property
The first thing that we will look at is if we can magically do the event hookup with a single property instead of the event:
<
ListView
...
local:MultiSelectListView.IsMultiSelect
=
"True"
>
...
</
ListView
>
That looks nicer than the event, and we have a couple of benefits: no code in the page, the event code is written once and the property is bindable.
What we are doing here is using an attached property to “inject” a bindable property into an existing view type:
public
static
class
MultiSelectListView
{
public
static
readonly
BindableProperty IsMultiSelectProperty =
BindableProperty.CreateAttached(
"IsMultiSelect"
,
typeof
(
bool
),
typeof
(ListView),
false
,
propertyChanged: OnIsMultiSelectChanged);
public
static
bool
GetIsMultiSelect(BindableObject view)
=> (
bool
)view.GetValue(IsMultiSelectProperty);
public
static
void
SetIsMultiSelect(BindableObject view,
bool
value)
=> view.SetValue(IsMultiSelectProperty, value);
private
static
void
OnIsMultiSelectChanged(
BindableObject bindable,
object
oldValue,
object
newValue)
{
var
listView = bindable
as
ListView;
if
(listView !=
null
)
{
// always remove event
listView.ItemSelected -= OnItemSelected;
// add the event if true
if
(
true
.Equals(newValue))
{
listView.ItemSelected += OnItemSelected;
}
}
}
private
static
void
OnItemSelected(
object
sender, SelectedItemChangedEventArgs e)
{
var
item = e.SelectedItem
as
SelectableItem;
if
(item !=
null
)
{
// toggle the selection property
item.IsSelected = !item.IsSelected;
}
// deselect the item
((ListView)sender).SelectedItem =
null
;
}
}
We now have a super easy way to turn any list into a multi-select list with a single property.
The ItemsSource
Another area we can improve is the ItemsSource
collection. Right now we have quite a long type with two generic types:
public
ObservableCollection<SelectableItem<
string
>> Items {
get
; }
We can not only make this shorter, but more convenient by creating a new type that derives from ObservableCollection
. And, now that we have a new type, we can add additional methods so that we don’t even have to deal with the SelectableItem
type at all:
public
class
SelectableObservableCollection<T> : ObservableCollection<SelectableItem<T>>
{
...
// a constructor overload
public
SelectableObservableCollection(IEnumerable<T> collection)
:
base
(collection.Select(c =>
new
SelectableItem<T>(c)))
{
}
...
// a convenience property
public
IEnumerable<T> SelectedItems
=>
this
.Where(i => i.IsSelected).Select(i => i.Data);
...
// a method overload
public
void
Add(T item)
{
Add(
new
SelectableItem<T>(item));
}
}
When we use this new type, we can do some cool things:
// a nice type
public
SelectableObservableCollection<
string
> Items {
get
; }
// cool methods and properties
private
void
OnDoSomething()
{
Debug.WriteLine(
"You have selected:"
);
// use a property
foreach
(
var
selected
in
Items.SelectedItems)
{
Debug.WriteLine($
" - {selected.Data}"
);
}
// use a method
Items.Add(
"A New String"
);
}
The Cell
Finally, as you may have a really cool check mark for selected items (unlike our red block now), we will want to be able to create a custom cell type that will allow us to just specify the item contents, and automatically handle the check mark:
<
ListView
ItemsSource
=
"{Binding Items}"
local:MultiSelectListView.IsMultiSelect
=
"True"
>
<
ListView.ItemTemplate
>
<
DataTemplate
>
<
local:SelectableViewCell
>
<
Label
Text
=
"{Binding}"
/>
</
local:SelectableViewCell
>
</
DataTemplate
>
</
ListView.ItemTemplate
>
</
ListView
>
This requires a new Cell
type:
[ContentProperty(nameof(DataView))]
public
class
SelectableViewCell : ViewCell
{
public
SelectableViewCell();
public
View CheckView {
get
;
set
; }
public
View DataView {
get
;
set
; }
}
I have left the implementation of this cell to your imagination… just kidding, I have it all in my my repository. You can find all this code, a sample app and more there too:
I hope you enjoyed this short(ish) post and are able to make your list views handle the selection of multiple items.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK