0

背景: 私はアプリに取り組んでいます。ページの 1 つはニュース フィードです。ニュースのデータは動的です。複数のテキスト ブロックや多数の画像が含まれる場合があり、コンテンツに基づいてニュース項目ごとにレイアウトが計算されます。

ListBox/DataTemplate/Binding で動作させることはできない (方法がわからない) ため、低レベルのアプローチに進み、仮想化キャンバスを実装しようとしました: https://dl.dropbox.com/u/16063542/ MyVirtualizingPanel.cs

アイデアは簡単です:

含まれるすべてのアイテムは以下を実装します。

public interface 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 メソッドを実装します。

public void 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(セグメント セグメント、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(セグメント セグメント、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);

        }
    }

多かれ少なかれ、それは機能します。ただし、レイアウトが複雑になり、複数の画像が表示されるようになると、スクロールのパフォーマンスがやや低下します。それに加えて、時々レンダリング時に顕著な問題があります: 突然 (1 フレームの間) 要素の位置がずれて表示され、「まばたき」のように見えますが、それを説明する方法がわかりません。とにかく、私の質問: このアプローチには最初に欠陥がありますか? それとも、それを機能させるために努力し続けるべきですか?

4

1 に答える 1

0

あなたの仮想化パネルは何も仮想化していないと思います。

コンテキストでは、仮想化は次のように機能します。10000 個のデータ項目を表示する必要があります。画面には 15 個の UI 要素しか収まりません。システム提供の VirtualizingStackPanel は ~17 個の UI 要素を作成し、ユーザーがリストをスクロールするときにそれらの要素を再利用します。UI 要素が画面外に出ると、パネルは UI 要素を「解放」します (必要に応じて介入できますClearContainerForItemOverride。方法を参照してください)。後で再利用できるように準備します (ここでも、必要に応じて干渉できます。 を参照してくださいPrepareContainerForItemOverride)。より多くの要素が必要な場合 (たとえば、データ項目が小さくなるため)、新しい UI 要素が作成されます。

全体のポイントは、10000 個の仮想アイテムがあるのに対し、実際のアイテムは最大 17 個しかないため、仮想化です。

ただし、あなたのコードでは、

public interface IVirtualizable
{
    FrameworkElement View { get; }
}

これは、データ項目と UI 要素の間に 1 対 1 の関係があることを意味します。UI 要素は高価で、GPU テクスチャやその他のシステム リソースを所有しています。そのため、要素を追加するとパフォーマンスの問題が発生します。

VirtualizingStackPanel の再実装は困難です。MyVirtualizingPanel クラスの 3 ~ 4 倍のコードがあります。幸いなことに、その必要はありません。すでにフレームワークに組み込まれており、非常に用途が広いです。選択されている/選択されていないものが原因でリスト ボックスが気に入らない場合は、プレーンな ItemsControl を使用し、ItemContainer を VirtualizingStackPanel に設定します。

于 2013-02-07T00:28:10.647 に答える