2010-01-10 84 views
1

使用silverlight,我有一个ItemsSource绑定到一个ObservableCollection异步更新的列表框。我想在绑定完成更新后自动选择列表框中的第一项。如何自动选择绑定的silverlight列表框中的第一个项目?

我找不到一个好的方法来实现这一点。我没有看到任何有用的事件在列表框中处理,如果我绑定到集合的CollectionChanged事件,绑定还没有更新,所以如果我在这一点上设置listbox.selectedindex,我会得到一个异常,该值是out的范围。有任何想法吗?也许某种方式来绑定绑定更新?

回答

0

在ListBox上,将SelectedItem属性绑定到您的codebind或viewmodel上的属性。然后,在异步回调处理程序设置属性的第一个项目集合中,提高PropertyChanged事件的财产(除非你已经在你的setter方法引发该事件):

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

我花了很长一段时间在网络上寻找解决方案来解决这个问题,并且最终导致了解决方案的磕磕绊绊。

你想要做的是将你的列表框绑定到一个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,因为它不知道源集合的更新。您可能注意到我正在使用名为AutoRefreshCollectionViewSource的自定义CVS实现。如果内存服务,我在网上找到代码并修改它以供我自己使用。我为过滤添加了额外的功能,所以可能会更多地清理这些类。

这里是我的代码版本:

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

你能做的就是创造出从Control继承的用户控件的另一件事,暴露通过重写OnItemsChanged方法ItemsChanged,以及处理该事件,并设置列表框的SelectedIndex为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 for the User控制:

<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>" 

我们自定义的控件添加到您的窗口/用户控件:

<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; 
} 
相关问题