LoginSignup
1
2

More than 3 years have passed since last update.

DataGridViewの縦スクロールの描画パフォーマンスを改善した話

Last updated at Posted at 2019-11-26

何が書いてあるか?

  • DataGridViewの縦スクロールの描画パフォーマンスを上げた方法
  • 原因の特定方法(大したことではないが)
  • リフレクションで無理やりSetプロパティの制約を回避した話

役に立つのか?

特殊な状況下なので、万人に役にたつかは微妙。
あ、たぶん。役に立たないです。
リフレクション使えば、例外回避して設定できるのかーぐらいかな。
(そもそも、リフレクション使う人は、それぐらい知ってると思うが)

特殊な状況について

DataGridViewを使いスプレッドシートの入出力画面を作成した。
カラムヘッダは独自に直接セル結合を行っていてFixed設定をしている。
※カラムヘッダは非表示(ColumnHeadersVisible = false)
ロウヘッダは行選択をするため残している。(RowHeadersVisible = true)

事象

DataGridViewの縦スクロール時に、上下端からスクロールする場合のスピードが顕著に遅い(カクカクする)

状況調査&原因特定?

DataGridViewはDataSourceを切り替えて使いまわしているが、遅いのは特定の場合で、すべてが遅いわけではない。

データ量の問題かと、表示レイアウトはそのままにDataSourceのみ変更したところ変化が無いことがわかった
⇒ つまり、データ量、DataSouceは関係ない

遅いところと、そうでないところの違いを調べたところ、
RowHeadersVisible の違いがあることがわかり、非表示にしたところパフォーマンスが改善した。

これが問題か?

対応方法について

RowHeadersVisible の設定でのパフォーマンス有無についてネットで調べたが特に有益な情報は得られず。
パフォーマンス改善について、ダブルバッファリングの手法があることがわかった。
以下、対応内容と採用可否である。

  1. RowHeadersの非表示
    時間があれば、独自実装して1カラム目をヘッダーのように扱うことは可能だが、ナイーブな(お察し下さい)実装のため残念ながら採用できない。

  2. ダブルバッファリング
    ネットで多くみかけたのですが、実際に試してみると、
    早くはなるものの、結合セルの部分の再描画が行われる黒くなり使い物にならない。

ここで手詰まりかと思っていたが、天からのひらめき。
RowHeadersVisibleのtrue/falseで、スピードが変わるならColumnHeadersも効果あるのでは?

ということで、第三の方法として

 3. ColumnHeadersの「表示」
本来、非表示であるカラムヘッダを表示してみたところ、パフォーマンスが改善されたのである。

ちょっとの格闘と対応の完全版

結局原因自体は、よくわからんが、カラムヘッダを表示した場合、パフォーマンスが改善された。
ちなみに、検証結果は以下の通り。
ピンポイントの組み合わせで遅かったよ・・・(多くの人は、この事象に出会わないのでは?)

RowHeadersVisible ColumnHeadersVisible 結果
true true OK
true false 遅い
false true OK
false false OK

というわけで、ヘッダの高さをゼロにして非表示扱いにしてみよう。
というのが今回のまとめになる。

dgv.ColumnHeadersHeight = 0; //これは、うまくいかない

ところが、ゼロを実際に設定してみたところ例外が発生した。

MSDN(DataGridView.ColumnHeadersHeight プロパティ)を見ると3以下は設定できないようだ。

じゃあ、リフレクションを使ってみようと試してみるが、プロパティはあってもフィールドが見つからない。(NonPublicを指定しているのに)
プロパティ(PropertyInfo)のSetValueを使ってみると例外が発生する・・・

と、しばらく格闘していたが、わかってみればなんのことはない。
使っているクラスが、CommonFramework.Forms.DataGridViewでなく、それを継承したクラスだったからだ。
なまじpublicのプロパティが見えるので気づくのに時間がかかった。

というわけで、以下のコードで対応。

// dgv は、DataGridView(を継承したクラス)のインスタンス
dgv.GetType().BaseType
   .GetField("columnHeadersHeight", BindingFlags.Instance | BindingFlags.NonPublic)
   .SetValue(dgv, 0);

なるほど。インスタンスからGetTypeするから混乱の元になったのか。
完全に動的に操作する必要ないから以下で大丈夫だった。
※とはいえ、フィールド名 columnHeadersHeight を特定するのが大変だったわけだが。

typeof(DataGridView)
   .GetField("columnHeadersHeight", BindingFlags.Instance | BindingFlags.NonPublic)
   .SetValue(dgv, 0);

.Netのバージョンだったり、そもそも継承の有無で役に立つ可能性は、わからないがメモとして残しておく。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2