0

背景:我正在开发应用程序。其中一页是新闻提要。新闻中的数据是动态的:它可能包含多个文本块、许多图像,并且根据内容为每个新闻项计算布局。

由于不可能(我不知道如何)使其与 ListBox/DataTemplate/Binding 一起使用,我采用较低级别的方法并尝试实现虚拟化画布: https ://dl.dropbox.com/u/16063542/ MyVirtualizingPanel.cs

想法很简单:

每个包含的项目都实现:

公共接口 IVirtualizable { void ChangeState(VirtualizableState newState);

    VirtualizableState CurrentState { get; }

    double FixedHeight { get; }

    FrameworkElement View { get; }

    Thickness Margin { get; }
}

-panel 应放入 ScrollViewer 并初始化:

public void InitializeWithScrollViewer(ScrollViewer _scrollViewer)
    {
        _listScrollViewer = _scrollViewer;
        EnsureBoundToScrollViewer();
    }

    protected void EnsureBoundToScrollViewer()
    {
        Binding binding = new Binding();
        binding.Source = _listScrollViewer;
        binding.Path = new PropertyPath("VerticalOffset");
        binding.Mode = BindingMode.OneWay;
        this.SetBinding(ListVerticalOffsetProperty, binding);


    }

Panel 实现 AddItems 方法:

公共无效AddItems(IEnumerable _itemsToBeAdded){

        double topMargin = 0;


        foreach (var itemToBeAdded in _itemsToBeAdded)
        {

            itemToBeAdded.View.Margin = new Thickness(0, itemToBeAdded.Margin.Top + topMargin, 0, 0);

            _virtualizableItems.Add(itemToBeAdded);


            topMargin += itemHeightIncludingMargin;
        }

        Height = topMargin;

    }

然后根据滚动位置的变化加载和卸载项目:

private void LoadItemsInSegment(Segment segment, VirtualizableState desiredState) { for (int i = segment.LowerBound; i <= segment.UpperBound; i++) { var item = _virtualizableItems[i];

            item.ChangeState(desiredState);

            if (!Children.Contains(item.View))
            {
                Children.Add(item.View);
            }
        }
    }

private void LoadItemsInSegment(Segment segment, VirtualizableState desiredState) { for (int i = segment.LowerBound; i <= segment.UpperBound; i++) { var item = _virtualizableItems[i];

            item.ChangeState(desiredState);

            if (!Children.Contains(item.View))
            {
                Children.Add(item.View);
            }
        }
    }

 private void UnloadItemsInSegment(Segment segment)
    {
        for (int i = segment.LowerBound; i <= segment.UpperBound; i++)
        {
            var item = _virtualizableItems[i];


            Children.Remove(item.View);

            item.ChangeState(VirtualizableState.Unloaded);

        }
    }

或多或少,它有效。但是,随着布局变得更加复杂并获得多个图像,滚动性能变得有些差。除此之外,在渲染时有时会出现明显的问题:突然(一帧)元素出现错位,看起来像“眨眼”,不知道如何解释。无论如何,我的问题是:这种方法最初是否有缺陷?还是我应该继续努力让它发挥作用?

4

1 回答 1

0

我认为您的虚拟化面板不会虚拟化任何东西。

在上下文中,虚拟化按以下方式工作。您需要显示 10000 个数据项。屏幕仅适合 15 个 UI 元素。系统提供的 VirtualizingStackPanel 创建约 17 个 UI 元素,当用户滚动浏览您的列表时重用这些元素。当 UI 元素离开屏幕时,面板会“释放” UI 元素(如果需要,您可以进行干预,请参阅ClearContainerForItemOverride方法)。并为以后的重用做准备(同样,您可以根据需要进行干预,请参阅PrepareContainerForItemOverride)。如果需要更多元素(例如,因为数据项变得更小),则会创建新的 UI 元素。

重点是您有 10000 个虚拟物品,而实际物品只有约 17 个,因此是虚拟化。

但是,在您的代码中,我看到

public interface IVirtualizable
{
    FrameworkElement View { get; }
}

这意味着您的数据项和 UI 元素之间存在一对一的关系。UI 元素很昂贵,它们拥有 GPU 纹理和其他系统资源。这就是为什么您在添加更多元素时会遇到性能问题。

重新实现 VirtualizingStackPanel 很困难。它的代码是 MyVirtualizingPanel 类的 3-4 倍。幸运的是,您不需要这样做,它已经在框架中,而且用途广泛。如果你不喜欢列表框,因为它选择/未选择的东西,使用普通的 ItemsControl,将 ItemContainer 设置为 VirtualizingStackPanel。

于 2013-02-07T00:28:10.647 回答