为什么DataGrid调用集合的IndexOf传递ItemcControl.ItemInfo类型的对象?

时间:2013-01-10 15:16:26

标签: c# wpf mvvm wpfdatagrid inotifycollectionchanged

我为SelectionHelper创建DataGrid,允许双向绑定所选项目 在此帮助器中,当选择从viewmodel更改时,我为第一个选定项目调用ScrollIntoView。呼叫成功返回。但后来在UI的消息队列中发生了某些事情,并调用了我的集合的IndexOf 由于UI虚拟化,我怀疑它是异步的。 DataGrid绝对是了解项目索引的重要信息。但是我无法理解为什么它会将ItemsControl.ItemInfo代替项目 这是一个bug还是未记录的功能?

我的收藏实现了这些界面:IList<T>IListINotifyCollectionChanged

以下是IndexOf的代码:

public int IndexOf(object value)
        {
            if ((value != null && !(value is T))
                || (value == null && typeof(T).IsValueType))
                throw new ArgumentException(WrongTypeMessage, "value");

            return IndexOf((T)value);
        }

它会按预期抛出异常=)

更新

是的,我的猜测是正确的。这是DataGrid的ScrollIntoView

的代码
public void ScrollIntoView(object item)
    {
      if (item == null)
        throw new ArgumentNullException("item");
      this.ScrollIntoView(this.NewItemInfo(item, (DependencyObject) null, -1));
    }

    internal void ScrollIntoView(ItemsControl.ItemInfo info)
    {
      if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
        this.OnBringItemIntoView(info);
      else
        this.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Delegate) new DispatcherOperationCallback(((ItemsControl) this).OnBringItemIntoView), (object) info);
    }

更新 问题已在此update

中修复

1 个答案:

答案 0 :(得分:2)

是的,我可能找到了原因:

这是DataGrid.ScrollIntoView的代码(由Resharper反编译)

internal void ScrollIntoView(ItemsControl.ItemInfo info)
    {
     if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
        this.OnBringItemIntoView(info);
     else
        this.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Delegate) new DispatcherOperationCallback(((ItemsControl) this).OnBringItemIntoView), (object) info);
    }

在这里,他们将info转换为对象类型。好吧,问题确实是DispatcherOperationCallback期望object 但是ItemsControl OnBringItemIntoView有两个重载: 一个用于ItemsControl.ItemInfo,另一个用于object类型。

internal object OnBringItemIntoView(ItemsControl.ItemInfo info)
    {
      FrameworkElement frameworkElement = info.Container as FrameworkElement;
      if (frameworkElement != null)
        frameworkElement.BringIntoView();
      else if ((info = this.LeaseItemInfo(info, true)).Index >= 0)
      {
        VirtualizingPanel virtualizingPanel = this.ItemsHost as VirtualizingPanel;
        if (virtualizingPanel != null)
          virtualizingPanel.BringIndexIntoView(info.Index);
      }
      return (object) null;
    }

internal object OnBringItemIntoView(object arg)
    {
     return this.OnBringItemIntoView(this.NewItemInfo(arg, (DependencyObject) null, -1));
    }
猜猜,哪一个被选中? ;-)
所以他们将ItemInfo包裹在ItemInfo中。这就是this.LeaseItemInfo(info, true) iside

的原因
internal object OnBringItemIntoView(ItemsControl.ItemInfo info)
    {
      FrameworkElement frameworkElement = info.Container as FrameworkElement;
      if (frameworkElement != null)
        frameworkElement.BringIntoView();
      else if ((info = this.LeaseItemInfo(info, true)).Index >= 0)
      {
        VirtualizingPanel virtualizingPanel = this.ItemsHost as VirtualizingPanel;
        if (virtualizingPanel != null)
          virtualizingPanel.BringIndexIntoView(info.Index);
      }
      return (object) null;
    }

收到错误的项目,并使用错误的值调用IndexOf

 internal ItemsControl.ItemInfo LeaseItemInfo(ItemsControl.ItemInfo info, bool ensureIndex = false)
    {
      if (info.Index < 0)
      {
        info = this.NewItemInfo(info.Item, (DependencyObject) null, -1);
        if (ensureIndex && info.Index < 0)
          info.Index = this.Items.IndexOf(info.Item);
      }
      return info;
    }

但是,对于未生成容器的所有情况,这应该会中断ScrollIntoView。简单的解决方法是通过ScrollIntoView

致电Dispatcher.BeginInvoke

我会等MS的answer