DataGridView Workaround

DataGridViewのセル描画性能の悪さに関して、何度か言及した。

From FeedBack
DataGridViewのセル描画性能の結論

これに対して、セルの描画を独自に行った場合に性能に差が出るか(差が出るのであれば、独自に描画処理を書く意味がある)を調べてみた。

  • DataGridViewTextBoxCellを拡張する

恐らく、セルとしては一番使うだろうと思われる、同クラスの描画処理を書き換えるために拡張したクラスを用意して、Paintメソッドをオーバライドしてみた。

public class ExtDGVTextBoxCell : DataGridViewTextBoxCell
{
    private static Brush bgBrush = Brushes.White;
    protected override void Paint(
        Graphics graphics
        , Rectangle clipBounds
        , Rectangle cellBounds
        , int rowIndex
        , DataGridViewElementStates cellState
        , object value
        , object formattedValue
        , string errorText
        , DataGridViewCellStyle cellStyle
        , DataGridViewAdvancedBorderStyle advancedBorderStyle
        , DataGridViewPaintParts paintParts)
    {
        if ((paintParts & DataGridViewPaintParts.Background) == DataGridViewPaintParts.Background)
        {
            graphics.FillRectangle(bgBrush, cellBounds);
        }
        bool selected = (cellState & DataGridViewElementStates.Selected) != DataGridViewElementStates.None;
        TextRenderer.DrawText(
            graphics
            , value.ToString()
            , cellStyle.Font
            , cellBounds
            , selected ? cellStyle.SelectionForeColor : cellStyle.ForeColor
            , TextFormatFlags.Default);

        if ((paintParts & DataGridViewPaintParts.Border) == DataGridViewPaintParts.Border)
        {
            PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle);
        }
    } 
}

セルの最小限の描画処理としていうことで、上から順に 「背景」-> 「セルの値(テキスト)」-> 「ボーダ」と描画処理を行っている。あと、背景描画に使用しているブラシはセル毎ではなく、単に白色のブラシを用意している。必要最小限の描画を行うということに加えて、仮にDataGridViewTextBoxCellクラスが描画の度にブラシを生成しているとすれば、ここで処理性能に差がでるはずだ。

  • DataGridViewクラスを拡張する

拡張するといっても面倒なことをしている訳ではなく、単に描画処理の経過時間を見るためにOnPaintをオーバライドしているだけだ。

public class ExtDataGridView : DataGridView
{
    protected override void OnPaint(PaintEventArgs e)
    {
        int start = Environment.TickCount;
        base.OnPaint(e);
        Debug.WriteLine(
            string.Format("DGV Paint( {0} ) time = {1} msec."
                          , e.ClipRectangle, (Environment.TickCount - start)));
    }
}
  • サンプルコードを用意する

サンプルといっても、10列のDataGridViewTextBoxColumnを作り、500行のデータを流しこむだけ。

foreach(DataGridViewColumn column in dataGridView1.Columns)
{
    column.CellTemplate = new ExtDGVTextBoxCell(); //この行でセルテンプレートを切り替える
}
string text = new string
  { "カラム1用のテキスト。何でもよいが桁数は必要"
   ,"カラム2用のテキスト。何でもよいが桁数は必要"
    :
    :
   ,"カラム10用のテキスト。何でもよいが桁数は必要"};
for (int i = 0; i < 500; i++)
{
    this.dataGridView1.Rows.Add(text);
}
  • 計測する

比較といっても、単純に通常のDataGridViewTextBoxCellをセルテンプレートに使用した場合と、最小限の描画処理を行うExtDGVTextBoxCellを使用した時の起動後にデフォルトの矩形を描画した際の時間の差を見るだけだ。

    • DataGridViewTextBoxCellを使用した場合
DGV Paint( {X=0,Y=0,Width=579,Height=529} ) time = 344 msec.
    • ExtDGVTextBoxCellを使用した場合
DGV Paint( {X=0,Y=0,Width=579,Height=529} ) time = 219 msec.

※1

と描画処理を最小限に抑えても、短縮できたのは高々100msという結果になった。残念ながらこの差は自前で描画する理由にはならないだろう。(遅さの)原因はもっと根本的な部分にありそうだ。

※1 これらの計測値は.NET Frameworkの性能を示す絶対値ではなく、私個人の環境で、描画命令による処理時間の違いを相対的に見るだけのものであり、それ以上の意味は無いことをお断りしておく。