Взаимоисключающие комбинированные списки, которые привязываются к одному источнику данных - реализация MVVM - PullRequest
3 голосов
/ 03 марта 2011

Я не уверен, что мой заголовок верен, но с этой проблемой я столкнулся сейчас ... У меня есть приведенный ниже код XAML ..

        <ItemsControl.ItemTemplate>

        <DataTemplate>

            <StackPanel Orientation="Horizontal">

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

                          SelectedItem="{Binding Path=SelectedField}"

                          ></ComboBox>

            </StackPanel>

        </DataTemplate>

        </ItemsControl.ItemTemplate>

Что в основном это делает, если мой источник данных содержит десять элементов, то будет сгенерировано 10 строк комбинированных списков, и все комбинированные списки привязаны к одному и тому же источнику элементов.

Теперь мое требование: после выбора элементав первом поле со списком этот элемент не должен быть доступен в последующих полях со списком.Как удовлетворить это требование в MVVM и WPF?

Ответы [ 2 ]

2 голосов
/ 03 марта 2011

Это оказалось сложнее, чем я думал, когда начал кодировать.Ниже образец делает то, что вы хотите.В выпадающих списках будут все буквы, которые еще доступны и не выделены в другом выпадающем списке.

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 голосов
/ 03 марта 2011

Эта функциональность не предоставляется WPF, но ее можно реализовать с помощью некоторого пользовательского кодирования.

Я создал 3 класса ViewModel:

Предпочтения VM - Этобудет нашим DataContext.Он содержит основной список параметров, которые могут отображаться в комбинированных списках, а также содержит свойство SelectedOptions, которое отслеживает, какие элементы выбраны в различных комбинированных списках.Он также имеет свойство 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 и запускает обновление, вызывая PropertyChanged для свойства Options.
  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 поля со списком (не 10).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...