筛选列表框中的组合框

本文关键字:组合 筛选 列表 | 更新日期: 2023-09-27 18:26:50

我正试图创建一个UserControl来显示ListBox(稍后我想将其绑定到视图模型中的集合),以便每个项目都显示在组合框中(这允许打开其下拉菜单并选择不同的值)。我还想确保不能两次选择任何值,并且我想添加一个按钮来创建其他列表项。

我的想法是让ListBox的每个DataTemplate在其资源中包含一个CollectionViewSource,对其进行筛选,然后将组合框绑定到筛选的值。我的问题是,我不明白如何在这种情况下使绑定工作——只要我的绑定是单向的,一切都很好,但当我使ComboBoxes双向绑定时,我会收到一个异常,告诉我需要为双向绑定设置一个Path才能工作。

我的XAML(为了清晰起见略有删减):

<UserControl x:yadayada x:Name="MultiSelectList">
<Grid>
    <ListBox ItemsSource="{Binding ElementName=MultiSelectList, Path=ChosenItems, Mode=TwoWay}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <ComboBox Loaded="FrameworkElement_OnLoaded" DropDownOpened="ComboBox_OnDropDownOpened">
                    <ComboBox.Resources>
                        <CollectionViewSource Source="{Binding ElementName=MultiSelectList, Path=AllItems}" x:Key="Src" />
                    </ComboBox.Resources> 
                    <ComboBox.ItemsSource>
                        <Binding Source="{StaticResource Src}" />
                    </ComboBox.ItemsSource>
                </ComboBox>
                <!-- uncommenting the following line crashes the program -->
                <!-- <ComboBox.SelectedValue><Binding></Binding></ComboBox.SelectedValue> -->
            </DataTemplate> 
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Content="New" Click="NewButton_Pressed"/>
</Grid>

背后的代码:

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WPFCentralOffice.UserControls
{
    public class Element : IEquatable<Element>
    {
        public int ID { get; set; }
        public string Caption { get; set; }
        public bool Equals(Element other)
        {
            return ID == other.ID;
        }
        public override string ToString()
        {
            return ID + " " + Caption;
        }
    }
    public partial class MultiSelectListUserControl : UserControl
    {
        public ObservableCollection<Element> ChosenItems
        {
            get { return (ObservableCollection<Element>)GetValue(ChosenItemsProperty); }
            set { SetValue(ChosenItemsProperty, value); }
        }
        public ObservableCollection<Element> AllItems
        {
            get { return (ObservableCollection<Element>)GetValue(AllItemsProperty); }
            set { SetValue(AllItemsProperty, value); }
        }
        public static DependencyProperty AllItemsProperty = DependencyProperty.Register(
            "AllItems",
            typeof(ObservableCollection<Element>),
            typeof(MultiSelectListUserControl));
        public static DependencyProperty ChosenItemsProperty = DependencyProperty.Register(
            "ChosenItems",
            typeof(ObservableCollection<Element>),
            typeof(MultiSelectListUserControl));
        public void NewButton_Pressed(object sender, RoutedEventArgs e)
        {
            if (!AllItems.Any() || AllItems.Count == ChosenItems.Count)
            {
                return;
            }
            var elem = AllItems.First(x => ChosenItems.All(y => x.ID != y.ID));
            ChosenItems.Add(elem);
        }
        public MultiSelectListUserControl()
        {
            InitializeComponent();
            SetValue(AllItemsProperty, new ObservableCollection<Element>());
            SetValue(ChosenItemsProperty, new ObservableCollection<Element>());
        }
        private void ComboBox_OnDropDownOpened(object sender, EventArgs e)
        {
            var c = (ComboBox)sender;
            c.Items.Filter = x => ChosenItems.All(y => ((Element)x).ID != y.ID) || ((Element)c.SelectedValue).ID == ((Element)x).ID;
        }
    }
}

这不仅不起作用,而且感觉是实现我想要的东西的一种非常复杂的方式。有没有一种更简单的方法可以列出具有不同值的组合框列表?或者有人能给我指针,这样我就可以正确地实现它吗?

筛选列表框中的组合框

根据@Ed Plunkett的建议,我终于可以使用ComboBox绑定到的代理对象了。这使我可以在触发相关操作(如添加新的列表项或在组合框中选择不同的值)时刷新每个ComboBox的可用项,只需替换每个ComboBoxBindingSource上的AvailableItems属性值。

这也允许XAML更加干净,因为每个ComboBox现在都有一个合适的绑定:

<UserControl x:foobar x:Name="MultiSelectList">
    <Grid>
        <ListBox ItemsSource="{Binding ElementName=MultiSelectList, Path=ComboBoxBindingSources, Mode=TwoWay}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=AvailableElements}" SelectedValue="{Binding Path=SelectedElement}" SelectionChanged="ComboBox_SelectionChanged" />
                </DataTemplate> 
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Content="New" Click="NewButton_Pressed"/>
    </Grid>
</UserControl>

背后的工作代码是:

  using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WPFCentralOffice.UserControls
{
    public class Element : IEquatable<Element>
    {
        public int ID { get; set; }
        public string Caption { get; set; }
        public bool Equals(Element other)
        {
            return ID == other.ID;
        }
        public override string ToString()
        {
            return Caption;
        }
    }
    public class ComboBoxBindingSource : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ComboBoxBindingSource(Element selectedElement, IEnumerable<Element> availableElements)
        {
            _selectedElement = selectedElement;
            AvailableElements = availableElements;
        }
        private Element _selectedElement;
        public Element SelectedElement
        {
            get { return _selectedElement; }
            set
            {
                _selectedElement = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("SelectedElement"));
                }
            }
        }
        private IEnumerable<Element> _availableElements;
        public IEnumerable<Element> AvailableElements
        {
            get { return _availableElements; }
            set
            {
                _availableElements = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("AvailableElements"));
                }
            }
        }
    }
    public partial class MultiSelectListUserControl : UserControl
    {
        public IEnumerable<Element> ChosenItems
        {
            get { return ComboBoxBindingSources.Select(x => x.SelectedElement); }
        }
        public ObservableCollection<Element> AllItems
        {
            get { return (ObservableCollection<Element>)GetValue(AllItemsProperty); }
            set { SetValue(AllItemsProperty, value); }
        }
        // ReSharper disable once InconsistentNaming
        public ObservableCollection<ComboBoxBindingSource> ComboBoxBindingSources
        {
            get { return (ObservableCollection<ComboBoxBindingSource>)GetValue(ComboBoxBindingSourcesProperty); }
            set { SetValue(ComboBoxBindingSourcesProperty, value); }
        }
        public static DependencyProperty AllItemsProperty = DependencyProperty.Register(
            "AllItems",
            typeof(ObservableCollection<Element>),
            typeof(MultiSelectListUserControl),
            new FrameworkPropertyMetadata(OnAllItemsChanged));
        private static void OnAllItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MultiSelectListUserControl)d).RefreshAvailableItems();
        }
        public static DependencyProperty ComboBoxBindingSourcesProperty = DependencyProperty.Register(
            "ComboBoxBindingSources",
            typeof(ObservableCollection<ComboBoxBindingSource>),
            typeof(MultiSelectListUserControl));
        public void NewButton_Pressed(object sender, RoutedEventArgs e)
        {
            if (!AllItems.Any() || AllItems.Count == ComboBoxBindingSources.Count)
            {
                return;
            }
            var remainingItems = AllItems.Where(x => ChosenItems.All(y => x.ID != y.ID)).ToList();
            ComboBoxBindingSources.Add(new ComboBoxBindingSource(remainingItems.First(), remainingItems));
            RefreshAvailableItems();
        }
        public void ComboBox_SelectionChanged(object sender, RoutedEventArgs e)
        {
            RefreshAvailableItems();
        }
        private void RefreshAvailableItems()
        {
            if (ComboBoxBindingSources == null)
            {
                ComboBoxBindingSources = new ObservableCollection<ComboBoxBindingSource>();
            }
            foreach (var source in ComboBoxBindingSources)
            {
                var newAvailables =
                    AllItems.Where(
                        x => source.SelectedElement == x || ComboBoxBindingSources.All(y => y.SelectedElement != x));
                source.AvailableElements = newAvailables;
            }
        }
        public MultiSelectListUserControl()
        {
            InitializeComponent();
            SetValue(AllItemsProperty, new ObservableCollection<Element>());
        }
    }
}