I want to bind Listbox selectedItems to array. But .NET throws exception at runtime.
d.SetBinding(ListBox.SelectedItemsProperty, new Binding { Source = SomeArray });
Where d
is some ListBox from XAML.
Exception:
Selected Item cannot be bound.
Why?
Here a working solution, you could easily adapt to your needs:
In xaml
:
<Window x:Class="ListBoxSelectedItems.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="200">
<StackPanel>
<ListBox
ItemsSource="{Binding ProductListSource, NotifyOnSourceUpdated=True}"
SelectedItem="{Binding SelectedProduct, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Multiple" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Item}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Label Content="{Binding Text}"/>
</StackPanel>
In code:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
namespace ListBoxSelectedItems
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : MyNotifyPropertyChanged
{
public ViewModel()
{
ProductListSource.Add(new Product() { Item = "Item_1", IsSelected = false });
ProductListSource.Add(new Product() { Item = "Item_2", IsSelected = false });
ProductListSource.Add(new Product() { Item = "Item_3", IsSelected = false });
}
private ObservableCollection<Product> _productList = new ObservableCollection<Product>();
public ObservableCollection<Product> ProductListSource
{
get => _productList;
set
{
_productList = value;
RaisePropertyChanged(nameof(ProductListSource));
}
}
private readonly Product _selectedProduct;
public Product SelectedProduct
{
get => _selectedProduct;
set
{
var selectedItems = ProductListSource.Where(x => x.IsSelected).ToList();
this.RaisePropertyChanged(nameof(SelectedProduct));
string s = "{";
int n = selectedItems.Count;
for (int i=0; i< n; i++)
{
s += selectedItems[i].ToString();
if (i < n - 1) s += ", ";
}
s += "}";
Debug.WriteLine(s + ".Count= " + n);
}
}
}
public class Product : MyNotifyPropertyChanged
{
private string _item;
public string Item
{
get => _item;
set
{
_item = value;
RaisePropertyChanged(nameof(Item));
}
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
_isSelected = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public new string ToString() => _item;
}
public class MyNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Hope it helps!
You can subscribe to the SelectionChanged event of the ListBox, and in the handler sync a collection of selected items.
In this example the Windows DataContext was set to itself (this) in its constructor. You could also easily call into a logic layer (ViewModel if you're using MVVM) from the event handler.
In Xaml:
<StackPanel>
<ListBox
ItemsSource="{Binding ListBoxItems}"
SelectionMode="Multiple"
SelectionChanged="ListBox_SelectionChanged">
</ListBox>
<ItemsControl
ItemsSource="{Binding SelectedItems}">
</ItemsControl>
</StackPanel>
And in the code-behind:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (string item in e.RemovedItems)
{
SelectedItems.Remove(item);
}
foreach (string item in e.AddedItems)
{
SelectedItems.Add(item);
}
}
This is the working solution, however when selection changes SelectedItemsProperty does not refresh bindings...
you can create a custom control as follow
public class MyListBox: ListBox{
public MyListBox()
{
this.SelectionChanged += (s,e)=>{ RefreshBindings(); };
}
private void RefreshBindings()
{
BindingExpression be =
(BindingExpression) GetBindingExpression(
SelectedItemsProperty);
if(be!=null){
bd.UpdateTarget();
}
}
}
or in your app you can define event in every listbox as shown below ..
myListBox.SelectionChanged += (s,e) => {
BindingExpression be =
(BindingExpression) myListBox.GetBindingExpression(
ListBox.SelectedItemsProperty);
if(be!=null){
bd.UpdateTarget();
}
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With