Выбранный элемент со списком WPF отсутствует в источнике элементов

Как сделать так, чтобы SelectedItem ComboBox отображался, даже если его нет в ItemsSource?

просто как простой пример ...

Предположим, у меня есть объект «Класс» со свойством «Учитель».

public class Class: INotifyPropertyChanged
{
   private Individual _teacher
   public Individual Teacher
   {
      get { return _teacher; }
      set 
      {
         teacher = value;
         RaisePropertyChanged("Teacher");
      }
   }
   ...
}

В графическом интерфейсе «Ведение классов» есть поле со списком для выбора учителя, и я хочу, чтобы в нем отображались только активные люди. И я не хочу, чтобы пользователи могли вводить текст в произвольной форме в ComboBox. Для этого я привязываю ItemsSource к коллекции в моей ViewModel, которая включает только активных людей, и SelectedItem, привязанный к свойству Teacher моего объекта «Class».

public class MaintainClasses_ViewModel:INotifyPropertyChanged
{
    private ObservableCollection<Individual> _activeIndividuals
        = GetAllActiveIndividuals();

    public ObservableCollection<Individual> ActiveIndividuals
    {
        get { return _activeIndividuals
    }

    public Class SelectedClass
    {
        get; 
        set;
    }
} 

с xaml для моего ComboBox ...

<ComboBox ItemsSource="{Binding ActiveIndividuals}" 
          SelectedItem="{Binding SelectedClass.Teacher}" />

Теперь предположим, что я открываю графический интерфейс «Ведение классов» для класса, в котором учитель, который уже был сохранен, теперь неактивен. Теперь ... Я хочу, чтобы в поле со списком отображались только активные люди - ПЛЮС учитель, который был выбран ранее (даже если они теперь неактивны, а НЕ в ItemsSource).

В настоящее время я нашел единственный способ сделать это - добавить Inactive Individual в коллекцию и вызвать событие PropertyChanged для коллекции. Однако мне бы очень хотелось заархивировать этот результат, не добавляя ничего в коллекцию. Предпочтительно какой-нибудь метод, использующий xaml, селекторы и / или преобразователи.


person Theodosius    schedule 27.02.2015    source источник
comment
Не могу понять ваш вопрос :(   -  person WPFUser    schedule 27.02.2015
comment
Некоторое время назад у меня была аналогичная проблема, одно из предложений, с которыми я столкнулся, заключалось в том, чтобы сделать поле со списком редактируемым, потому что это единственный раз, когда ComboBox допускает значения за пределами коллекции, к которой привязан. Я лично не пошел с таким подходом из-за требований моего приложения. Мой подход включал MenuItem вместо ComboBox. HTH   -  person XAMlMAX    schedule 27.02.2015
comment
красивое имя Феодосий   -  person Theodosius Von Richthofen    schedule 27.02.2015
comment
Чтобы прояснить мой вопрос ... Поскольку свойство, к которому привязано свойство SelectItem, также отсутствует в коллекции, к которой привязан ItemsSource, в поле со списком ничего не отображается как выбранное. Я бы хотел, чтобы выбранный элемент отображался в поле со списком, даже если его НЕТ в ItemsSource. Создание редактируемого поля со списком позволило бы добиться этого ... Я думаю, но я не хочу, чтобы пользователь мог вводить значения ... просто выберите их из списка (например, раскрывающийся список в winforms).   -  person Theodosius    schedule 28.02.2015


Ответы (1)


Вот что я использовал, надеюсь, это поможет:

ComboBoxAdaptor.cs:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Markup;

    namespace Adaptors
    {
        [ContentProperty("ComboBox")]
        public class ComboBoxAdaptor : ContentControl
        {
            #region Protected Properties
            protected bool IsChangingSelection
            {get; set;}

            protected ICollectionView CollectionView
            {get; set;}
            #endregion

            #region Dependency Properties
            public static readonly DependencyProperty ComboBoxProperty = 
                DependencyProperty.Register("ComboBox", typeof(ComboBox), typeof(ComboBoxAdaptor),
                new FrameworkPropertyMetadata(new PropertyChangedCallback(ComboBox_Changed)));
            public ComboBox ComboBox
            {
                get { return (ComboBox)GetValue(ComboBoxProperty);}
                set { SetValue(ComboBoxProperty, value);}
            }

            public static readonly DependencyProperty NullItemProperty =
                DependencyProperty.Register("NullItem", typeof(object), typeof(ComboBoxAdaptor),
                new PropertyMetadata("(None)");
            public object NullItem
            {
                get {return GetValue(NullItemProperty);}
                set {SetValue(NullItemProperty, value);}
            }

            public static readonly DependencyProperty ItemsSourceProperty =
                DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ComboBoxAdaptor),
                new FrameworkPropertyMetadata(new PropertyChangedCallback(ItemsSource_Changed)));
            public IEnumerable ItemsSource
            {
                get {return (IEnumerable)GetValue(ItemsSourceProperty);}
                set {SetValue(ItemsSourceProperty, value);}
            }

            public static readonly DependencyProperty SelectedItemProperty =
                DependencyProperty.Register("SelectedItem", typeof(object), typeof(ComboBoxAdaptor), 
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                new PropertyChangedCallback(SelectedItem_Changed)));
            public object SelectedItem
            {
                get {return GetValue(SelectedItemProperty);}
                set {SetValue(SelectedItemProperty, value);}
            }

            public static readonly DependencyProperty AllowNullProperty =
                DependencyProperty.Register("AllowNull", typeof(bool), typeof(ComboBoxAdaptor),
                new PropertyMetadata(true, AllowNull_Changed));
            public bool AllowNull
            {
                get {return (bool)GetValue(AllowNullProperty);}
                set {SetValue(AllowNullProperty, value);}
            }
            #endregion

            #region static PropertyChangedCallbacks
            static void ItemsSource_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
                adapter.Adapt();
            }

            static void AllowNull_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
                adapter.Adapt();
            }

            static void SelectedItem_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
                if (adapter.ItemsSource != null)
                {
                    //If SelectedItem is changing from the Source (which we can tell by checking if the
                    //ComboBox.SelectedItem is already set to the new value), trigger Adapt() so that we
                    //throw out any items that are not in ItemsSource.
                    object adapterValue = (e.NewValue ?? adapter.NullItem);
                    object comboboxValue = (adapter.ComboBox.SelectedItem ?? adapter.NullItem);
                    if (!object.Equals(adapterValue, comboboxValue))
                    {
                        adapter.Adapt();
                        adapter.ComboBox.SelectedItem = e.NewValue;
                    }
                    //If the NewValue is not in the CollectionView (and therefore not in the ComboBox)
                    //trigger an Adapt so that it will be added.
                    else if (e.NewValue != null && !adapter.CollectionView.Contains(e.NewValue))
                    {
                        adapter.Adapt();
                    }
                }
            }
            #endregion

            #region Misc Callbacks
            void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                if (ComboBox.SelectedItem == NullItem)
                {
                    if (!IsChangingSelection)
                    {
                        IsChangingSelection = true;
                        try
                        {
                            int selectedIndex = ComboBox.SelectedIndex;
                            ComboBox.SelectedItem = null;
                            ComboBox.SelectedIndex = -1;
                            ComboBox.SelectedIndex = selectedIndex;
                        }
                        finally
                        {
                            IsChangingSelection = false;
                        }
                    }
                }
                object newVal = (ComboBox.SelectedItem == null? null: ComboBox.SelectedItem);
                if (!object.Equals(SelectedItem, newVal)
                {
                    SelectedItem = newVal;
                }
            }

            void CollectionView_CurrentChanged(object sender, EventArgs e)
            {
                if (AllowNull && (ComboBox != null) && (((ICollectionView)sender).CurrentItem == null) && (ComboBox.Items.Count > 0))
                {
                    ComboBox.SelectedIndex = 0;
                }
            }
            #endregion

            #region Methods
            protected void Adapt()
            {
                if (CollectionView != null)
                {
                    CollectionView.CurrentChanged -= CollectionView_CurrentChanged;
                    CollectionView = null;
                }
                if (ComboBox != null && ItemsSource != null)
                {
                    CompositeCollection comp = new CompositeCollection();
                    //If AllowNull == true, add a "NullItem" as the first item in the ComboBox.
                    if (AllowNull)
                    {
                        comp.Add(NullItem);
                    }
                    //Now Add the ItemsSource.
                    comp.Add(new CollectionContainer{Collection = ItemsSource});
                    //Lastly, If Selected item is not null and does not already exist in the ItemsSource,
                    //Add it as the last item in the ComboBox
                    if (SelectedItem != null)
                    {
                        List<object> items = ItemsSource.Cast<object>().ToList();
                        if (!items.Contains(SelectedItem))
                        {
                            comp.Add(SelectedItem);
                        }
                    }
                    CollectionView = CollectionViewSource.GetDefaultView(comp);
                    if (CollectionView != null)
                    {
                        CollectionView.CurrentChanged += CollectionView_CurrentChanged;
                    }
                    ComboBox.ItemsSource = comp
                }
            }
            #endregion
        }
    }

Как использовать его в Xaml

<adaptor:ComboBoxAdaptor 
         NullItem="Please Select an Item.."
         ItemsSource="{Binding MyItemsSource}"
         SelectedItem="{Binding MySelectedItem}">
      <ComboBox Width="100" />
</adaptor:ComboBoxAdaptor>

Некоторые примечания

Если SelectedItem изменится на значение, отсутствующее в ComboBox, оно будет добавлено в ComboBox (но не в ItemsSource). В следующий раз, когда SelectedItem будет изменен с помощью Binding, любые элементы, отсутствующие в ItemsSource, будут удалены из ComboBox.

Кроме того, ComboBoxAdaptor позволяет вставлять нулевой элемент в ComboBox. Это дополнительная функция, которую можно отключить, установив AllowNull="False" в файле xaml.

person NuclearProgrammer    schedule 24.03.2016