开发者

Data bind to a portion of a collection

I have a static collection of items (say numbers from 1 to 100) that I'm presenting in a ComboBox. I can bind these items to the ComboBox no problem.

My question is how to bind a second ComboBox to a subset of those items. The behavior I want is to have the second ComboBox bound to the subset of items remaining after the first ComboBox is selected. For exampl开发者_JAVA百科e the first ComboBox would show 1,2,3...,100. If the number 43 is selected in the first ComboBox then the second ComboBox should show 44,45,...,100.

How can this be accomplished and have the second ComboBox update if the first is changed without a lot of code-behind?


I would do this with a using MVVM pattern. Create a class that implements INotifyChange and expose three Property.

  • ICollection FullCollection
  • int FirstIndex
  • ICollection PartialCollection

Use this class as DataContext for your Control and bind SelectedIndex of the first combo box to FirstIndex property, ItemSource of first combobox to FullCollection and ItemSource of second collection to PartialCollection (Be sure that SelectedIndex binding mode is Two Way). Then on set of FirstIndex property set the PartialCollection property as you want. Remember that you have to use NotifyPropertyChange on set method of each Properties. Hope this help.


I'd go with MVVM design but if you want to just do testing and want to see quick results without going into design patterns then I'd suggest using something like CLINQ / BLINQ / Obtics framework to leverege the power of LINQ while keeping the results live for the combo box.

Since LoSciamano has already posted a reply on that (while i was posting this!) I won't dive into details of MVVM implementation


I would expose the first collection as an ObservableCollection<T> and the second collection as a bare IEnumerable. You can then use the default collection view to handle the events necessary to re-filter the sub collection.

class FilteredVM : ViewModelBase
{
    public ObservableCollection<MyVM> Items { get; private set; }
    public IEnumerable<MyVM> SubItems { get; private set; }

    public FilteredVM()
    {
        this.Items = new ObservableCollection<MyVM>();
        this.SubItems = Enumerable.Empty<MyVM>();

        var view = CollectionViewSource.GetDefaultView(this.Items);
        view.CurrentChanged += (sender, e) => { SetupFilter();  };
        view.CollectionChanged += (sender, e) => { SetupFilter(); };
    }

    private void SetupFilter()
    {
        var view = CollectionViewSource.GetDefaultView(this.Items);
        var current = view.CurrentItem;
        if (current != null)
        {
            this.SubItems = this.Items.Where((vm,idx) => idx > view.CurrentPosition);
        }
        else
        {
            this.SubItems = Enumerable.Empty<MyVM>();
        }

        this.OnPropertyChanged("SubItems");
    }
}

Alternatively, if you'd like to keep CollectionViewSource out of your VM:

class FilteredVM : ViewModelBase
{
    private MyVM selectedItem;
    public MyVM SelectedItem
    {
        get { return this.selectedItem; }
        set
        {
            if (value != this.selectedItem)
            {
                this.selectedItem = value;
                this.OnPropertyChanged("SelectedItem");
                this.SetupFilter();
            }
        }
    }

    public ObservableCollection<MyVM> Items { get; private set; }
    public IEnumerable<MyVM> SubItems { get; private set; }

    public FilteredVM()
    {
        this.Items = new ObservableCollection<MyVM>();
        this.SubItems = Enumerable.Empty<MyVM>();

        this.Items.CollectionChanged += (sender, e) => { this.SetupFilter(); };
    }

    private void SetupFilter()
    {
        if (this.SelectedItem != null)
        {
            var item = this.SelectedItem; // save for closure
            this.SubItems = this.Items.SkipWhile(vm => vm != item).Skip(1);
        }
        else
        {
            this.SubItems = Enumerable.Empty<MyVM>();
        }

        this.OnPropertyChanged("SubItems");
    }
}

Keep in mind this will require SelectedItem to be properly bound in the View to the ViewModel. The first approach listed allows the SelectedItem to be bound to anywhere (or nowhere).


Have 2 Observable collections so that when item in the 1st box is selected, it will kick off method which clears and re populates the 2nd collection. As it is observableCollection, it gets reflected in the WPF GUI automatically


Here is a concrete example (as usual, may be improved, but the idea is here) :

Code behind an view model :

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }
}


public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        Initialsource = new ObservableCollection<int>();
        for (int i = 0; i < 101; i++)
        {
            Initialsource.Add(i);
        }
    }

    private int _selectedsourceItem;
    public int SelectedsourceItem
    {
        get { return _selectedsourceItem; }
        set
        {
            _selectedsourceItem = value;
            SubsetSource = new ObservableCollection<int>(Initialsource.Where(p => p > _selectedsourceItem));
            InvokePropertyChanged(new PropertyChangedEventArgs("SubsetSource"));
            InvokePropertyChanged(new PropertyChangedEventArgs("SelectedsourceItem"));
        }
    }

    private ObservableCollection<int> _initialsource;
    public ObservableCollection<int> Initialsource
    {
        get { return _initialsource; }
        set
        {
            _initialsource = value;
            InvokePropertyChanged(new PropertyChangedEventArgs("Initialsource"));
        }
    }

    private ObservableCollection<int> _subsetSource;
    public ObservableCollection<int> SubsetSource
    {
        get { return _subsetSource ?? (_subsetSource = new ObservableCollection<int>()); }
        set
        {
            _subsetSource = value;
            InvokePropertyChanged(new PropertyChangedEventArgs("SubsetSource"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void InvokePropertyChanged(PropertyChangedEventArgs e)
    {

        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, e);
    }
}

XAML :

<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
    <StackPanel Orientation="Horizontal">
        <ComboBox Width="100" ItemsSource="{Binding Initialsource}" SelectedItem="{Binding SelectedsourceItem, Mode=TwoWay}"></ComboBox>
        <ComboBox Width="100" ItemsSource="{Binding SubsetSource}"></ComboBox>
    </StackPanel>
</Grid>


You can use my ObservableComputations library:

ObservableCollection<Item> itemsSubset = ItemsObservableCollection
    .Skiping(fromIndex)
    .Taking(itemsInSubsetCount);

itemsSubset reflacts all the changes in ItemsObservableCollection.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜