简体   繁体   中英

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?

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();
    }
};

ListBox.SelectedItems is read-only. Did you mean to bind to ListBox.SelectedItem instead?

I am not sure if I understand your question correctly or the exact scenario - but assuming you wanted to have one listbox "d" show the items that were selected in another listbox "MyOtherListbox" then you just need to set the binding mode to 'one way' else it will bring up an error.

You could do something like

d.SetBinding(ListBox.ItemsSourceProperty, new Binding { Source = MyOtherListbox.SelectedItems, Mode = BindingMode.OneWay});

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!

my trick: in xaml, use MultiBinding , force execute converter in Count property change (it work!).

<MultiBinding Converter="{StaticResource SelectedRowsTotal }">
    <Binding Path="SelectedItems"  ElementName="listBox1" />
    <Binding Path="SelectedItems.Count"  ElementName="listBox1" />
</MultiBinding>

Converter:

public class SelectedRowsTotal : IMultiValueConverter 
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    {
        var selecteds = values as IEnumerable;

        if (selected == null) 
            return null;

        return selecteds.Cast<SomeType>().Sum(x=> x.SomeProperty) = total;
    }

    object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    {
        return null;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM