次の方法で共有


WPF DataGridでの巨大テーブルのスクロール性能

質問

2010年10月22日金曜日 4:53

お世話になります。

.NET4 C#、WPFで巨大テーブル(最大21億行)をグリッドでストレスなく操作できるアプリの開発を行っており、DataGridコントロールの使用を検討しています。現在、事前にテストプログラムを作成し評価している所です。

テーブルサイズが大きい時、前方向(画面の下方向)への行・ページのスクロール性能は問題ないのですが、

①スクロールバーのスライダーを移動させた場合(例えば、先頭→最後)グリッドの表示内容が更新されるのに10秒程度かかる

※所要時間はPC性能とテーブルサイズによって異なる

②後方向(画面の上方向)への行・ページのスクロールが異常に遅い

と言う問題があります。

下記にテストプログラムのソースを示します。実行結果のタイムスタンプを見ると、DataCollectionクラスに、データの取得要求が出るのに時間がかかっています。

これは、DataGridの使い方に問題があるのでしょうか?、ご教示お願いします。

=====================================================

<Window x:Class="HugeTable_GridTest.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Loaded="Window_Loaded" Title="Virtual Grid" Height="350" Width="525">

    <Grid>

        <DataGrid x:Name="VirtualGrid"

                  DataContext="{Binding}"

                  VirtualizingStackPanel.IsVirtualizing="True"

                  VirtualizingStackPanel.VirtualizationMode="Recycling"

                  ScrollViewer.IsDeferredScrollingEnabled="True"

                  DataGrid.ItemsSource="{Binding}"

                  AutoGenerateColumns="True"

                  AutoGeneratingColumn="dataGrid_AutoGenerateColumn"

                  LoadingRow="dataGrid_LoadingRow" />

    </Grid>

</Window>

=====================================================

using System;

using System.Windows;

using System.Collections;

using System.Collections.Generic;

using System.Windows.Controls;

using System.Diagnostics;

namespace HugeTable_GridTest

{

    public partial class MainWindow : Window

    {

        public MainWindow()

        {

            InitializeComponent();

        }

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            DataCollection<RequestData> collection = new DataCollection<RequestData>();

            DataContext = collection;

        }

        private void dataGrid_AutoGenerateColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)

        {

            e.Column.Header = "Request_index Time_stamp";

        }

        private void dataGrid_LoadingRow(object sender, DataGridRowEventArgs e)

        {

            e.Row.Header = (e.Row.GetIndex() ; 1).ToString("###,###");

        }

    }

    public class DataCollection<T> : IList<T>, IList

    {

        //private readonly int _rowCount = 10000000;      // ★★★ テーブル行数 1千万 ★★★

        //private readonly int _rowCount = 50000000;      // ★★★ テーブル行数 5千万 ★★★

        private readonly int _rowCount = 100000000;       // ★★★ テーブル行数 1億   ★★★

        //private readonly int _rowCount = 500000000;     // ★★★ テーブル行数 5億   ★★★

        public virtual int Count { get { return _rowCount; } }

        public T this[int index]

        {

            get

            {

                String data = index.ToString() ; " " ; DateTime.Now.ToString() ; "." ; DateTime.Now.Millisecond.ToString();

                Debug.WriteLine("RequestData..." ; data);

                var list = new List<RequestData>{new RequestData{RequestIndex=data}};

                IList<T> theList = (IList<T>)list;

                return theList[0];

            }

            set { throw new NotSupportedException(); }

        }

        object IList.this[int index]

        {

            get { return this[index]; }

            set { throw new NotSupportedException(); }

        }

        public IEnumerator<T> GetEnumerator()

        {

            for (int i = 0; i < Count; i;;)

            {

                yield return this[i];

            }

        }

        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        public void Add(T item) { throw new NotSupportedException(); }

        int IList.Add(object value) { throw new NotSupportedException(); }

        bool IList.Contains(object value) { return Contains((T)value); }

        public bool Contains(T item) { return false; }

        public void Clear() { throw new NotSupportedException(); }

        int IList.IndexOf(object value) { return IndexOf((T)value); }

        public int IndexOf(T item) { return -1; }

        public void Insert(int index, T item) { throw new NotSupportedException(); }

        void IList.Insert(int index, object value) { Insert(index, (T)value); }

        public void RemoveAt(int index) { throw new NotSupportedException(); }

        void IList.Remove(object value) { throw new NotSupportedException(); }

        public bool Remove(T item) { throw new NotSupportedException(); }

        public void CopyTo(T[] array, int arrayIndex) { throw new NotSupportedException(); }

        void ICollection.CopyTo(Array array, int index) { throw new NotSupportedException(); }

        public object SyncRoot { get { return this; } }

        public bool IsSynchronized { get { return false; } }

        public bool IsReadOnly { get { return true; } }

        public bool IsFixedSize { get { return false; } }

    }

    public class RequestData

    {

        private string _RequestIndex;

        public string RequestIndex

        {

            get { return _RequestIndex; }

            set { _RequestIndex = value; }

        }

    }

}

=====================================================

すべての返信 (8)

2010年10月22日金曜日 5:10 ✅回答済み

①スクロールバーのスライダーを移動させた場合(例えば、先頭→最後)グリッドの表示内容が更新されるのに10秒程度かかる
※所要時間はPC性能とテーブルサイズによって異なる

この問題ですが、すでに Connect に報告されています。

https://connect.microsoft.com/VisualStudioJapan/feedback/details/535243/net-framework-4-wpf-datagrid

ひらぽん http://d.hatena.ne.jp/hilapon/


2010年10月26日火曜日 2:33 ✅回答済み

ありがとうございます。

商用グリッドも検討のスコープに入れていますが、まずは必要最低限の機能で自製することを検討してみます。

参考になるオープンソース等ありましたら教えて下さい。

オープンソースは判りませんが、暫定的に WindowsForms との相互運用という手もあります。DataGrid の問題が解決するまでは、当面 WindowsFormsHost を使って DataGridView を載せて代用するという手もありかと。。。

以下、WindowsFormsHost を使った簡単なサンプルです。

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="MainWindow" Height="350" Width="525" Initialized="Window_Initialized">
 <Grid>
  <Grid.RowDefinitions>
   <RowDefinition Height="80" />
   <RowDefinition />
  </Grid.RowDefinitions>
  <WindowsFormsHost Grid.Row="1" HorizontalAlignment="Stretch" Margin="0" 
       Name="windowsFormsHost1" VerticalAlignment="Stretch" />
 </Grid>
</Window>

MainWindow.xaml.cs

using System.Data;
using System.Windows;
using System.Windows.Forms;

namespace WpfApplication1 {
 /// <summary>
 /// MainWindow.xaml の相互作用ロジック
 /// </summary>
 public partial class MainWindow : Window {

  DataGridView _grid;

  public MainWindow() {
   InitializeComponent();
  }

  private void Window_Initialized(object sender, System.EventArgs e) {

   DataTable dt = new DataTable();
   dt.Columns.Add("id", typeof(int));
   dt.Columns.Add("name", typeof(string));
   dt.Columns.Add("address", typeof(string));
   dt.Columns.Add("telephone", typeof(string));

   dt.Rows.Add(new object[] { 0, "太郎", "東京都新宿区", "03********" });
   dt.Rows.Add(new object[] { 0, "次郎", "東京都豊島区", "03********" });
   dt.Rows.Add(new object[] { 0, "三郎", "東京都渋谷区", "03********" });
   dt.Rows.Add(new object[] { 0, "四郎", "東京都品川区", "03********" });

   _grid = new DataGridView();
   _grid.Dock = DockStyle.Fill;
   windowsFormsHost1.Child = _grid;
   _grid.DataSource = dt;
  }
 }
}

ひらぽん http://d.hatena.ne.jp/hilapon/


2010年10月26日火曜日 14:14 ✅回答済み | 1 票

ListView と GridView の組み合わせというのはどうでしょうか?
入力も可能です。
DataGrid が WPF で用意されるまではこれを使っていました。
http://msdn.microsoft.com/ja-jp/library/system.windows.controls.listview(VS.80).aspx

さらに VirtualizingStackPanel で高速化もできます。
http://msdn.microsoft.com/ja-jp/library/system.windows.controls.virtualizingpanel(v=VS.80).aspx

えムナウ@わんくま同盟 Microsoft MVP Visual Studio C# Since 2005/01-2010/12


2010年10月27日水曜日 4:23 ✅回答済み

えムナウ さん

ありがとうございます。

最初に記載したテストプログラムのxamlを下記に示すように書き換えてテストしました。(まずい所があればご指摘下さい)

結果は、体感ですが、

①スクロールバーのスライダーでの表示位置移動は、瞬時ではありませんが、1億行データでも実用可

②後方向(画面の上方向)への行スクロールはDataGridと同等で遅い、ページスクロールはDataGridより遅い

といった所です。

<Window x:Class="HugeTable_GridTest.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Loaded="Window_Loaded" Title="Virtual Grid" Height="350" Width="525">

    <Grid>

        <ListView x:Name="VirtualGrid"

                  DataContext="{Binding}"

                  VirtualizingStackPanel.IsVirtualizing="True"

                  VirtualizingStackPanel.VirtualizationMode="Recycling"

                  ScrollViewer.IsDeferredScrollingEnabled="True"

                  ListView.ItemsSource="{Binding}">

            <ListView.View>

                <GridView>

                    <GridViewColumn Header="Request_index Time_stamp" Width="200">

                        <GridViewColumn.CellTemplate>

                            <DataTemplate>

                                <TextBlock Text="{Binding RequestIndex}"/>

                            </DataTemplate>

                        </GridViewColumn.CellTemplate>

                    </GridViewColumn>

                </GridView>

            </ListView.View>

        </ListView>

    </Grid>

</Window>

機能面(行・列選択)では、選択行は色が変わるが列選択が不可?、列選択の仕様を工夫する必要がありそうです。

もう少し検討してみます。


2010年10月25日月曜日 4:19

ひらぽんさん 有難うございます。

DataGridコントロールの問題のようですね。

②も同様と推定されますが・・・

DataGridコントロールで対策されるか否か、対策されるならば時期はいつかも不明で、当方としてはどう対処すべきか悩ましい所です。

別の方式を検討します。


2010年10月25日月曜日 5:52

> 別の方式を検討します。

サードパーティの製品を使うというのも手ですね。 例えば インフラジスティックス社の xamDataGrid は、大量のデータを高速に処理できるのが自慢なようです。

http://jp.infragistics.com/dotnet/netadvantage/wpf/xamdatagrid.aspx#Overview

ひらぽん http://d.hatena.ne.jp/hilapon/


2010年10月25日月曜日 14:59

ありがとうございます。

商用グリッドも検討のスコープに入れていますが、まずは必要最低限の機能で自製することを検討してみます。

参考になるオープンソース等ありましたら教えて下さい。


2010年10月26日火曜日 7:31

ひらぽん さん

サンプルまで作成して頂き有難うございます。

実はWPFのDataGridの前にWindowsFormsのDataGridViewを試して見ました。仮想グリッド(VirtualMode=true)を指定し、Just in Timeで表示に必要なデータを与える方式です。

結果は、行数が500万以上になると、スクロール性能(WPFと同様スライダーでの表示位置移動)が悪く、とても使いものになりませんでした。また、行数(RowCount)に大きな数を指定するとグリッドが表示されるまでに時間が掛かり、仮想グリッドなのに行数に比例してメモリ消費量が増えるのはチョット納得できないものでした。

また、よいアイデアありましたらお願いします。