ComboBox, связанный с ICollectionView, отображает неправильный SelectedItem

У меня проблема с парой полей со списком в Silverlight 4.0.

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

Например. (это всего лишь пример кода, но он точно представляет, как он работает)

<ComboBox ItemsSource="{Binding BackgroundColors}"
          SelectedItem="{Binding SelectedBackgroundColor, Mode=TwoWay}" />

<ComboBox ItemsSource="{Binding ForegroundColors}"
          SelectedItem="{Binding SelectedForegroundColor, Mode=TwoWay}" />

Чтобы обеспечить эту динамическую фильтрацию, у меня есть 2 разных ICollectionView в моей ViewModel, к которым привязано каждое поле со списком ItemsSource. Каждый ICollectionView имеет источник одного и того же ObservableCollection<T>, но в фильтре настроено отфильтровывать выбранный элемент другого.

private ObservableCollection<Color> _masterColorList;         
public ICollectionView BackgroundColors { get; }
public ICollectionView ForegroundColors { get; }

Когда SelectedItem изменяется в пользовательском интерфейсе, свойства ViewModel обновляются, и как часть этого противоположное ICollectionView обновляется через .Refresh().

Eg.

public Color SelectedForegroundColor
{
    get { return _selectedForegroundColor; }
    set
    {
        if (_selectedForegroundColor == value)
            return;

        _selectedForegroundColor = value;

        BackgroundColors.Refresh();

        RaisePropertyChanged(() => SelectedForegroundColor);
    }
}

Это позволяет повторно запустить фильтр и изменить то, что доступно для выбора.

Это работает довольно хорошо, но есть проблема:

Скажем, у нас есть 3 цвета в нашем основном списке:

  • Синий
  • Зеленый
  • красный

Поле со списком 1 (CB1) выбрано Синим Поле со списком 2 (CB2) выбрано Зеленым

Таким образом, поля со списком имеют эти списки (выделено жирным шрифтом)

CB1

  • Синий
  • красный

CB2

  • Зеленый
  • красный

Если я затем выберу Красный в CB1, я ожидаю, что Красный будет удален из CB2 и заменен Синим. Это происходит правильно, НО отображаемое значение меняется с Зеленого на Синего.

Базовое связанное значение не изменяется, и ICollectionView.CurrentItem является правильным, но на дисплее явно отображается неправильное значение.

Я думаю, что происходит то, что, поскольку зеленый находится раньше в списке, он путается с тем, что показывается. Это также происходит, если вы сортируете ICollectionView.

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

Кто-нибудь видел эту проблему раньше или какие-либо идеи, как я могу это исправить?


person Alastair Pitts    schedule 02.11.2011    source источник


Ответы (1)


Есть как минимум 5 серьезных проблем с привязкой выпадающих списков.

Здесь, я думаю, вы столкнулись

http://connect.microsoft.com/VisualStudio/feedback/details/523394/silverlight-forum-combobox-selecteditem-binding

Вот этот.

Как только вы обновите привязку к источнику элементов, перестанет работать.

Я использовал одно из доступных там решений, этот код решил эту проблему для меня:

public class ComboBoxEx : ComboBox
    {
        #region Fields

        private bool _suppressSelectionChangedUpdatesRebind = false;

        #endregion

        #region Properties

        public static readonly DependencyProperty SelectedValueProperProperty =
            DependencyProperty.Register(
                "SelectedValueProper",
                typeof(object),
                typeof(ComboBoxEx),
                new PropertyMetadata((o, dp) => {
                                          var comboBoxEx = o as ComboBoxEx;
                                          if (comboBoxEx == null)
                                              return;

                                          comboBoxEx.SetSelectedValueSuppressingChangeEventProcessing(dp.NewValue);
                                      }));

        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        public object SelectedValueProper
        {
            get { return GetValue(SelectedValueProperProperty); }
            set { SetValue(SelectedValueProperProperty, value); }
        }

        #endregion

        #region Constructor and Overrides

        public ComboBoxEx()
        {
            SelectionChanged += ComboBoxEx_SelectionChanged;
        }

        /// <summary>
        /// Updates the current selected item when the <see cref="P:System.Windows.Controls.ItemsControl.Items"/> collection has changed.
        /// </summary>
        /// <param name="e">Contains data about changes in the items collection.</param>
        protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            // Must re-apply value here because the combobox has a bug that 
            // despite the fact that the binding still exists, it doesn't 
            // re-evaluate and subsequently drops the binding on the change event
            SetSelectedValueSuppressingChangeEventProcessing(SelectedValueProper);
        }

        #endregion

        #region Events

        private void ComboBoxEx_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // Avoid recursive stack overflow
            if (_suppressSelectionChangedUpdatesRebind)
                return;

            if (e.AddedItems != null && e.AddedItems.Count > 0) {
                //SelectedValueProper = GetMemberValue( e.AddedItems[0] );
                SelectedValueProper = SelectedValue; // This is faster than GetMemberValue
            }
            // Do not apply the value if no items are selected (ie. the else)
            // because that just passes on the null-value bug from the combobox
        }

        #endregion

        #region Helpers

        /// <summary>
        /// Gets the member value based on the Selected Value Path
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private object GetMemberValue(object item)
        {
            return item.GetType().GetProperty(SelectedValuePath).GetValue(item, null);
        }

        /// <summary>
        /// Sets the selected value suppressing change event processing.
        /// </summary>
        /// <param name="newSelectedValue">The new selected value.</param>
        private void SetSelectedValueSuppressingChangeEventProcessing(object newSelectedValue)
        {
            try {
                _suppressSelectionChangedUpdatesRebind = true;
                SelectedValue = newSelectedValue;
            }
            finally {
                _suppressSelectionChangedUpdatesRebind = false;
            }
        }

        #endregion
    }

Это не мой код, а код из статей, связанных с этой ошибкой.

person Valentin Kuzub    schedule 03.11.2011
comment
К сожалению, этот или любой другой обходной путь на этом сайте подключения, похоже, не сработал. Я считаю, что привязка SelectedItem/SelectedValue все еще работает, но визуальное отображение в поле со списком неверно. - person Alastair Pitts; 04.11.2011
comment
дох. Ну, я могу порекомендовать не привязываться к ICollectionView, может быть, и попробовать что-то более простое, например IEnumerable‹T› (для начала), я столкнулся с большой ошибкой с CollectionViewSource здесь stackoverflow.com/questions/6305608/ К сожалению, есть много проблем с привязками combobox. Я подумаю о других возможных причинах. - person Valentin Kuzub; 04.11.2011
comment
Да, в новой версии нашего кода мы вернули ObservableCollection‹T›. Просто супер раздражающий баг :/ - person Alastair Pitts; 04.11.2011
comment
привязка к выпадающему списку в silverlight — это худшее, с чем я столкнулся в своей жизни программирования. слишком много проблем .. и еще не исправлено. Это также раздражает, потому что вы совершенно не ожидаете, что он будет таким глючным. - person Valentin Kuzub; 04.11.2011