Binding Selecteditems of Listview to Viewmodel

Binding SelectedItems of ListView to ViewModel

1. One way to source binding:

You have to use SelectionChanged event. The easiest way is to write eventhandler in codebehind to "bind selecteditems" to viewmodel.

//ViewModel
public ICollectionView BusinessCollection {get; set;}
public List<YourBusinessItem> SelectedObject {get; set;}

//Codebehind
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var viewmodel = (ViewModel) DataContext;
viewmodel.SelectedItems = listview.SelectedItems
.Cast<YourBusinessItem>()
.ToList();
}

This still aligns with MVVM design, because view and viewmodel resposibilities are kept separated. You dont have any logic in codebehind and viewmodel is clean and testable.

2. Two way binding:

if you also need to update view, when viewmodel changes, you have to attach to ViewModel's PropertyChanged event and to the selected items' CollectionChanged event. of course you can do it in codebehind, but in this case I would create something more reusable:

//ViewModel
public ObservableCollection<YourBusinessItem> SelectedObject {get; set;}

//in codebehind:
var binder = new SelectedItemsBinder(listview, viewmodel.SelectedItems);
binder.Bind();

or can create custom attached property, so you can use binding syntax in xaml:

<ListView local:ListViewExtensions.SelectedValues="{Binding SelectedItem}" .../>
public class SelectedItemsBinder
{
private ListView _listView;
private IList _collection;

public SelectedItemsBinder(ListView listView, IList collection)
{
_listView = listView;
_collection = collection;

_listView.SelectedItems.Clear();

foreach (var item in _collection)
{
_listView.SelectedItems.Add(item);
}
}

public void Bind()
{
_listView.SelectionChanged += ListView_SelectionChanged;

if (_collection is INotifyCollectionChanged)
{
var observable = (INotifyCollectionChanged) _collection;
observable.CollectionChanged += Collection_CollectionChanged;
}
}

public void UnBind()
{
if (_listView != null)
_listView.SelectionChanged -= ListView_SelectionChanged;

if (_collection != null && _collection is INotifyCollectionChanged)
{
var observable = (INotifyCollectionChanged) _collection;
observable.CollectionChanged -= Collection_CollectionChanged;
}
}

private void Collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var item in e.NewItems ?? new object[0])
{
if (!_listView.SelectedItems.Contains(item))
_listView.SelectedItems.Add(item);
}
foreach (var item in e.OldItems ?? new object[0])
{
_listView.SelectedItems.Remove(item);
}
}

private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems ?? new object[0])
{
if (!_collection.Contains(item))
_collection.Add(item);
}

foreach (var item in e.RemovedItems ?? new object[0])
{
_collection.Remove(item);
}
}
}

Attached property implementation

public class ListViewExtensions
{

private static SelectedItemsBinder GetSelectedValueBinder(DependencyObject obj)
{
return (SelectedItemsBinder)obj.GetValue(SelectedValueBinderProperty);
}

private static void SetSelectedValueBinder(DependencyObject obj, SelectedItemsBinder items)
{
obj.SetValue(SelectedValueBinderProperty, items);
}

private static readonly DependencyProperty SelectedValueBinderProperty = DependencyProperty.RegisterAttached("SelectedValueBinder", typeof(SelectedItemsBinder), typeof(ListViewExtensions));

public static readonly DependencyProperty SelectedValuesProperty = DependencyProperty.RegisterAttached("SelectedValues", typeof(IList), typeof(ListViewExtensions),
new FrameworkPropertyMetadata(null, OnSelectedValuesChanged));

private static void OnSelectedValuesChanged(DependencyObject o, DependencyPropertyChangedEventArgs value)
{
var oldBinder = GetSelectedValueBinder(o);
if (oldBinder != null)
oldBinder.UnBind();

SetSelectedValueBinder(o, new SelectedItemsBinder((ListView)o, (IList)value.NewValue));
GetSelectedValueBinder(o).Bind();
}

public static void SetSelectedValues(Selector elementName, IEnumerable value)
{
elementName.SetValue(SelectedValuesProperty, value);
}

public static IEnumerable GetSelectedValues(Selector elementName)
{
return (IEnumerable)elementName.GetValue(SelectedValuesProperty);
}
}

UWP ListView bind SelectedItem to property in viewmodel

If you had implemented this with the traditional {Bindings } markup you wouldn't have this issue, since the bindings are evaluated at runtime, while {x:Bind } is evaluated at compile time.

The situation here is that both ItemsSource and SelectedItem are properties of type Object and hence the problem arises when your Target tries to update the Source, due to the fact that you cannot assign a property of type Object to your (ViewModel.SelectedSection) property.
The opposite doesn't throw any error since you your ViewModel.SelectedSection property can be implicitly casted to object.

One of the features of x:Bind, is that you can can cast your properties, like this for example:

{x:Bind (x:Boolean)CheckBox.IsChecked, Mode=TwoWay}"

The problem is that since in your situation we are not dealing with one of the XAML intrinsic data types, you have to map onto your XAML namespace your ViewModel class, by including it on your XML namespace defined at the root of your page.

xmlns:myviewmodel="using:......"

After including it, i think you can successfully cast it onto the desired reference type, without any compilation error, by performing casting like this:

<ListView Name="SectionsListView"
IsItemClickEnabled="True"
ItemsSource="{x:Bind ViewModel.CurrentProject.Sections, Mode=OneWay}"
SelectedItem="{x:Bind (myviewmodel:ViewModel) ViewModel.SelectedSection, Mode=TwoWay}">

Or you can make some tweaks on your code and work with {Binding }, which honestly just simplifies entirely this process!

Getting WPF ListView.SelectedItems in ViewModel

To get the SelectedItems only when a command is executed then use CommandParameter and pass in the ListView.SelectedItems.

<ListBox x:Name="listbox" ItemsSource="{Binding StringList}" SelectionMode="Multiple"/>
<Button Command="{Binding GetListItemsCommand}" CommandParameter="{Binding SelectedItems, ElementName=listbox}" Content="GetSelectedListBoxItems"/>

How to bind multiple selection of listview to viewmodel?

What you can do is you can handle the Button_Click(...) in your code-behind. Then in that code-behind method you can create a List of selected items by iterating over the selected items of the listView.

Since it is allowed to access the ViewModel from the View you can now call a method on your ViewModel and pass the list of selected items as a parameter.

I'm not sure if this would also work with Bindings only, however it is not bad practice to use code-behind as well.

Example Code:

public void Button_Click(object sender, EventArguments arg)
{
List<ListViewItem> mySelectedItems = new List<ListViewItem>();

foreach(ListViewItem item in myListView.SelectedItems)
{
mySelectedItems.Add(item);
}

ViewModel.SomeMethod(mySelectedItems);
}

EDIT

Here is a minimalist example, XAML:

<DataTemplate
x:Key="CarTemplate"
DataType="{x:Type Car}">
</DataTemplate>

<ListView x:Name="myListView"
ItemsSource="{Binding Path=Cars}"
ItemTemplate="{StaticResource CarTemplate}">
</ListView>

CODE-BEHIND:

public void Button_Click(object sender, EventArguments arg)
{
List<Car> mySelectedItems = new List<Car>();

foreach(Car item in myListView.SelectedItems)
{
mySelectedItems.Add(item);
}

ViewModel.SomeMethod(mySelectedItems);
}

WPF ListView SelectedItems DataBinding MVVM

We solved it with an attached property which looks like:

public class MultiSelectorExtension
{
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.RegisterAttached("SelectedItems", typeof(INotifyCollectionChanged), typeof(MultiSelectorExtension),
new PropertyMetadata(default(INotifyCollectionChanged), OnSelectedItemsChanged));

public static void SetSelectedItems(DependencyObject element, INotifyCollectionChanged value)
{
element.SetValue(SelectedItemsProperty, value);
}

public static INotifyCollectionChanged GetSelectedItems(DependencyObject element)
{
return (INotifyCollectionChanged)element.GetValue(SelectedItemsProperty);
}

private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelector multiSelectorControl = d as MultiSelector;

NotifyCollectionChangedEventHandler handler = (sender,
args) =>
{
if (multiSelectorControl != null)
{
IList listSelectedItems = multiSelectorControl.SelectedItems;
if (args.OldItems != null)
{
foreach (var item in args.OldItems)
{
if (listSelectedItems.Contains(item))
{
listSelectedItems.Remove(item);
}
}
}

if (args.NewItems != null)
{
foreach (var item in args.NewItems)
{
if (!listSelectedItems.Contains(item))
{
listSelectedItems.Add(item);
}
}
}
}
};

if (e.OldValue == null && multiSelectorControl != null)
{
multiSelectorControl.SelectionChanged += OnSelectionChanged;
}

if (e.OldValue is INotifyCollectionChanged)
{
(e.OldValue as INotifyCollectionChanged).CollectionChanged -= handler;
}

if (e.NewValue is INotifyCollectionChanged)
{
(e.NewValue as INotifyCollectionChanged).CollectionChanged += handler;
}

}

private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
DependencyObject d = sender as DependencyObject;

if (GetSelectionChangedInProgress(d))
return;

// Internal Flag to avoid infinite loop
SetSelectionChangedInProgress(d, true);

dynamic selectedItems = GetSelectedItems(d);

try
{
foreach (var item in e.RemovedItems.Cast<dynamic>().Where(item => selectedItems.Contains(item)))
{
selectedItems.Remove(item);
}
}
catch (Exception)
{

}

try
{
foreach (var item in e.AddedItems.Cast<dynamic>().Where(item => !selectedItems.Contains(item)))
{
selectedItems.Add(item);
}
}
catch (Exception)
{
}

SetSelectionChangedInProgress(d, false);
}

private static readonly DependencyProperty SelectionChangedInProgressProperty =
DependencyProperty.RegisterAttached("SelectionChangedInProgress", typeof(bool), typeof(MultiSelectorExtension), new PropertyMetadata(default(bool)));

private static void SetSelectionChangedInProgress(DependencyObject element,
bool value)
{
element.SetValue(SelectionChangedInProgressProperty, value);
}

private static bool GetSelectionChangedInProgress(DependencyObject element)
{
return (bool)element.GetValue(SelectionChangedInProgressProperty);
}
}

And our usage at a ListView looks like:

AttachedProperties:MultiSelectorExtension.SelectedItems="{Binding SelectedPersonss, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

How can i get all selected item(s) from ListView in ViewModel class?

To get selected items into the ViewModel, first create a property of bool type in your model that will be bound with IsSelected property of ListViewItem.

Property in Model class:

 public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
RaiseChange("IsSelected");
}
}

XAML Style:

 <ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>

Final Property in ViewModel:

 public List<DataGridItem> SelectedItem
{
get
{
return list.Where(item=>item.IsSelected).ToList();
}
}

Xamarin List View SelectedItem ViewModel binding

While what you have may work there are a few tweaks I would suggest.

No need to set your BindingContext in your constructor and in OnAppearing(). Just make your LoginViewModel a class level private property in your code-behind and only assign it to your BindingContext in your constructor. Then call GetEmployees() in OnAppearing().

Also, you should make GetEmployees() return a Task, in order to await as far up the chain as possible.

ViewModel:

....

public async Task GetEmployees()
{
var loginService = new LoginService();
EmployeeList = await loginService.GetEmployeesAsync();
}

....

Code-behind:

public partial class LoginPage : ContentPage
{
private LoginViewModel _model;

public LoginPage()
{
InitializeComponent();
BindingContext = _model = new LoginViewModel(this);
}

protected override async void OnAppearing() //Notice I changed this to async and can now await the GetEmployees() call
{
base.OnAppearing();
await _model.GetEmployees();
}

public ListView MyList
{
get
{
return mylist;
}
}

private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e) {
if (e.SelectedItem == null) return;

await Navigation.PushAsync(new MenuPage());
}
}

XAML:

<!-- Adding OnItemSelected in XAML below -->
<ListView x:Name="mylist"
ItemsSource="{Binding EmployeeList}"
HasUnevenRows="True"
SelectedItem="{Binding LoginName}"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding Name}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

SelectedItems ListView binding fails in UWP

The SelectedItems property of a ListView is read-only as documented here, hence prohibiting two way binding in UWP.

However, you could bind each item IsSelected property to the viewmodel and get the collection of selected items by filtering only items with IsSelected to true.



Related Topics



Leave a reply



Submit