相互独立的组合框,绑定到相同的数据源 - MVVM实现

时间:2011-03-02 23:16:18

标签: wpf silverlight mvvm combobox

我不确定我的标题是否正确,但这是我现在面临的问题..我有以下XAML代码..

        <ItemsControl.ItemTemplate>

        <DataTemplate>

            <StackPanel Orientation="Horizontal">

                        <ComboBox ItemsSource="{Binding Path=AvailableFields}"

                          SelectedItem="{Binding Path=SelectedField}"

                          ></ComboBox>

            </StackPanel>

        </DataTemplate>

        </ItemsControl.ItemTemplate>

这基本上做的是,如果我的数据源包含10个项目,那么这将生成10行组合框,并且所有组合框都被绑定到相同的项目源。

现在我的要求是在第一个组合框中选择一个项目后,该项目在后续组合框中不可用。如何在MVVM和WPF中满足这个要求?

2 个答案:

答案 0 :(得分:2)

WPF不提供此功能,但可以使用一些自定义编码来实现。

我创建了3个ViewModel类:

PreferencesVM - 这将是我们的DataContext。它包含可以出现在ComboBox中的主选项列表,还包含SelectedOptions属性,该属性用于跟踪在各种ComboBox中选择的项目。它还有一个Preferences属性,我们将ItemsControl.ItemsSource绑定到。

PreferenceVM - 这代表一个ComboBox。它有一个SelectedOption属性,ComboBox.SelectedItem绑定到该属性。它还引用了PreferencesVM,还有一个名为Options(ComboBox.ItemsSource绑定到此属性)的属性,它通过过滤器返回PreferencesVM上的Options,该过滤器检查项目是否可以显示在ComboBox中。

OptionVM - 表示ComboBox中的一行。

以下几点构成了解决方案的关键:

  1. 当设置了PreferenceVM.SelectedOption(即选择了ComboBoxItem)时,该项目将添加到PreferencesVM.AllOptions集合中。
  2. PreferenceVM处理Preferences.SelectedItems.CollectionChanged,并通过为Options属性引发PropertyChanged来触发刷新。
  3. PreferenceVM.Options使用过滤器来决定返回哪些项目 - 只允许不在PreferencesVM.SelectedOptions中的项目,除非它们是SelectedOption。
  4. 我上面所描述的内容可能足以帮助您前进,但为了省去您的头痛我将在下面发布我的代码。

    PreferencesVM.cs:

     public class PreferencesVM
            {
                public PreferencesVM()
                {
                    PreferenceVM pref1 = new PreferenceVM(this);
                    PreferenceVM pref2 = new PreferenceVM(this);
                    PreferenceVM pref3 = new PreferenceVM(this);
    
                    this._preferences.Add(pref1);
                    this._preferences.Add(pref2);
                    this._preferences.Add(pref3);
                    //Only three ComboBoxes, but you can add more here.
    
                    OptionVM optRed = new OptionVM("Red");
                    OptionVM optGreen = new OptionVM("Green");
                    OptionVM optBlue = new OptionVM("Blue");
    
    
                    _allOptions.Add(optRed);
                    _allOptions.Add(optGreen);
                    _allOptions.Add(optBlue);
                }
    
                private ObservableCollection<OptionVM> _selectedOptions =new ObservableCollection<OptionVM>();
                public ObservableCollection<OptionVM> SelectedOptions
                {
                    get { return _selectedOptions; }
                }
    
                private ObservableCollection<OptionVM> _allOptions = new ObservableCollection<OptionVM>();
                public ObservableCollection<OptionVM> AllOptions
                {
                    get { return _allOptions; }
                }
    
    
                private ObservableCollection<PreferenceVM> _preferences = new ObservableCollection<PreferenceVM>();
                public ObservableCollection<PreferenceVM> Preferences
                {
                    get { return _preferences; }
                }
            }
    

    PreferenceVM.cs:

    public class PreferenceVM:INotifyPropertyChanged
        {
            private PreferencesVM _preferencesVM;
            public PreferenceVM(PreferencesVM preferencesVM)
            {
                _preferencesVM = preferencesVM;
                _preferencesVM.SelectedOptions.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedOptions_CollectionChanged);
            }
    
            void SelectedOptions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this,new PropertyChangedEventArgs("Options"));
            }
    
            private OptionVM _selectedOption;
            public OptionVM SelectedOption
            {
                get { return _selectedOption; }
                set
                {
                    if (value == _selectedOption)
                        return;
                    if (_selectedOption != null)
                        _preferencesVM.SelectedOptions.Remove(_selectedOption);
                    _selectedOption = value;
                    if (_selectedOption != null)
                        _preferencesVM.SelectedOptions.Add(_selectedOption);
                }
            }
    
            private ObservableCollection<OptionVM> _options = new ObservableCollection<OptionVM>();
            public IEnumerable<OptionVM> Options
            {
                get { return _preferencesVM.AllOptions.Where(x=>Filter(x)); }
            }
    
                private bool Filter(OptionVM optVM)
                {
                    if(optVM==_selectedOption)
                        return true;
                    if(_preferencesVM.SelectedOptions.Contains(optVM))
                        return false;
                    return true;
                }
    
                public event PropertyChangedEventHandler PropertyChanged;
        }
    
    OptionVM.cs:
    
        public class OptionVM
        {
            private string _name;
            public string Name
            {
                get { return _name; }
            }
    
            public OptionVM(string name)
            {
                _name = name;
            }
    }
    

    MainWindow.xaml.cs:

      public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new PreferencesVM();
            }
    }
    

    MainWindow.xaml:

    <Window x:Class="WpfApplication64.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <ItemsControl ItemsSource="{Binding Path=Preferences}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <ComboBox ItemsSource="{Binding Path=Options}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedOption}"></ComboBox>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </Window>
    

    **请注意,为了减少代码行数,我提供的解决方案只生成3个ComboBoxes(而不是10个)。

答案 1 :(得分:2)

当我开始编码时,这比我想象的更难。下面的示例可以满足您的需求。组合框将包含所有仍然可用但未在另一个组合框中选择的字母。

XAML:

<Window x:Class="TestApp.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">

    <StackPanel>
        <ItemsControl ItemsSource="{Binding Path=SelectedLetters}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ComboBox 
                        ItemsSource="{Binding Path=AvailableLetters}" 
                        SelectedItem="{Binding Path=Letter}" /> 
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>

</Window>

代码背后:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;

namespace TestApp
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            DataContext = new VM();
        }
    }

    public class VM : INotifyPropertyChanged
    {
        public VM()
        {
            SelectedLetters = new List<LetterItem>();
            for (int i = 0; i < 10; i++)
            {
                LetterItem letterItem = new LetterItem();
                letterItem.PropertyChanged += OnLetterItemPropertyChanged;
                SelectedLetters.Add(letterItem);
            }
        }

        public List<LetterItem> SelectedLetters { get; private set; }

        private void OnLetterItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != "Letter")
            {
                return;
            }

            foreach (LetterItem letterItem in SelectedLetters)
            {
                letterItem.RefreshAvailableLetters(SelectedLetters);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public class LetterItem : INotifyPropertyChanged
        {
            static LetterItem()
            {
                _allLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Select(c => c.ToString());
            }

            public LetterItem()
            {
                AvailableLetters = _allLetters;
            }

            public void RefreshAvailableLetters(IEnumerable<LetterItem> letterItems)
            {
                AvailableLetters = _allLetters.Where(c => !letterItems.Any(li => li.Letter == c) || c == Letter);
            }

            private IEnumerable<string> _availableLetters;
            public IEnumerable<string> AvailableLetters
            {
                get { return _availableLetters; }
                private set
                {
                    _availableLetters = value;
                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("AvailableLetters"));
                    }
                }
            }


            private string _letter;
            public string Letter
            {
                get { return _letter; }
                set
                {
                    if (_letter == value)
                    {
                        return;
                    }
                    _letter = value;
                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("Letter"));
                    }
                }
            }

            public event PropertyChangedEventHandler PropertyChanged;

            private static readonly IEnumerable<string> _allLetters;
        }
    }
}