结合到同一数据源的互斥组合框 - MVVM实现

问题描述:

我不知道我的标题是正确的,但是这是我现在面临的问题..我有以下XAML代码..结合到同一数据源的互斥组合框 - MVVM实现

 <ItemsControl.ItemTemplate> 

     <DataTemplate> 

      <StackPanel Orientation="Horizontal"> 

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

          SelectedItem="{Binding Path=SelectedField}" 

          ></ComboBox> 

      </StackPanel> 

     </DataTemplate> 

     </ItemsControl.ItemTemplate> 

什么这通常做的就是,如果我的数据源包含10个项目,这将产生10列组合框和所有组合框是有界到同一的ItemSource。

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

+0

难道这些答案可以帮助?如果是这样,你应该upvote和接受,否则给一些反馈,所以我们知道我们出错的地方。 – 2011-03-22 00:09:34

此功能不是由WPF提供的,但它可以使用一些定制编码来实现。

我创建了3视图模型类:

PreferencesVM - 这将是我们的DataContext。它包含可以出现在组合框选项的主列表,并且还含有SelectedOptions属性,它跟踪哪些项目在不同的组合框选择。它也有一个Preferences属性,我们将把我们的ItemsControl.ItemsSource绑定到。

偏好VM - 这表示一个组合框。它有一个SelectedOption属性,该属性与ComboBox.SelectedItem绑定。它还具有对PreferencesVM的引用,并且名为Options的属性(ComboBox.ItemsSource绑定到此),该属性通过过滤器返回PreferencesVM上的选项,该过滤器检查该项是否可以显示在ComboBox中。

OptionVM - 表示ComboBox中的一行。

以下几点形成的关键溶液:

  1. 当PreferenceVM.SelectedOption被设定(即,ComboBoxItem被选中)时,该项目被添加到PreferencesVM.AllOptions集合。
  2. PreferenceVM处理Preferences.SelectedItems.CollectionChanged,并通过为Options属性提高PropertyChanged来触发刷新。
  3. PreferenceVM.Options使用过滤器来决定要返回哪些项目 - 只允许不在PreferencesVM.SelectedOptions中的项目,除非它们是SelectedOption。

我上面描述的可能足以让你走,但为了节省你的头痛,我会在下面发布我的代码。

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

主窗口。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个组合框(不是10)。

事实证明,当我开始编码时,这比我想象的要困难。下面的示例做你想要的。组合框将包含所有仍然可用且未在另一个组合框中选中的字母。

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