Как автоматически выбрать первый элемент в связанном списке Silverlight? - PullRequest
1 голос
/ 10 января 2010

Используя silverlight, у меня есть список с ItemsSource, привязанным к ObservableCollection, который обновляется асинхронно. Я хотел бы автоматически выбрать первый элемент в списке, как только будет завершено обновление привязки.

Я не могу найти хороший способ, чтобы это произошло. Я не вижу каких-либо полезных событий для обработки в списке, и если я связываюсь с событием CollectionChanged коллекции, привязка еще не обновляется, поэтому, если я установлю listbox.selectedindex в этот момент, я получу исключение, что значение отсутствует диапазона. Есть идеи? Может быть, какой-нибудь способ зацепить обновление привязки?

Ответы [ 3 ]

1 голос
/ 30 января 2012

Я потратил много времени на поиски решения этой проблемы в интернете, и, в основном, наткнулся на решение.

Что вы хотите сделать, это привязать ваш список к ICollectionView. Затем убедитесь, что для параметра IsSynchronizedWithCurrentItem не установлено значение false.

Плохо, не будет работать

IsSynchronizedWithCurrentItem="False"

Это значение по умолчанию для Silverlight, не тратьте время на его печать

IsSynchronizedWithCurrentItem="{x:Null}"

Это вызовет ошибку во время выполнения, и я считаю, что это то же самое, что и {x: null} в любом случае

IsSynchronizedWithCurrentItem="True"

ICollectionView имеет метод с именем MoveCurrentToFirst . Это имя кажется немного двусмысленным, но на самом деле оно перемещает указатель CurrentItem на первый элемент (я изначально думал, что он переупорядочил коллекцию, переместив любой выбранный элемент в первую позицию элемента). Свойство IsSynchronizedWithCurrentItem позволяет Listbox (или любому элементу управления, реализующему Selector) работать в магии с ICollectionViews. В своем коде вы можете вызвать ICollectioView.CurrentItem вместо того, что вы привязали Listbox.SelectedItem, чтобы получить выбранный в данный момент элемент.

Вот как я могу сделать мой ICollectionView доступным для просмотра (я использую MVVM):

public System.ComponentModel.ICollectionView NonModifierPricesView
        {
            get
            {
                if (_NonModifierPricesView == null)
                {
                    _NonModifierPricesView = AutoRefreshCollectionViewSourceFactory.Create(x => ((MenuItemPrice)x).PriceType == MenuItemPrice.PriceTypes.NonModifier);
                    _NonModifierPricesView.Source = Prices;
                    _NonModifierPricesView.ApplyFilter(x => ((MenuItemPrice)x).DTO.Active == true);
                }
                ICollectionView v = _NonModifierPricesView.View;
                v.MoveCurrentToFirst();
                return v;
            }
        }

Теперь, когда вы хотите привязаться к наблюдаемой коллекции, вы не можете использовать коллекцию по умолчанию CollectionViewSource, поскольку она не знает об обновлениях исходной коллекции. Вы, наверное, заметили, что я использую собственную реализацию CVS, которая называется AutoRefreshCollectionViewSource . Если память не изменяет, я нашел код в сети и изменил его для своих собственных нужд. Я добавил дополнительную функциональность для фильтрации, поэтому, возможно, смогу очистить эти классы еще больше.

Вот моя версия кода:

AutoRefreshCollectionViewSource.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Data;

    public class AutoRefreshCollectionViewSource : System.Windows.Data.CollectionViewSource
    {

        // A delegate for launching a refresh of the view at a different priority.
        private delegate void NoArgDelegate();
        private Predicate<object> MyFilter; // this is the filter we put on when we do ApplyFilter
        private Predicate<object> BaseFilter; // this is the filter that is applied always

        public AutoRefreshCollectionViewSource(Predicate<object> _baseFilter) : base() 
        {
            BaseFilter = _baseFilter;

            if (BaseFilter == null)
                BaseFilter = x => true;

        }

        /// <summary>
        /// A collection containing all objects whose event handlers have been
        /// subscribed to.
        /// </summary>
        private List<INotifyPropertyChanged> colSubscribedItems = new List<INotifyPropertyChanged>();

        // We must override the OnSourceChanged event so that we can subscribe
        // to the objects in the new collection (and unsubscribe from the old items).

        protected override void OnSourceChanged(object oldSource, object newSource)
        {
            // Unsubscribe from the old source.
            if (oldSource != null)
                SubscribeSourceEvents(oldSource, true);

            // Subscribe to the new source.
            if (newSource != null)
                SubscribeSourceEvents(newSource, false);

            base.OnSourceChanged(oldSource, newSource);
        }

        /// <summary>
        /// Adds or Removes EventHandlers to each item in the source collection as well as the
        /// collection itself (if supported).
        /// </summary>
        /// <param name="source">The collection to (un)subscribe to and whose objects should be (un)subscribed.</param>
        /// <param name="remove">Whether or not to subscribe or unsubscribe.</param>
        private void SubscribeSourceEvents(object source, bool remove)
        {
            // Make sure the source is not nothing.
            // This may occur when setting up or tearing down this object.
            if (source != null)

                if (source is INotifyCollectionChanged)
                    // We are (un)subscribing to a specialized collection, it supports the INotifyCollectionChanged event.
                    // (Un)subscribe to the event.
                    if (remove)
                        ((INotifyCollectionChanged)source).CollectionChanged -= Handle_INotifyCollectionChanged;
                    else
                        ((INotifyCollectionChanged)source).CollectionChanged += Handle_INotifyCollectionChanged;

            if (remove)
                // We are unsubscribing so unsubscribe from each object in the collection.
                UnsubscribeAllItemEvents();
            else
                // We are subscribing so subscribe to each object in the collection.
                SubscribeItemsEvents((IEnumerable)source, false);

        }

        /// <summary>
        /// Unsubscribes the NotifyPropertyChanged events from all objects
        /// that have been subscribed to. 
        /// </summary>
        private void UnsubscribeAllItemEvents()
        {
            while (colSubscribedItems.Count > 0)
                SubscribeItemEvents(colSubscribedItems[0], true);
        }

        /// <summary>
        /// Subscribes or unsubscribes to the NotifyPropertyChanged event of all items
        /// in the supplied IEnumerable.
        /// </summary>
        /// <param name="items">The IEnumerable containing the items to (un)subscribe to/from.</param>
        /// <param name="remove">Whether or not to subscribe or unsubscribe.</param>
        private void SubscribeItemsEvents(IEnumerable items, bool remove)
        {
            foreach (object item in items)
                SubscribeItemEvents(item, remove);
        }

        /// <summary>
        /// Subscribes or unsubscribes to the NotifyPropertyChanged event if the supplied
        /// object supports it.
        /// </summary>
        /// <param name="item">The object to (un)subscribe to/from.</param>
        /// <param name="remove">Whether or not to subscribe or unsubscribe.</param>
        private void SubscribeItemEvents(object item, bool remove)
        {
            if (item is INotifyPropertyChanged)
                // We only subscribe of the object supports INotifyPropertyChanged.

                if (remove)
                {
                    // Unsubscribe.
                    ((INotifyPropertyChanged)item).PropertyChanged -= Item_PropertyChanged;
                    colSubscribedItems.Remove((INotifyPropertyChanged)item);
                }
                else
                {
                    // Subscribe.
                    ((INotifyPropertyChanged)item).PropertyChanged += Item_PropertyChanged;
                    colSubscribedItems.Add((INotifyPropertyChanged)item);
                }
        }

        /// <summary>
        /// Handles a property changed event from an item that supports INotifyPropertyChanged.
        /// </summary>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="e">The event arguments associated with the event.</param>
        /// <remarks></remarks>
        private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // By default, we do not need to refresh.
            bool refresh = false;

            if (e.PropertyName == "Active" || e.PropertyName == "DTO.Active")
                refresh = true;

            if (refresh)
                // Call the refresh.
                // Notice that the dispatcher will make the call to Refresh the view.  If the dispatcher is not used,
                // there is a possibility for a StackOverFlow to result.
                this.Dispatcher.BeginInvoke(new NoArgDelegate(this.View.Refresh), null);
        }

        /// <summary>
        /// Handles the INotifyCollectionChanged event if the subscribed source supports it.
        /// </summary>
        private void Handle_INotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    SubscribeItemsEvents(e.NewItems, false);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    SubscribeItemsEvents(e.OldItems, true);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    SubscribeItemsEvents(e.OldItems, true);
                    SubscribeItemsEvents(e.NewItems, false);
                    break;
                case NotifyCollectionChangedAction.Reset:
                    UnsubscribeAllItemEvents();
                    SubscribeItemsEvents((IEnumerable)sender, false);
                    break;
            }
        }

        public void ApplyFilter(Predicate<object> f)
        {
            if (f != null)
                MyFilter = f;

            this.View.Filter = x => MyFilter(x) && BaseFilter(x);
            this.View.Refresh();
        }

        public void RemoveFilter()
        {
            this.View.Filter = BaseFilter;
            this.View.Refresh();
}
}

AutoRefreshCollectionViewSourceFactory.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
    public class AutoRefreshCollectionViewSourceFactory
    {
        private static List<AutoRefreshCollectionViewSource> Collections;
        public static AutoRefreshCollectionViewSource Create()
        {
            if (Collections == null)
                Collections = new List<AutoRefreshCollectionViewSource>();
            AutoRefreshCollectionViewSource cvs = new AutoRefreshCollectionViewSource(null);
            Collections.Add(cvs);
            return cvs;
        }
        public static AutoRefreshCollectionViewSource Create(Predicate<object> p)
        {
            if (Collections == null)
                Collections = new List<AutoRefreshCollectionViewSource>();
            AutoRefreshCollectionViewSource cvs = new AutoRefreshCollectionViewSource(p);
            Collections.Add(cvs);
            return cvs;
        }
        public static void ApplyFilterOnCollections()
        {
            foreach (AutoRefreshCollectionViewSource cvs in Collections)
                cvs.ApplyFilter(null);
        }
        public static void RemoveFilterFromCollections()
        {
            foreach (AutoRefreshCollectionViewSource cvs in Collections)
                cvs.RemoveFilter();
        }
        public static void CleanUp()
        {
            Collections = null;
        }
}
1 голос
/ 30 мая 2012

Еще одна вещь, которую вы можете сделать, это создать UserControl, который наследуется от ListBox, выставить ItemsChanged путем переопределения метода OnItemsChanged, обработать это событие и установить для SelectedIndex ListBox значение 0.

public partial class MyListBox : ListBox
{
    public delegate void ItemsSourceChangedHandler(object sender, EventArgs e);

    #region Override
    protected override void OnItemsChanged(
        NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);
        OnItemsChangedEvent(e);
    }
    #endregion Override

    #region Class Events

    public delegate void ItemsChangedEventHandler(object sender,
        NotifyCollectionChangedEventArgs e);
    public event ItemsChangedEventHandler ItemsChanged;

    private void OnItemsChangedEvent(
        NotifyCollectionChangedEventArgs e)
    {
        if (ItemsChanged != null)
        {
            ItemsChanged(this, e);
        }
    }

    #endregion Class Events

}

XAML для пользовательского элемента управления:

<ListBox x:Class="CoverArtRefiner.MyListBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">

    </Grid>
</ListBox>

На экране добавьте следующее:

xmlns:local="clr-namespace:<The Namespace which MyListBox is contained in>"

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

<local:MyListBox ItemsChanged="listBox_ItemsChanged" Background="Black" />

И, наконец, обработать событие:

private void listBox_ItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    MyListBox listBox = (MyListBox)sender;
    if (listBox.Items.Count > 0)
        listBox.SelectedIndex = 0;
}
0 голосов
/ 10 января 2010

В ListBox свяжите свойство SelectedItem со свойством в вашей привязке кода или модели представления. Затем в обработчике асинхронного обратного вызова установите для свойства первый элемент в коллекции и вызовите событие PropertyChanged для свойства (если только вы уже не вызываете событие в установщике своего свойства):

MySelectedListItem = _entitylist.FirstOrDefault();
RasisePropertyChanged("MySelectedListItem");
...