Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListBox SelectedItems Binding

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?

like image 346
Polaris Avatar asked Apr 29 '10 10:04

Polaris


3 Answers

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!

like image 92
Paul Efford Avatar answered Oct 19 '22 10:10

Paul Efford


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);
    }
}
like image 9
Kep Amun Avatar answered Nov 13 '22 15:11

Kep Amun


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();
    }
};
like image 5
Akash Kava Avatar answered Nov 13 '22 15:11

Akash Kava