开发者

ListBox SelectedItems Binding

开发者 https://www.devze.com 2022-12-28 09:17 出处:网络
I want to bind List开发者_如何学Cbox selectedItems to array. But .NET throws exception at runtime.

I want to bind List开发者_如何学Cbox 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();
    }
};


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!


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


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;
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号