可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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);
}
}
回答2:
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();
}
};
回答3:
ListBox.SelectedItems
is read-only. Did you mean to bind to ListBox.SelectedItem
instead?
回答4:
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});
回答5:
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;
}
}
回答6:
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!