【问题标题】:Mutually Exclusive comboboxes that binds to same data source - MVVM implementation绑定到相同数据源的互斥组合框 - MVVM 实现
【发布时间】:2011-07-07 16:19:16
【问题描述】:

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

        <ItemsControl.ItemTemplate>

        <DataTemplate>

            <StackPanel Orientation="Horizontal">

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

                          SelectedItem="{Binding Path=SelectedField}"

                          ></ComboBox>

            </StackPanel>

        </DataTemplate>

        </ItemsControl.ItemTemplate>

这基本上是,如果我的数据源包含十个项目,这将生成 10 行组合框,所有组合框都绑定到同一个项目源。

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

【问题讨论】:

  • 这些答案有帮助吗?如果是这样,您应该投票并接受,否则请提供一些反馈,以便我们知道我们哪里出错了。

标签: wpf silverlight mvvm combobox


【解决方案1】:

事实证明,这比我开始编写代码时所想的要难。下面的示例可以满足您的需求。组合框将包含所有仍然可用且未在另一个组合框中选择的字母。

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

【讨论】:

    【解决方案2】:

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

    我创建了 3 个 ViewModel 类:

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

    PreferenceVM - 这代表一个 ComboBox。它有一个 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();
            }
    }
    

    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 个)。

    【讨论】:

      猜你喜欢
      • 2011-08-18
      • 1970-01-01
      • 1970-01-01
      • 2013-06-20
      • 2011-06-13
      • 1970-01-01
      • 2015-12-17
      • 2013-09-03
      • 2013-04-07
      相关资源
      最近更新 更多