Xaml Set SelecteIndex для itemSource обновлен в поле со списком

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

У меня пока так:

    <Style TargetType="ComboBox">
        <Setter Property="SelectedIndex"
                Value="0"></Setter>
    </Style>

Он устанавливает selectedIndex, но только при первой установке itemSource. Когда весь itemSource изменяется, он загружается в поле со списком, но больше не выбирается. В этот момент он должен выбрать первый пункт. Каждый ItemSource имеет привязку, и я не хочу устанавливать SelectedIndex по коду. Я хочу решение в xaml, возможно, с триггером.


person Patrick    schedule 04.09.2014    source источник


Ответы (2)


Я должен не согласиться с Шериданом - разработчику, конечно, разрешено устанавливать свойство SelectedIndex. Однако невозможно достичь поставленной цели только в XAML, т. е. используя стили, шаблоны элементов управления и триггеры. Это связано с тем, что свойства зависимостей имеют определенный приоритет значения, например. локально установленные значения (с помощью привязки или самого элемента управления) имеют более высокий приоритет, чем значения, полученные из стиля WPF (поэтому ваш сеттер работает только один раз). Подробнее об этом можно узнать на странице http://msdn.microsoft.com/en-us/library/ms743230(v=vs.110).aspx

Однако, помимо возможности управлять выбранным индексом из модели представления (как предложил Шеридан), есть также возможность создать, например. прикрепленное свойство зависимости, которое установлено в поле со списком и которое ищет изменения в свойстве ItemsSource полей со списком. Тогда ваш XAML будет выглядеть примерно так:

<ComboBox x:Name="ComboBox" l:ComboBoxExtensions.InitialIndexOnItemsSourceChanged="0" />

Обратите внимание, что я установил для прикрепленного свойства зависимости ComboBoxExtensions.InitialIndexOnItemsSourceChanged значение 0, что означает, что каждый раз, когда ItemsSource изменяется, SelectedIndex будет установлено значение 0. l: относится к локальному пространству имен XML (xmlns), на которое мне нужно ссылаться, чтобы использовать мой пользовательский прикрепленный файл. имущество.

Я реализовал свойство зависимости следующим образом:

public class ComboBoxExtensions
{
    public static readonly DependencyProperty InitialIndexOnItemsSourceChangedProperty;
    private static readonly IDictionary<ComboBox, BindingSpy<ComboBox, IEnumerable>> ComboBoxToBindingSpiesMapping = new Dictionary<ComboBox, BindingSpy<ComboBox, IEnumerable>>();


    static ComboBoxExtensions()
    {
        InitialIndexOnItemsSourceChangedProperty = DependencyProperty.RegisterAttached("InitialIndexOnItemsSourceChanged",
                                                                                       typeof (int?),
                                                                                       typeof (ComboBoxExtensions),
                                                                                       new FrameworkPropertyMetadata(null, OnInitialIndexOnItemsSourceChanged));
    }

    public static void SetInitialIndexOnItemsSourceChanged(ComboBox targetComboBox, int? value)
    {
        if (targetComboBox == null) throw new ArgumentNullException("targetComboBox");

        targetComboBox.SetValue(InitialIndexOnItemsSourceChangedProperty, value);
    }

    public static int? GetInitialIndexOnItemsSourceChanged(ComboBox targetComboBox)
    {
        if (targetComboBox == null) throw new ArgumentNullException("targetComboBox");

        return (int?) targetComboBox.GetValue(InitialIndexOnItemsSourceChangedProperty);
    }

    private static void OnInitialIndexOnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var targetComboBox = d as ComboBox;
        if (targetComboBox == null)
            return;

        if ((int?) e.NewValue != null)
        {
            SetInitialIndexIfPossible(targetComboBox);
            EstablishBindingSpy(targetComboBox);
            return;
        }

        ReleaseBindingSpy(targetComboBox);
    }

    private static void EstablishBindingSpy(ComboBox targetComboBox)
    {
        if (ComboBoxToBindingSpiesMapping.ContainsKey(targetComboBox))
            return;

        var bindingSpy = new BindingSpy<ComboBox, IEnumerable>(targetComboBox, ItemsControl.ItemsSourceProperty);
        bindingSpy.TargetValueChanged += OnItemsSourceChanged;
        ComboBoxToBindingSpiesMapping.Add(targetComboBox, bindingSpy);
    }

    private static void ReleaseBindingSpy(ComboBox targetComboBox)
    {
        if (ComboBoxToBindingSpiesMapping.ContainsKey(targetComboBox) == false)
            return;

        var bindingSpy = ComboBoxToBindingSpiesMapping[targetComboBox];
        bindingSpy.ReleaseBinding();
        ComboBoxToBindingSpiesMapping.Remove(targetComboBox);
    }

    private static void OnItemsSourceChanged(BindingSpy<ComboBox, IEnumerable> bindingSpy)
    {
        SetInitialIndexIfPossible(bindingSpy.TargetObject);
    }

    private static void SetInitialIndexIfPossible(ComboBox targetComboBox)
    {
        var initialIndexOnItemsSourceChanged = GetInitialIndexOnItemsSourceChanged(targetComboBox);
        if (targetComboBox.ItemsSource != null && initialIndexOnItemsSourceChanged.HasValue)
        {
            targetComboBox.SelectedIndex = initialIndexOnItemsSourceChanged.Value;
        }
    }
}

В этом классе я определяю ранее упомянутое присоединенное свойство. Другой важной частью является метод OnInitialIndexOnItemsSourceChanged, который будет вызываться, когда прикрепленное свойство установлено в поле со списком. Там я просто определяю, должен ли я соблюдать свойство ItemsSource поля со списком или нет.

Чтобы добиться наблюдения за ItemsSource, я использую другой пользовательский класс с именем BindingSpy, который устанавливает привязку данных между ItemsSource и пользовательским свойством зависимости связывающего шпиона, потому что это единственный способ, которым я могу определить, изменилось ли свойство ItemsSource. К сожалению, ComboBox (соответственно ItemsControl) не предоставляет событие, указывающее на изменение исходной коллекции. BindingSpy реализовано следующим образом:

public class BindingSpy<TSource, TValue> : DependencyObject where TSource : DependencyObject
{
    private readonly TSource _targetObject;
    private readonly DependencyProperty _targetProperty;

    public static readonly DependencyProperty TargetValueProperty = DependencyProperty.Register("TargetValue",
                                                                                                typeof (TValue),
                                                                                                typeof (BindingSpy<TSource, TValue>),
                                                                                                new FrameworkPropertyMetadata(null, OnTargetValueChanged));

    public BindingSpy(TSource targetObject, DependencyProperty targetProperty)
    {
        if (targetObject == null) throw new ArgumentNullException("targetObject");
        if (targetProperty == null) throw new ArgumentNullException("targetProperty");
        _targetObject = targetObject;
        _targetProperty = targetProperty;

        var binding = new Binding
                      {
                          Source = targetObject,
                          Path = new PropertyPath(targetProperty),
                          Mode = BindingMode.OneWay
                      };
        BindingOperations.SetBinding(this, TargetValueProperty, binding);
    }

    public TValue TargetValue
    {
        get { return (TValue) GetValue(TargetValueProperty); }
        set { SetValue(TargetValueProperty, value); }
    }

    public TSource TargetObject
    {
        get { return _targetObject; }
    }

    public DependencyProperty TargetProperty
    {
        get { return _targetProperty; }
    }

    public void ReleaseBinding()
    {
        BindingOperations.ClearBinding(this, TargetValueProperty);
    }

    private static void OnTargetValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var bindingSpy = d as BindingSpy<TSource, TValue>;
        if (bindingSpy == null)
            return;

        if (bindingSpy.TargetValueChanged != null)
            bindingSpy.TargetValueChanged(bindingSpy);
    }

    public event Action<BindingSpy<TSource, TValue>> TargetValueChanged;
}

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

Другой способ добиться этого — создать поведение WPF, которое является частью Blend SDK. Вы можете найти учебник здесь: http://wpftutorial.net/Behaviors.html. В основном это тот же шаблон, что и с прикрепленными свойствами.

Если вы используете MVVM, я определенно рекомендую вам использовать решение Шеридана, но если нет, мое решение может быть более подходящим.

Если у вас есть какие-либо вопросы, пожалуйста, не стесняйтесь спрашивать.

person feO2x    schedule 04.09.2014

ComboBox.SelectedIndex устанавливается самим ComboBox и не должен устанавливаться разработчиком. Предполагается, что данные привязывают к нему свойство int, чтобы вы могли указать, какой элемент выбран, и/или выбрать элемент из кода:

<ComboBox ItemsSource="{Binding YourItems}" SelectedIndex="{Binding YourIndex}" />

Затем в коде:

int selectedIndex = YourIndex;

Или, например, для выбора первого элемента:

YourIndex = 0;

Чтобы узнать больше о привязке данных, см. Обзор привязки данных‎ на странице MSDN.

person Sheridan    schedule 04.09.2014