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
Error: Must Create Dependencysource on Same Thread as the Dependencyobject Even by Using Dispatcher
How to Prevent a Single Object Property from Being Converted to a Datetime When It Is a String
Regex Word Boundary Expressions
How to Get the Subscribers of an Event
How to Retain Callsite Information When Wrapping Nlog
Using Linq Except Not Working as I Thought
C# Date Formatting Is Losing Slash Separators
How to Figure Out Which Key of Modelstate Has Error
Unity3D Ui, Calculation for Position Dragging an Item
Adding Custom Properties for Each Request in Application Insights Metrics
Getting Date or Time Only from a Datetime Object
Prevent Textbox Autofill with Previously Entered Values
How to Change Listview Selected Row Backcolor Even When Focus on Another Control
With Rx, How to Ignore All-Except-The-Latest Value When My Subscribe Method Is Running
Does Filestreamresult Close Stream
Is CSV with Multi Tabs/Sheet Possible