web-dev-qa-db-ja.com

リストボックスを備えたWPFリストボックス-UIの仮想化とスクロール

私のプロトタイプは、サムネイル画像で表される「ページ」を含む「ドキュメント」を表示します。各ドキュメントには、任意の数のページを含めることができます。たとえば、各5ページの1000ドキュメント、各1000ページの5ドキュメント、またはその中間の場合があります。ドキュメントには他のドキュメントは含まれていません。私のxamlマークアップには、ListBoxがあります。そのItemsTemplateは、ListBoxも持つinnerItemsTemplateを参照しています。ドキュメントやページに対してさまざまな操作(削除、マージ、新しい場所への移動など)を実行できるように、選択したアイテムの2つのレベルが必要です。 innerItemsTemplate ListBoxは、WrapPanelItemsPanelTemplateとして使用します。

それぞれ数ページのドキュメントが多数あるシナリオ(たとえば、それぞれ5ページの10000ドキュメント)の場合、VirtualizingStackPanelによるUI仮想化のおかげでスクロールはうまく機能します。ただし、ページ数が多いと問題が発生します。 1000ページのドキュメントは、一度に約50ページしか表示されません(画面に収まるものは何でも)。下にスクロールすると、外側のListBoxが次のドキュメントに移動し、950ページほどスキップします。目に見える。それに加えて、VirtualzingWrapPanelがないため、アプリのメモリが実際に増加します。

特に説明が難しいので、これを正しい方法で行っているのだろうかと思います。 UI仮想化を使用し、スムーズなスクロールを使用して、各1000ページ(画面に収まるもののみを表示)で10000のドキュメントを表示できるようにしたいと思います。

次のドキュメントを表示する前に、スクロールがドキュメント内のすべてのページを移動することを確認し、UIの仮想化を維持するにはどうすればよいですか?スクロールバーは次のドキュメントにのみ移動するようです。

ListBox内でListBoxを使用する現在の方法で、「ドキュメント」と「ページ」を表すことは論理的に思えますか?

ご意見をいただければ幸いです。ありがとうございました。

26
Rob Buhler

ここでの答えは驚くべきものです。

  • ItemsControlまたはListBoxを使用すると、コントロールが「アイテムごと」にスクロールするため、ドキュメント全体を一度にジャンプするという、現在の動作が得られます。
  • 代わりにTreeViewを使用すると、コントロールはスムーズにスクロールするため、ドキュメントをスクロールして次のドキュメントに移動できますが、仮想化は可能です。

WPFチームがこの動作を選択した理由は、TreeViewは通常、表示領域よりも大きいアイテムがあるのに対し、通常はListBoxesにはないためだと思います。

いずれにせよ、WPFでは、TreeViewを変更するだけでListBoxの外観と動作をItemsControlまたはItemContainerStyleのようにするのは簡単です。これは非常に簡単です。独自のテンプレートを作成することも、システムテーマファイルから適切なテンプレートをコピーすることもできます。

したがって、次のようなものになります。

<TreeView ItemsSource="{Binding documents}">
  <TreeView.ItemsPanel>
    <ItemsPanelTemplate>
      <VirtualizingStackPanel />
    </ItemsPanelTemplate>
  </TreeView.ItemsPanel>
  <TreeView.ItemContainerStyle>
    <Style TargetType="{x:Type TreeViewItem}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type TreeViewItem}">
            <ContentPresenter /> <!-- put your desired container style here  with a ContentPresenter inside -->
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </TreeView.ItemContainerStyle>
  <TreeView.ItemTemplate>
    <DataTemplate TargetType="{x:Type my:Document}">
      <Border BorderThickness="2"> <!-- your document frame will be more complicated than this -->
        <ItemsControl ItemsSource="{Binding pages}">
          ...
        </ItemsControl>
      </Border>
    </DataTemplate>
  </TreeView.ItemTemplate>
</TreeView>

ピクセルベースのスクロールとListBoxスタイルの複数選択を連携させる

この手法を使用してピクセルベースのスクロールを取得する場合、ドキュメントを表示する外側のItemsControlをListBoxにすることはできません(ListBoxはTreeViewまたはTreeViewItemのサブクラスではないため)。したがって、ListBoxの複数選択のサポートがすべて失われます。私の知る限り、これら2つの機能を一緒に使用するには、いずれかの機能に独自のコードを含める必要があります。

同じコントロールに両方の機能セットが必要な場合は、基本的にいくつかのオプションがあります。

  1. TreeViewItemのサブクラスで自分で複数選択を実装します。複数の子を選択できるため、外部コントロールにはTreeViewの代わりにTreeViewItemを使用します。 ItemsContainerStyle内のテンプレートで:ContentPresenterの周りにCheckBoxを追加し、テンプレートでCheckBoxをIsSelectedにバインドし、コントロールテンプレートを使用してCheckBoxのスタイルを設定して目的の外観にします。次に、独自のマウスイベントハンドラーを追加して、複数選択のためのCtrl-ClickおよびShift-Clickを処理します。

  2. VirtualizingPanelのサブクラスで、ピクセルスクロール仮想化を自分で実装します。 VirtualizingStackPanelの複雑さのほとんどは、ピクセル以外のスクロールとコンテナのリサイクルに関連しているため、これは比較的簡単です。 Dan Crevierのブログ VirtualizingPanelを理解するためのいくつかの有用な情報があります。

25
Ray Burns

リフレクションを使用してVirtualizingStackPanelのプライベート機能にアクセスする準備ができている場合は、仮想化を犠牲にすることなく、WPF4.0でスムーズなスクロールVirtualizingStackPanelsを実現できます。あなたがしなければならないのは、VirtualizingStackPanelのプライベートIsPixelBasedプロパティをtrueに設定することです。

.Net 4.5では、VirtualizingPanel.ScrollUnit = "Pixel"を設定できるため、このハックは必要ありません。

それを本当に簡単にするために、ここにいくつかのコードがあります:

public static class PixelBasedScrollingBehavior 
{
    public static bool GetIsEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsEnabledProperty);
    }

    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }

    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior), new UIPropertyMetadata(false, HandleIsEnabledChanged));

    private static void HandleIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var vsp = d as VirtualizingStackPanel;
        if (vsp == null)
        {
            return;
        }

        var property = typeof(VirtualizingStackPanel).GetProperty("IsPixelBased",
                                                                     BindingFlags.NonPublic | BindingFlags.Instance);

        if (property == null)
        {
            throw new InvalidOperationException("Pixel-based scrolling behaviour hack no longer works!");
        }

        if ((bool)e.NewValue == true)
        {
            property.SetValue(vsp, true, new object[0]);
        }
        else
        {
            property.SetValue(vsp, false, new object[0]);
        }
    }
}

たとえば、これをListBoxで使用するには、次のようにします。

<ListBox>
   <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
         <VirtualizingStackPanel PixelBasedScrollingBehavior.IsEnabled="True">
          </VirtualizingStackPanel>
       </ItemsPanelTemplate>
   </ListBox.ItemsPanel>
</ListBox>
43
Samuel Jack

.NET4.5にはVirtualizingPanel.ScrollUnit="ScrollUnit"プロパティ。 TreeViewの1つをListBoxに変換したところ、パフォーマンスが著しく向上しました。

詳細はこちら: http://msdn.Microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.scrollunit(v = vs.110).aspx

16
Boden Garman

これは私のために働いた。いくつかの単純な属性がそれを行うようです(.NET4.5)

<ListBox            
    ItemsSource="{Binding MyItems}"
    VirtualizingStackPanel.IsVirtualizing="True"
    VirtualizingStackPanel.ScrollUnit="Pixel"/>
7
rmirabelle

この回答の前に質問を付けさせてください。ユーザーは、リスト内のすべてのアイテム内のすべてのサムネイルを常に表示する必要がありますか?

その質問に対する答えが「いいえ」の場合、おそらく、内側のアイテムテンプレート内に表示されるページの数を制限し(たとえば、スクロールが5ページでうまく機能することを示している場合)、別のページを使用することが可能です。より大きく、そのドキュメントのすべてのページを表示する「選択されたアイテム」テンプレート? Billy Hollisが、選択したアイテムをdnrtvのリストボックスに「ポップ」する方法を説明しています episode 115

0
kiwipom